woutdp / live_svelte Goto Github PK
View Code? Open in Web Editor NEWSvelte inside Phoenix LiveView with seamless end-to-end reactivity
Home Page: https://hexdocs.pm/live_svelte
License: MIT License
Svelte inside Phoenix LiveView with seamless end-to-end reactivity
Home Page: https://hexdocs.pm/live_svelte
License: MIT License
Note that you can't make it happen by refreshing the counter page. The mermaid page is definitely part of the problem and you make the crash happen by simply refreshing it but sometimes it works.
Error message shown in browser
Error message shown in 'mix phx.server' console:
/Users/dg/agle/202308/phoenix-ash-svelte-flowbite/priv/static/assets/server/server.js:14964
return typeof selector === "string" ? new Selection([[document.querySelector(selector)]], [document.documentElement]) : new Selection([[selector]], root);
^
ReferenceError: document is not defined
at select_default2 (/Users/dg/agle/202308/phoenix-ash-svelte-flowbite/priv/static/assets/server/server.js:14964:57)
at Object.render3 [as render] (/Users/dg/agle/202308/phoenix-ash-svelte-flowbite/priv/static/assets/server/server.js:181274:19)
at /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/priv/static/assets/server/server.js:181576:22
at new Promise (<anonymous>)
at performCall (/Users/dg/agle/202308/phoenix-ash-svelte-flowbite/priv/static/assets/server/server.js:181575:35)
at executeQueue ([info] GET /
[debug] Processing with KantanWeb.PageController.home/2
Parameters: %{}
Pipelines: [:browser]
[info] Sent 200 in 10ms
[info] GET /svelte/mermaid
[debug] Processing with KantanWeb.PageController.mermaid/2
Parameters: %{}
Pipelines: [:browser]
[error] GenServer #PID<0.613.0> terminating
** (ArgumentError) argument error
:erlang.port_command(#Port<0.9>, "[[\"server/server\",\"ssrRenderComponent\"],[\"Mermaid\",{},{}]]\n")
(nodejs 2.0.0) lib/nodejs/worker.ex:102: NodeJS.Worker.handle_call/3
(stdlib 4.3) gen_server.erl:1149: :gen_server.try_handle_call/4
(stdlib 4.3) gen_server.erl:1178: :gen_server.handle_msg/6
(stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.831.0>): {{"server/server", "ssrRenderComponent"}, ["Mermaid", %{}, %{}], [binary: false, timeout: 30000]}
State: ["/Users/dg/agle/202308/phoenix-ash-svelte-flowbite/_build/dev/lib/nodejs/priv/server.js", #Port<0.9>]
Client #PID<0.831.0> is alive
(stdlib 4.3) gen.erl:256: :gen.do_call/4
(elixir 1.14.4) lib/gen_server.ex:1035: GenServer.call/3
(nodejs 2.0.0) lib/nodejs/supervisor.ex:38: anonymous fn/5 in NodeJS.Supervisor.run_in_transaction/3
(poolboy 1.5.2) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/poolboy/src/poolboy.erl:84: :poolboy.transaction/3
(nodejs 2.0.0) lib/nodejs/supervisor.ex:68: NodeJS.Supervisor.call/3
(nodejs 2.0.0) lib/nodejs/supervisor.ex:77: NodeJS.Supervisor.call!/3
(live_svelte 0.5.1) lib/ssr.ex:19: LiveSvelte.SSR.render/3
(live_svelte 0.5.1) lib/component.ex:56: LiveSvelte."render (overridable 1)"/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/tag_engine.ex:68: Phoenix.LiveView.TagEngine.component/3
(kantan 0.1.0) lib/kantan_web/controllers/page_html/mermaid.html.heex:1: anonymous fn/2 in KantanWeb.PageHTML.mermaid/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/engine.ex:137: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/engine.ex:153: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/3
(phoenix 1.7.2) lib/phoenix/controller.ex:1010: anonymous fn/5 in Phoenix.Controller.template_render_to_iodata/4
(telemetry 1.2.1) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix 1.7.2) lib/phoenix/controller.ex:976: Phoenix.Controller.render_and_send/4
(kantan 0.1.0) lib/kantan_web/controllers/page_controller.ex:1: KantanWeb.PageController.action/2
(kantan 0.1.0) lib/kantan_web/controllers/page_controller.ex:1: KantanWeb.PageController.phoenix_controller_pipeline/2
(phoenix 1.7.2) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
(kantan 0.1.0) lib/kantan_web/endpoint.ex:1: KantanWeb.Endpoint.plug_builder_call/2
(kantan 0.1.0) deps/plug/lib/plug/debugger.ex:136: KantanWeb.Endpoint."call (overridable 3)"/2
[info] Sent 500 in 122ms
[error] #PID<0.831.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.783.0>, stream id 8) terminated
Server: localhost:4000 (http)
Request: GET /svelte/mermaid
** (exit) an exception was raised:
** (NodeJS.Error) got {:node_js_worker_exit, {{:badarg, [{:erlang, :port_command, [#Port<0.9>, "[[\"server/server\",\"ssrRenderComponent\"],[\"Mermaid\",{},{}]]\n"], [error_info: %{module: :erl_erts_errors}]}, {NodeJS.Worker, :handle_call, 3, [file: 'lib/nodejs/worker.ex', line: 102]}, {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 1149]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 1178]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]}, {GenServer, :call, [#PID<0.613.0>, {{"server/server", "ssrRenderComponent"}, ["Mermaid", %{}, %{}], [binary: false, timeout: 30000]}, 30000]}}} while retrieving Exception.message/1 for %NodeJS.Error{message: {:node_js_worker_exit, {{:badarg, [{:erlang, :port_command, [#Port<0.9>, "[[\"server/server\",\"ssrRenderComponent\"],[\"Mermaid\",{},{}]]\n"], [error_info: %{module: :erl_erts_errors}]}, {NodeJS.Worker, :handle_call, 3, [file: 'lib/nodejs/worker.ex', line: 102]}, {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 1149]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 1178]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]}, {GenServer, :call, [#PID<0.613.0>, {{"server/server", "ssrRenderComponent"}, ["Mermaid", %{}, %{}], [binary: false, timeout: 30000]}, 30000]}}}, stack: nil} (expected a string)
(nodejs 2.0.0) lib/nodejs/supervisor.ex:80: NodeJS.Supervisor.call!/3
(live_svelte 0.5.1) lib/ssr.ex:19: LiveSvelte.SSR.render/3
(live_svelte 0.5.1) lib/component.ex:56: LiveSvelte."render (overridable 1)"/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/tag_engine.ex:68: Phoenix.LiveView.TagEngine.component/3
(kantan 0.1.0) lib/kantan_web/controllers/page_html/mermaid.html.heex:1: anonymous fn/2 in KantanWeb.PageHTML.mermaid/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/engine.ex:137: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/engine.ex:153: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/3
(phoenix 1.7.2) lib/phoenix/controller.ex:1010: anonymous fn/5 in Phoenix.Controller.template_render_to_iodata/4
(telemetry 1.2.1) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix 1.7.2) lib/phoenix/controller.ex:976: Phoenix.Controller.render_and_send/4
(kantan 0.1.0) lib/kantan_web/controllers/page_controller.ex:1: KantanWeb.PageController.action/2
(kantan 0.1.0) lib/kantan_web/controllers/page_controller.ex:1: KantanWeb.PageController.phoenix_controller_pipeline/2
(phoenix 1.7.2) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
(kantan 0.1.0) lib/kantan_web/endpoint.ex:1: KantanWeb.Endpoint.plug_builder_call/2
(kantan 0.1.0) deps/plug/lib/plug/debugger.ex:136: KantanWeb.Endpoint."call (overridable 3)"/2
(kantan 0.1.0) lib/kantan_web/endpoint.ex:1: KantanWeb.Endpoint.call/2
(phoenix 1.7.2) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.10.0) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.10.0) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
To avoid wasting memory on features that might not be used, some Svelte frameworks, such as Svelte Skeleton v2, use singleton stores that are intended to be created via top-level SvelteKit files such as +layout.svelte
.
<script>
import { initializeStores } from '@skeletonlabs/skeleton';
initializeStores();
</script>
initializeToastStore()
does this:
return setContext(TOAST_STORE_KEY, toastStore);
In other words, the Toast store is a singleton that is accessed via a top-level Svelte context that is available to every Svelte component in the application.
A LiveSvelte application needs to initialize the Skeleton toast store somehow. The top-level objects of a Phoenix application are generally app.js
and app.html.heex.
Of course, using app.js
doesn't work because Svelte throws an error when getContext
/setContext
/hasContext
are called outside of component initialization. Apparently, only a .svelte file can initiate a workflow that calls *Context()
since contexts are associated with Svelte components.
Heex templates can include Svelte components via LiveSvelte. Can app.html.heex
do it? To try, I created a Svelte component that initializes the Toast store. I added the following to the top of app.html.heex
: <.svelte name='Toast'>
.
Toast.svelte
:
<script>
import { initializeStores, Toast } from '@skeletonlabs/skeleton';
initializeStores();
</script>
<Toast />
My application starts fine -- in fact, it works with Svelte Skeleton 1.x so long as you remove the bit about initializeStores
. However, this doesn't work with Skeleton 2.x which now uses Svelte contexts. Svelte components must share a common ancestor in order to access the same context instance. Since the above component doesn't have any children, other Svelte components in my application can't access the context singleton created by it. Therefore, they complain that initializeStores
has not been called.
Top-level contexts could be established via a parent LiveSvelte component with slots and/or subcomponents. However, such an approach is at odds with creating a Phoenix application that consists of dead views, live views, and LiveSvelte components.
Working with frameworks that require SvelteKit (because of top-level Svelte contexts) would be a huge win because the most interesting parts of the Svelte ecosystem are headed that way.
Reported by victorbjorklund: https://elixirforum.com/t/livesvelte-e2e-reactivity-in-liveview-with-svelte/54822/2?u=woutdp
But getting some errors when building in Docker:
=> ERROR [builder 16/20] RUN mix assets.deploy 1.3s
[builder 16/20] RUN mix assets.deploy:
#26 1.253 /app/assets/node_modules/esbuild-svelte/dist/index.js:23
#26 1.253 const svelteFilter = (options == null ? void 0 : options.include) ?? SVELTE_FILTER;
#26 1.253 ^
#26 1.253
#26 1.253 SyntaxError: Unexpected token ‘?’
#26 1.253 at wrapSafe (internal/modules/cjs/loader.js:915:16)
#26 1.253 at Module._compile (internal/modules/cjs/loader.js:963:27)
#26 1.253 at Object.Module._extensions…js (internal/modules/cjs/loader.js:1027:10)
#26 1.253 at Module.load (internal/modules/cjs/loader.js:863:32)
#26 1.253 at Function.Module._load (internal/modules/cjs/loader.js:708:14)
#26 1.253 at Module.require (internal/modules/cjs/loader.js:887:19)
#26 1.253 at require (internal/modules/cjs/helpers.js:74:18)
#26 1.253 at Object. (/app/assets/build.js:2:22)
#26 1.253 at Module._compile (internal/modules/cjs/loader.js:999:30)
#26 1.253 at Object.Module._extensions…js (internal/modules/cjs/loader.js:1027:10)
#26 1.268 ** (exit) 1
#26 1.268 (mix 1.14.2) lib/mix/tasks/cmd.ex:74: Mix.Tasks.Cmd.run/1
#26 1.268 (mix 1.14.2) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
#26 1.268 (mix 1.14.2) lib/mix/task.ex:479: Mix.Task.run_alias/6
#26 1.268 (mix 1.14.2) lib/mix/cli.ex:84: Mix.CLI.run_task/2
executor failed running [/bin/sh -c mix assets.deploy]: exit code: 1
Not sure if error is LiveSvelte, Esbuild-svelte or Docker.
Awesome stack.
Loving every bit of it. Allow us to support this project by becoming a patron.
I believe this Phoenix + Svelte combo has a lot of potential. Cheers
This could be cool, although maybe not needed? Worth looking into
esbuild
broke the way build works starting with 0.17.0
and I'm trying to make it work with live_svelte
.
esbuild
"^0.17.19"I did a bit of fiddling and tried to:
optClient
and optsServer
in build.js
://
// Comment out usage of "watch" in optClient and optsServer
// // watch,
//
// const client = esbuild.build(optsClient)
// const server = esbuild.build(optsServer)
// if (watch) {
// client.then(_result => {
// process.stdin.on("close", () => process.exit(0))
// process.stdin.resume()
// })
// server.then(_result => {
// process.stdin.on("close", () => process.exit(0))
// process.stdin.resume()
// })
// }
const ctx0 = esbuild.context(optsClient)
ctx0.then(v => {
if (watch) {
v.watch().then(v => {
})
v.dispose();
}
});
const ctx1 = esbuild.context(optsServer)
ctx1.then(v => {
if (watch) {
v.watch().then(v => {
})
v.dispose();
}
});
(I know I would want to use "await" but esbuild is not happy that it's top-level)
I also ended up adding a bunch of dependencies to package.json
not mentioned in the README:
{
"devDependencies": {
},
"dependencies": {
"esbuild": "^0.17.19",
"esbuild-svelte": "^0.7.3",
"esbuild-plugin-import-glob": "^0.1.1",
"svelte-preprocess": "^5.0.4",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
}
}
I feel I'm close. The only error I'm getting is this one:
mix.bat phx.server
[info] Running ProjectWeb.Endpoint with cowboy 2.10.0 at 127.0.0.1:4000 (http)
[info] Access ProjectWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
[watch] build finished, watching for changes...
X [ERROR] Could not resolve "live_svelte"
js/app.js:31:25:
31 Γöé import { getHooks } from "live_svelte"
Γò╡ ~~~~~~~~~~~~~
You can mark the path "live_svelte" as external to exclude it from the bundle, which will remove this error.
1 error
Rebuilding...
Done in 454ms.
So I wonder if there's any hints for me. I love where this project is going VERY much! Now I would love it to actually work too 🥇
I think there should be an easer way to install LiveSvelte:
config.exs
record as we can get the server file path directly from within the js fileNeed an option to disable SSR for a Svelte implemented with the ~V sigil.
Currently we invoke the SSR render call on every update, this should only happen on mount.
I've found it difficult to achieve as the mount doesn't have the assign attributes and the update and preload function don't actually update the attributes.
I think the way of going about it is setting the state in the parent LiveView somehow and then check if it already ran the SSR render
For the SSR, we sometimes return a head depending if the Svelte component has something like:
<svelte:head>
<title>Some title</title>
</svelte:head>
Ideally we include this in the root head somehow on first page render, I haven't found a way to do this from the live_component.
Currently this just does not work and we don't include the head, we only include the styling.
I have a question regarding server side rendering
LightStatusBar.svelte
<script>
import { tweened } from "svelte/motion";
import { cubicOut } from "svelte/easing";
export let brightness = 0;
const progress = tweened(0, {
duration: 400,
easing: cubicOut,
});
const updateProgress = (b) => progress.set(b / 100);
$: updateProgress(brightness);
</script>
<progress class="border-none w-full rounded-lg h-12" value={$progress} />
<div
class="h-12 rounded-md w-full font-mono font-semibold"
>
<div class="text-center w-full flex items-center justify-center h-full">
{brightness > 0 ? `${brightness}%` : "OFF"}
</div>
</div>
<style>
:root{
--progColor: linear-gradient(to right, hsl(6, 100%, 80%), hsl(356, 100%, 65%));
--progHeight: 20px;
}
progress, progress::-webkit-progress-value {
width: 100%;
border: 0;
height: var(--progHeight);
border-radius: 20px;
background: var(--progColor);
}
progress::-webkit-progress-bar {
width: 100%;
border: 0;
height: var(--progHeight);
border-radius: 20px;
background: white;
}
progress::-moz-progress-bar {
width: 100%;
border: 0;
height: var(--progHeight);
border-radius: 20px;
background: var(--progColor);
}
</style>
LightControllers.svelte
<script>
export let pushEvent;
export let isOn = false;
const toggleLight = () => {
isOn = !isOn;
pushEvent(isOn ? "on" : "off");
};
</script>
<div class="my-2 flex justify-between items-center">
<button
type="button"
class={`${
isOn ? "bg-brand" : "bg-gray-200"
} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-brand focus:ring-offset-2`}
role="switch"
aria-checked="false"
on:click={toggleLight}
>
<span class="sr-only">Use setting</span>
<span
aria-hidden="true"
class={`${
isOn ? "translate-x-5" : "translate-x-0"
} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`}
/>
</button>
<div class="flex gap-2">
<button
phx-click="down"
class="flex items-center justify-center text-xl font-bold w-10 h-10 p-2 rounded bg-slate-100 hover:bg-brand active:bg-brand shadow-md hover:shadow-xl"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</button>
<button
phx-click="up"
class="flex items-center justify-center text-xl font-bold w-10 h-10 p-2 rounded bg-slate-100 hover:bg-brand active:bg-brand shadow-md hover:shadow-xl"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 15.75l7.5-7.5 7.5 7.5"
/>
</svg>
</button>
</div>
</div>
live_page.ex
defmodule LightBulbWeb.LivePage do
use LightBulbWeb, :live_view
require Logger
def mount(_params, _session, socket) do
Logger.info(socket)
{:ok, assign(socket, %{brightness: 10, previous: nil})}
end
def render(assigns) do
~H"""
<div class="max-w-screen-xl mx-auto p-4 flex flex-col gap-4">
<h1 class="text-center text-2xl font-light my-4">Light Bulb Controller</h1>
<LiveSvelte.render name="LightStatusBar" props={%{brightness: @brightness}} />
<LiveSvelte.render name="LightControllers" props={%{isOn: isOn?(@brightness)}} />
</div>
"""
end
def handle_event("up", _, socket) do
brightness = socket.assigns.brightness
Logger.info brightness
case brightness do
100 ->
{:noreply, socket}
_ ->
new_brightness = brightness + 10
{:noreply, assign(socket, %{brightness: new_brightness, previous: brightness})}
end
end
def handle_event("down", _, socket) do
brightness = socket.assigns.brightness
Logger.info brightness
case brightness do
0 ->
{:noreply, socket}
_ ->
new_brightness = brightness - 10
{:noreply, assign(socket, %{brightness: new_brightness, previous: brightness})}
end
end
def handle_event("on", _, socket) do
previous = socket.assigns.previous
brightness = socket.assigns.brightness
{:noreply, assign(socket, %{brightness: previous, previous: brightness})}
end
def handle_event("off", _, socket) do
previous = socket.assigns.brightness
{:noreply, assign(socket, %{brightness: 0, previous: previous})}
end
defp isOn?(brightness) do
brightness > 0
end
end
Even thought I trigger Pheonix function up/down to increase or decrease the brightness, on SSR I do get the initial state of 10%
What am I missing? Do I need to change somehow the handle_event functions or is it on the Svelte side?
Thank You
Slots are not supported by LiveSvelte because of issues in Svelte itself. I don't have the issue number but once it's resolved, this usage can be officially supported.
A working named slot example is in the following files. Timeline.svelte optionally accepts a slot named 'toast.' Other use cases might not work.
Note: The Svelte way to specify slot content is something like < span slot='slot_name'> . This doesn't work in Heex templates. Use the Phoenix <: slot_name> way instead.
Another downside is that Elixir warns when compiling:
warning: undefined slot "toast" for component LiveSvelte.svelte/1
lib/kantan_web/controllers/page_html/timeline.html.heex:6
I don't like it copies the entire file, while the important change is just one line. Ideally it changes the target tailwind file and adds the line, and if it can't find it, don't add it. And in addition add some instructions to setup tailwind yourself if you want.
https://elixirforum.com/t/livesvelte-e2e-reactivity-in-liveview-with-svelte/54822/4
Hi @woutdp thanks for creating this project! I’ve used it successfully in a DEV environment, but I can’t figure out how to get SSR to work when I deploy it to fly.io.
Request: GET /demo/select_tickets
** (exit) an exception was raised:
** (NodeJS.Error) Cannot find module 'js/render'
Require stack:
- /app/lib/nodejs-2.0.0/priv/server.js
Error: Cannot find module 'js/render'
Require stack:
- /app/lib/nodejs-2.0.0/priv/server.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1021:15)
at Function.resolve (node:internal/modules/cjs/helpers:114:19)
at requireModule (/app/lib/nodejs-2.0.0/priv/server.js:10:34)
at requireModuleFunction (/app/lib/nodejs-2.0.0/priv/server.js:25:15)
at callModuleFunction (/app/lib/nodejs-2.0.0/priv/server.js:31:14)
at getResponse (/app/lib/nodejs-2.0.0/priv/server.js:44:26)
at Interface.onLine (/app/lib/nodejs-2.0.0/priv/server.js:53:39)
at Interface.emit (node:events:513:28)
at Interface._onLine (node:readline:491:10)
at Interface._normalWrite (node:readline:665:12)
(nodejs 2.0.0) lib/nodejs/supervisor.ex:80: NodeJS.Supervisor.call!/3
(live_svelte 0.3.5) lib/ssr.ex:19: LiveSvelte.SSR.render/3
(live_svelte 0.3.5) lib/component.ex:56: LiveSvelte."render (overridable 1)"/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/tag_engine.ex:68: Phoenix.LiveView.TagEngine.component/3
(camp 0.1.1) lib/camp_web/live_demo/demo_select_tickets_live.ex:112: anonymous fn/2 in CampWeb.DemoSelectTicketsLive.render/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:398: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
I’ve checked that I’ve installed NodeJS on both the builder and runner images, and I’ve tried both 16.x and 18.x versions.
When I remote console into the running application, I get:
NodeJS.call("console.log", ["helloworld"])
{:error,
"Cannot find module 'console.log'\nRequire stack:\n- /app/lib/nodejs-2.0.0/priv/server.js\nError: Cannot find module 'console.log'\nRequire stack:\n- /app/lib/nodejs-2.0.0/priv/server.js\n at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1021:15)\n at Function.resolve (node:internal/modules/cjs/helpers:114:19)\n at requireModule (/app/lib/nodejs-2.0.0/priv/server.js:10:34)\n at requireModuleFunction (/app/lib/nodejs-2.0.0/priv/server.js:25:15)\n at callModuleFunction (/app/lib/nodejs-2.0.0/priv/server.js:31:14)\n at getResponse (/app/lib/nodejs-2.0.0/priv/server.js:44:26)\n at Interface.onLine (/app/lib/nodejs-2.0.0/priv/server.js:53:39)\n at Interface.emit (node:events:513:28)\n at Interface._onLine (node:readline:491:10)\n at Interface._normalWrite (node:readline:665:12)"}
I couldn’t find any solutions on the [elixir-nodejs 1](https://github.com/revelrylabs/elixir-nodejs) docs either.
It works well when I remove the NodeJS.Supervisor from application.ex.
Any guidance would be much appreciated!
assets/svelte/_build
contains artifacts from llive views that use the ~V sigil.
This directory seems to be created when I run (among other mix
commands) mix phx.server
. However, this directory is not getting created. I'm using live_svelte version 0.11.0.
This has happened to me before and it "Just Started Working." However, I seem to have broken the ~V sigil's magic permanently this time.
Hey, @woutdp
Is there an example how could I use Live Svelte to upload an image or a text file?
Thanks
I filed this issue previously but I have since disabled SSR because I've had so many problems with it. Today even the 'counter' example wasn't working at all with SSR enabled (I'm using NodeJS 20.2.0) -- but it inexplicably started working again. The following scenario is consistent, however:
mix phx.server
). Hopefully you have Postgres installed already.Result: Error in browser. The following is sent to the console:
[error] #PID<0.1282.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.802.0>, stream id 51) terminated
Server: localhost:4000 (http)
Request: GET /svelte/mermaid
** (exit) an exception was raised:
** (NodeJS.Error) got {:node_js_worker_exit, {{:badarg, [{:erlang, :port_command, [#Port<0.70>, "[[\"server/server\",\"ssrRenderComponent\"],[\"Mermaid\",{},{}]]\n"], [error_info: %{module: :erl_erts_errors}]}, {NodeJS.Worker, :handle_call, 3, [file: 'lib/nodejs/worker.ex', line: 102]}, {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 1113]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 1142]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 241]}]}, {GenServer, :call, [#PID<0.1217.0>, {{"server/server", "ssrRenderComponent"}, ["Mermaid", %{}, %{}], [binary: false, timeout: 30000]}, 30000]}}} while retrieving Exception.message/1 for %NodeJS.Error{message: {:node_js_worker_exit, {{:badarg, [{:erlang, :port_command, [#Port<0.70>, "[[\"server/server\",\"ssrRenderComponent\"],[\"Mermaid\",{},{}]]\n"], [error_info: %{module: :erl_erts_errors}]}, {NodeJS.Worker, :handle_call, 3, [file: 'lib/nodejs/worker.ex', line: 102]}, {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 1113]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 1142]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 241]}]}, {GenServer, :call, [#PID<0.1217.0>, {{"server/server", "ssrRenderComponent"}, ["Mermaid", %{}, %{}], [binary: false, timeout: 30000]}, 30000]}}}, stack: nil} (expected a string)
(nodejs 2.0.0) lib/nodejs/supervisor.ex:80: NodeJS.Supervisor.call!/3
(live_svelte 0.5.1) lib/ssr.ex:19: LiveSvelte.SSR.render/3
(live_svelte 0.5.1) lib/component.ex:56: LiveSvelte."render (overridable 1)"/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/tag_engine.ex:68: Phoenix.LiveView.TagEngine.component/3
(kantan 0.1.0) lib/kantan_web/controllers/page_html/mermaid.html.heex:1: anonymous fn/2 in KantanWeb.PageHTML.mermaid/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/engine.ex:137: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/engine.ex:153: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/3
(phoenix 1.7.2) lib/phoenix/controller.ex:1010: anonymous fn/5 in Phoenix.Controller.template_render_to_iodata/4
(telemetry 1.2.1) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix 1.7.2) lib/phoenix/controller.ex:976: Phoenix.Controller.render_and_send/4
(kantan 0.1.0) lib/kantan_web/controllers/page_controller.ex:1: KantanWeb.PageController.action/2
(kantan 0.1.0) lib/kantan_web/controllers/page_controller.ex:1: KantanWeb.PageController.phoenix_controller_pipeline/2
(phoenix 1.7.2) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
(kantan 0.1.0) lib/kantan_web/endpoint.ex:1: KantanWeb.Endpoint.plug_builder_call/2
(kantan 0.1.0) deps/plug/lib/plug/debugger.ex:136: KantanWeb.Endpoint."call (overridable 3)"/2
(kantan 0.1.0) lib/kantan_web/endpoint.ex:1: KantanWeb.Endpoint.call/2
(phoenix 1.7.2) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.10.0) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.10.0) /Users/dg/agle/202308/phoenix-ash-svelte-flowbite/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
Probably best to attach to window
It doesn't work.
For example Typescript doesn't work. I'm not sure why.
The documentation is getting a bit unwieldly, so let's move most things over to HexDocs and keep just the bare minimum in the README.md
I'm getting the following error when trying to SSR a component, the node version i'm running is v19.9.0
, any ideas of how I can dig into this further? Thanks for creating this :)
[error] #PID<0.18745.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.17637.0>, stream id 35) terminated
Server: localhost:4000 (http)
Request: GET /organizations
** (exit) an exception was raised:
** (NodeJS.Error) Cannot read properties of undefined (reading 'render')
TypeError: Cannot read properties of undefined (reading 'render')
at render (/Users/vento/Documents/projects/solosoftware/wut/priv/static/assets/server/server.js:450:20)
at callModuleFunction (/Users/vento/Documents/projects/solosoftware/wut/deps/nodejs/priv/server.js:32:23)
at getResponse (/Users/vento/Documents/projects/solosoftware/wut/deps/nodejs/priv/server.js:44:26)
at Interface.onLine (/Users/vento/Documents/projects/solosoftware/wut/deps/nodejs/priv/server.js:53:39)
at Interface.emit (node:events:513:28)
at Interface._onLine (node:readline:491:10)
at Interface._normalWrite (node:readline:665:12)
at Socket.ondata (node:readline:272:10)
at Socket.emit (node:events:513:28)
at addChunk (node:internal/streams/readable:315:12)
(nodejs 2.0.0) lib/nodejs/supervisor.ex:80: NodeJS.Supervisor.call!/3
(live_svelte 0.9.0) lib/ssr.ex:19: LiveSvelte.SSR.render/3
(live_svelte 0.9.0) lib/component.ex:73: LiveSvelte."svelte (overridable 1)"/1
(phoenix_live_view 0.19.4) lib/phoenix_live_view/tag_engine.ex:68: Phoenix.LiveView.TagEngine.component/3
(wut 0.1.0) lib/wut_web/components/layouts/app.html.heex:30: anonymous fn/2 in WutWeb.Layouts.app/1
(phoenix_live_view 0.19.4) lib/phoenix_live_view/diff.ex:386: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.19.4) lib/phoenix_live_view/diff.ex:136: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.19.4) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 0.19.4) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
(phoenix_live_view 0.19.4) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.7.7) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
(wut 0.1.0) lib/wut_web/endpoint.ex:1: WutWeb.Endpoint.plug_builder_call/2
(wut 0.1.0) deps/plug/lib/plug/debugger.ex:136: WutWeb.Endpoint."call (overridable 3)"/2
(wut 0.1.0) lib/wut_web/endpoint.ex:1: WutWeb.Endpoint.call/2
(phoenix 1.7.7) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.10.0) /Users/vento/Documents/projects/solosoftware/wut/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.10.0) /Users/vento/Documents/projects/solosoftware/wut/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.10.0) /Users/vento/Documents/projects/solosoftware/wut/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 5.0.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Can we pass props to dead views not SSR?
I love the idea of having a dedicated sigil for the templates but I just looked at the payload with the svelte template and it sends the whole props for the todo list.
Maybe removing the morphdom from the svelte templates may be a good idea and just consume the messages directly - I think live view native allows created a common library for different types of clients - https://native.live/
You may also have a look at
https://github.com/hansihe/live_data - https://www.youtube.com/watch?v=I4vVxtrow-E
https://github.com/Miserlou/live_json
I was trying to use data i stored in the svelte store on a new liveview it however came as blank and navigating back the data was logged on the console am still new to liveview and i;m geussing the two are independed i tried calling the client session storage or local storge but neigther works ...
I have been looking for ways to pass data within liveview minus saving in the database initially am handling a multi stage form how would you go through now that livesvelte is in picture..
sessionStorage is not defined
ReferenceError: sessionStorage is not defined
at /home/maxino/Value_8/Realest8/realest8_platform/priv/svelte/server.js:9897:3
at $$render (/home/maxino/Value_8/Realest8/realest8_platform/priv/svelte/server.js:9261:18)
at Object.render (/home/maxino/Value_8/Realest8/realest8_platform/priv/svelte/server.js:9269:20)
at render2 (/home/maxino/Value_8/Realest8/realest8_platform/priv/svelte/server.js:9938:22)
at callModuleFunction (/home/maxino/Value_8/Realest8/realest8_platform/deps/nodejs/priv/server.js:32:23)
at getResponse (/home/maxino/Value_8/Realest8/realest8_platform/deps/nodejs/priv/server.js:44:26)
at Interface.onLine (/home/maxino/Value_8/Realest8/realest8_platform/deps/nodejs/priv/server.js:53:39)
at Interface.emit (node:events:514:28)
at [_onLine] [as _onLine] (node:internal/readline/interface:422:12)
at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:616:22)
Currently SSR takes about 8ms each render in production, and about 20-40ms in dev, and this is being executed synchronously.
So let's say you have 10 components inside a LiveView, it'll end up taking 80ms!
When turning off SSR it's super fast, you can easily render a 100 components and not feel any delay.
The culprit is the call to NodeJS.
Doing the following things will probably do the trick.
Maybe Bun is the answer here, or a better way of calling Node.
Thank you for updating build.js to use svelte-preprocess
!
I ran mix live_svelte.setup
with live_svelte version 0.11.0. build.js
only works with esbuild version 0.16.x and earlier because the 'watch' implementation changed.
If you don't pin esbuild to version 0.16.x in package.json, build.js
should look something like this.
Right now if ssr is activated then it happens twice on mount - first for disconnected mount and then for connected - and also on each live navigation between different views. Except disconnected mount those renders are not needed and might actually slow down things. It is obvious that there is no point in rendering connected mount. As for live navigation - it seems like main gain of ssr is an ability to see a page before assets have loaded, but in live navigation we already have js and css loaded so there should be no advantage from ssr.
I currently set ssr={!connected?(@socket)}
on a svelte component in live view to avoid those renders. I think it worth mentioning somewhere.
see screenshot. only appears broken on firefox. Probably this issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1368063. I also noticed the same issue on your blog post.
Now that I have more experience with Phoenix components, it seems that <.foo> in heex templates should be a noun - a component name - instead of a verb. Therefore I am suggesting deprecating LiveSvelte.render in favor of LiveSvelte.svelte so that it is more in the spirit of .button, .link, etc.
There are many benefits of Deno over NodeJS (e.g., it seems like it might be lighter weight since some of the legacy libraries have been removed), but there may be compatibility issues.
deno_ex might be the best vehicle to get there.
I'm using ~V for a dead view that doesn't use assigns. The compiler complains that assigns is defined but not used. So I rename it to _assigns. Then I get the error:
** (RuntimeError) ~H requires a variable named "assigns" to exist and be set to a map
(phoenix_live_view 0.18.18) expanding macro: Phoenix.Component.sigil_H/2
lib/kantan_web/controllers/svelte/navigation.ex:6: KantanWeb.Svelte.Navigation.render/2
(live_svelte 0.5.1) expanding macro: LiveSvelte.sigil_V/2
lib/kantan_web/controllers/svelte/navigation.ex:6: KantanWeb.Svelte.Navigation.render/2
I think it's a bit cumbersome atm.
Ideally I can just call a function.
Also the id is something I'd like to avoid to provide if it's possible.
Would be nice to keep slots, if we can make that work
Am I using mix assets.build
and mix assets.deploy
correctly? They both output the same error message.
mix assets.build
Rebuilding...
Done in 1772ms.
✘ [ERROR] Could not resolve "../svelte/**/*.svelte"
js/app.js:25:28:
25 │ import * as Components from "../svelte/**/*.svelte"
See here for a fix.
Unfortunately, it doesn't work in Docker so this is on hold.
Instead of the JS files being copied during the setup, I'd prefer to just import from live_svelte wherever possible
Hi there
This is great - thanks for this - I was actually going to create something similar but no need now - what you've done is great.
One thing though - I think it would be better if the Svelte components had no idea they were in a LiveView app - currently you're using
<button class="plus" phx-click="increment">
+1
</button>
from inside the Svelte component, i.e. the Svelte component uses phx-click
attribute, which seems wrong to me.
I think the better solution would be for the component to dispatch an event, e.g.
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('increment');
}
</script>
<button class="plus" on:click={handleClick}>
+1
</button>
or just using event forwarding,
and for your LiveSvelte.render
component to also take an events
argument or something, which in turn send a message to LiveView, e.g.
<LiveSvelte.render name="SimpleCounter" props={%{number: @number}} events={%{increment: "increment"}} />
One slightly tricky thing is that ideally you'd be able to map the payload between what gets emitted ----> what you send to the LiveView handler - there would be various approaches for this - I guess sending event.detail
wouldn't be a bad default.
Anyway, this way your Svelte components would have no knowledge of LiveView/Phoenix and you could more easily import third-party components, etc.
Just an idea - what do you think?
I'd love to be able to just do the following and render Svelte directly from a LiveComponent:
defmodule App.LiveComponent do
use LiveSvelte.LiveComponent
attr(:foo, :string, default: "")
attr(:bar, :string)
def render(assigns) do
~v"""
<script>
export let foo = "foo";
export let bar = "bar"
export let pushEvent;
</script>
<h1>It's working!</h1>
"""
end
end
The live_svelte
packages feel so awesome. Enabling a discussion panel would help people if they wanna discuss stuff about getting started or doing a specific tweak, something that is not an issue.
Getting this dependency error when trying to update to LiveView 0.19:
Because your app depends on live_svelte ~> 0.7.0 which depends on phoenix_live_view ~> 0.18.3, phoenix_live_view ~> 0.18.3 is required.
This might have to do with the destroyed being called
I'm trying to use https://unpic.pics/img/svelte/ but getting an error:
<script lang="ts">
import { Image } from "@unpic/svelte";
</script>
<nav>
<div class="flex flex-col justify-center sm:items-start">
<a
href="/"
dir="auto"
class="break-words whitespace-pre-wrap text-base inline-flex w-max justify-center text-gray-700 no-underline hover:text-gray-600 hover:no-underline sm:justify-start select-none"
><div class="flex flex-col relative w-12">
<Image
alt="Website name"
priority={false}
width={40}
height={40}
layout="constrained"
decoding="async"
class="bg-transparent object-contain object-center"
src="/images/app/logo-t-s.webp"
/>
</div>
</a>
</div>
</nav>
For typescript support, I've simply added typescript as devDependencies
Not a super big issue but when following the installation steps of the readme, no package.json gets generated.
The easy fix was to copy and paste the one from the example_project/assets and correcting the paths.
{
"devDependencies": {
"@types/lodash": "^4.14.192",
"dynamic-marquee": "^2.6.2",
"esbuild": "^0.16.17",
"esbuild-plugin-import-glob": "^0.1.1",
"esbuild-svelte": "^0.7.4",
"lodash": "^4.17.21",
"stylus": "^0.55.0",
"svelte": "^4.1.2",
"svelte-preprocess": "^5.0.4",
"typescript": "^4.9.5"
},
"dependencies": {
"live_json": "file:../deps/live_json",
"live_svelte": "file:../deps/live_svelte",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view",
"svelvet": "^8.1.0"
}
}
Thought that some other "'weirdos" working on Windows like me might find it useful.
Current:
<LiveSvelte.render name="SomeComponent" props={%{number: @number}} />
New:
<LiveSvelte.render name="SomeComponent" number={@number} />
We can probably use @rest
for this. We should still allow props
as that can be a way to add keywords that are taken such as the class
and name
keyword (or even the props
keyword, which is very meta)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.