mirror of
https://github.com/woutdp/live_svelte
synced 2026-05-24 01:18:53 +00:00
Refactor handling SSR and slots
This commit is contained in:
parent
4e3dd6960c
commit
0f5bbfebaa
6 changed files with 100 additions and 45 deletions
|
|
@ -15,56 +15,62 @@ function base64ToElement(base64) {
|
|||
return template
|
||||
}
|
||||
|
||||
export const createSlots = (slots, el) => {
|
||||
function createSlot(content) {
|
||||
element = base64ToElement(content)
|
||||
function dataAttributeToJson(attributeName, el) {
|
||||
const data = el.getAttribute(attributeName)
|
||||
return data ? JSON.parse(data) : {}
|
||||
}
|
||||
|
||||
function createSlots(slots, ref) {
|
||||
const createSlot = (slotName, ref) => {
|
||||
let savedTarget, savedAnchor, savedElement
|
||||
return () => {
|
||||
return {
|
||||
getElement() {
|
||||
return base64ToElement(dataAttributeToJson('data-slots', ref.el)[slotName])
|
||||
},
|
||||
update() {
|
||||
const element = this.getElement()
|
||||
detach(savedElement)
|
||||
insert(savedTarget, element, savedAnchor)
|
||||
savedElement = element
|
||||
},
|
||||
c: noop,
|
||||
m(target, anchor) {
|
||||
const element = this.getElement()
|
||||
savedTarget = target
|
||||
savedAnchor = anchor
|
||||
savedElement = element
|
||||
insert(target, element, anchor);
|
||||
insert(target, element, anchor)
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching && element.innerHTML) {
|
||||
detach(element);
|
||||
}
|
||||
if (detaching) detach(savedElement)
|
||||
},
|
||||
l: noop,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const svelteSlots = {}
|
||||
|
||||
for (const slotName in slots) {
|
||||
svelteSlots[slotName] = [createSlot(slots[slotName])];
|
||||
svelteSlots[slotName] = [createSlot(slotName, ref)]
|
||||
}
|
||||
|
||||
return svelteSlots
|
||||
}
|
||||
|
||||
function getProps(ref) {
|
||||
const dataProps = ref.el.getAttribute('data-props')
|
||||
const props = dataProps ? JSON.parse(dataProps) : {}
|
||||
|
||||
return {
|
||||
...props,
|
||||
...dataAttributeToJson('data-props', ref.el),
|
||||
pushEvent: (event, data, callback) => ref.pushEvent(event, data, callback),
|
||||
$$slots: createSlots({default: ref.el.getAttribute('data-slot-default')}, ref.el),
|
||||
$$slots: createSlots(dataAttributeToJson('data-slots', ref.el), ref),
|
||||
$$scope: {}
|
||||
}
|
||||
}
|
||||
|
||||
function findSlotCtx(component) {
|
||||
// The default slot always exists if there's a slot set
|
||||
// even if no slot is set for the explicit default slot
|
||||
return component.$$.ctx.find(ctxElement => ctxElement.default)
|
||||
}
|
||||
|
||||
|
|
@ -88,8 +94,14 @@ const SvelteComponent = {
|
|||
},
|
||||
|
||||
updated() {
|
||||
// Set the props
|
||||
this._instance.$set(getProps(this))
|
||||
findSlotCtx(this._instance).default[0]().update()
|
||||
|
||||
// Set the slots
|
||||
const slotCtx = findSlotCtx(this._instance)
|
||||
for (const key in slotCtx) {
|
||||
slotCtx[key][0]().update()
|
||||
}
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
module.exports.render = (name, props={}, slots=null) => {
|
||||
const ssrComponent = require('../../priv/static/assets/server/server.js')[name].default
|
||||
const $$slots = slots ? {default: () => slots} : {}
|
||||
slots = Object.fromEntries(Object.entries(slots).map(([k, v]) => [k, () => v]))
|
||||
const $$slots = slots || {}
|
||||
return ssrComponent.render(props, {$$slots, context: new Map()})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ defmodule LiveSvelte do
|
|||
use Phoenix.LiveComponent
|
||||
import Phoenix.HTML
|
||||
|
||||
alias LiveSvelte.Slots
|
||||
alias LiveSvelte.SSR
|
||||
|
||||
attr(:props, :map, default: %{})
|
||||
attr(:name, :string)
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ defmodule LiveSvelte do
|
|||
id={id(@name)}
|
||||
data-name={@name}
|
||||
data-props={json(@props)}
|
||||
data-slot-default={Base.encode64(get_slot(assigns))}
|
||||
data-slots={Slots.base_encode_64(@slots) |> json}
|
||||
phx-update="ignore"
|
||||
phx-hook="SvelteComponent"
|
||||
>
|
||||
|
|
@ -45,39 +48,26 @@ defmodule LiveSvelte do
|
|||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
slots =
|
||||
assigns
|
||||
|> Slots.rendered_slot_map()
|
||||
|> Slots.js_process()
|
||||
|
||||
# Making sure we only render once
|
||||
ssr_code =
|
||||
if not connected?(socket) do
|
||||
props = Map.get(assigns, :props, %{})
|
||||
slot = get_slot(assigns)
|
||||
ssr_render(assigns.name, props, slot)
|
||||
SSR.render(assigns.name, Map.get(assigns, :props, %{}), slots)
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(:slots, slots)
|
||||
|> assign(:ssr_render, ssr_code)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
defp get_slot(assigns) do
|
||||
~H"""
|
||||
<%= if assigns[:inner_block] do %>
|
||||
<%= render_slot(@inner_block) %>
|
||||
<% end %>
|
||||
"""
|
||||
|> Phoenix.HTML.Safe.to_iodata()
|
||||
|> List.to_string()
|
||||
|> String.trim()
|
||||
end
|
||||
|
||||
defp ssr_render(name, props, slots \\ nil)
|
||||
defp ssr_render(name, nil, slots), do: ssr_render(name, %{}, slots)
|
||||
|
||||
defp ssr_render(name, props, slots),
|
||||
do: NodeJS.call!({"svelte/render", "render"}, [name, props, slots])
|
||||
|
||||
defp json(props) do
|
||||
props
|
||||
|> Jason.encode()
|
||||
|
|
|
|||
53
lib/slots.ex
Normal file
53
lib/slots.ex
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
defmodule LiveSvelte.Slots do
|
||||
import Phoenix.Component
|
||||
|
||||
def rendered_slot_map(assigns) do
|
||||
assigns
|
||||
|> filter_slots_from_assigns()
|
||||
|> render_slots()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Processes the slots for use in JavaScript.
|
||||
"""
|
||||
def js_process(assigns) do
|
||||
assigns
|
||||
|> Enum.map(fn
|
||||
{:inner_block, value} -> {:default, value}
|
||||
key_value -> key_value
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
def base_encode_64(assigns) do
|
||||
assigns
|
||||
|> Enum.map(fn {key, value} -> {key, Base.encode64(value)} end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
defp filter_slots_from_assigns(assigns) do
|
||||
assigns
|
||||
|> Enum.filter(fn
|
||||
{_key, [%{__slot__: _}]} -> true
|
||||
_ -> false
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
defp render_slots(assigns) do
|
||||
Enum.reduce(assigns, %{}, fn
|
||||
{key, value}, acc -> Map.put(acc, key, render(%{slot: value}))
|
||||
end)
|
||||
end
|
||||
|
||||
defp render(assigns) do
|
||||
~H"""
|
||||
<%= if assigns[:slot] do %>
|
||||
<%= render_slot(@slot) %>
|
||||
<% end %>
|
||||
"""
|
||||
|> Phoenix.HTML.Safe.to_iodata()
|
||||
|> List.to_string()
|
||||
|> String.trim()
|
||||
end
|
||||
end
|
||||
7
lib/ssr.ex
Normal file
7
lib/ssr.ex
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
defmodule LiveSvelte.SSR do
|
||||
def render(name, props, slots \\ nil)
|
||||
def render(name, nil, slots), do: render(name, %{}, slots)
|
||||
|
||||
def render(name, props, slots),
|
||||
do: NodeJS.call!({"svelte/render", "render"}, [name, props, slots])
|
||||
end
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
defmodule LiveSvelteTest do
|
||||
use ExUnit.Case
|
||||
doctest LiveSvelte
|
||||
|
||||
test "greets the world" do
|
||||
assert LiveSvelte.hello() == :world
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue