diff --git a/README.md b/README.md index 1d380e4..6b552d0 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,19 @@ Render Svelte directly into Phoenix LiveView with E2E reactivity. ## Resources -- [HexDocs](https://hexdocs.pm/live_svelte) -- [HexPackage](https://hex.pm/packages/live_svelte) -- [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view) -- [Blog Post](https://wout.space/notes/live-svelte) +- [HexDocs](https://hexdocs.pm/live_svelte) +- [HexPackage](https://hex.pm/packages/live_svelte) +- [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view) +- [Blog Post](https://wout.space/notes/live-svelte) ## Features -- ⚡ **End-To-End Reactivity** with LiveView -- 🔋 **Server-Side Rendered** (SSR) Svelte -- ⭐ **Svelte Preprocessing** Support with [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess) -- 🦄 **Tailwind** Support -- 💀 **Dead View** Support -- 🦥 **Slot Interoperability** *(Experimental)* +- ⚡ **End-To-End Reactivity** with LiveView +- 🔋 **Server-Side Rendered** (SSR) Svelte +- ⭐ **Svelte Preprocessing** Support with [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess) +- 🦄 **Tailwind** Support +- 💀 **Dead View** Support +- 🦥 **Slot Interoperability** _(Experimental)_ ## Demo @@ -58,11 +58,11 @@ LiveSvelte builds on top of Phoenix LiveView to allow for easy client side state ### Reasons why you'd use LiveSvelte -- You have (complex) local state -- You want to use an NPM package -- You want to take advantage of Svelte's animations -- You want scoped CSS -- You like Svelte and its DX :) +- You have (complex) local state +- You want to use an NPM package +- You want to take advantage of Svelte's animations +- You want scoped CSS +- You like Svelte and its DX :) ## Requirements @@ -99,6 +99,7 @@ end ``` 3. Run the following in your terminal + ```bash mix deps.get mix live_svelte.setup @@ -127,10 +128,10 @@ In addition we commented out some things such as the `esbuild` watcher configure Svelte components need to go into the `assets/svelte` directory -- Set the `name` of the Svelte component. -- _Optional:_ Provide the `props` you want to use that should be reactive as a map to the props field -- _Optional:_ Provide `class` to set the class attribute on the root svelte element -- _Optional:_ Set `ssr` to false to disable server-side rendering +- Set the `name` of the Svelte component. +- _Optional:_ Provide the `props` you want to use that should be reactive as a map to the props field +- _Optional:_ Provide `class` to set the class attribute on the root svelte element +- _Optional:_ Set `ssr` to false to disable server-side rendering e.g. If your component is named `assets/svelte/Example.svelte`: @@ -164,7 +165,7 @@ An example project can be found in the `/example_project` directory. // This pushes the event over the websocket // The last parameter is optional. It's a callback for when the event is finished. // You could for example set a loading state until the event is finished if it takes a longer time. - pushEvent('set_number', { number: number + 1 }, () => {}) + pushEvent("set_number", {number: number + 1}, () => {}) // Note that we actually never set the number in the frontend! // We ONLY push the event to the server. @@ -173,7 +174,7 @@ An example project can be found in the `/example_project` directory. } function decrease() { - pushEvent('set_number', { number: number - 1 }, () => {}) + pushEvent("set_number", {number: number - 1}, () => {}) } @@ -246,6 +247,7 @@ LiveView allows for a bunch of interoperability which you can read more about he To use the preprocessor, install the desired preprocessor. e.g. Typescript + ``` cd assets && npm install --save-dev typescript ``` @@ -293,9 +295,9 @@ mix assets.build ### Releasing -- Update the version in `README.md` -- Update the version in `package.json` -- Update the version in `mix.exs` +- Update the version in `README.md` +- Update the version in `package.json` +- Update the version in `mix.exs` Run: @@ -304,5 +306,6 @@ mix hex.publish ``` ## Credits -- [Ryan Cooke](https://dev.to/debussyman) - [E2E Reactivity using Svelte with Phoenix LiveView](https://dev.to/debussyman/e2e-reactivity-using-svelte-with-phoenix-liveview-38mf) -- [Svonix](https://github.com/nikokozak/svonix) + +- [Ryan Cooke](https://dev.to/debussyman) - [E2E Reactivity using Svelte with Phoenix LiveView](https://dev.to/debussyman/e2e-reactivity-using-svelte-with-phoenix-liveview-38mf) +- [Svonix](https://github.com/nikokozak/svonix) diff --git a/assets/copy/build.js b/assets/copy/build.js index cf2599f..945ca04 100644 --- a/assets/copy/build.js +++ b/assets/copy/build.js @@ -1,71 +1,71 @@ -const esbuild = require('esbuild') -const sveltePlugin = require('esbuild-svelte') -const importGlobPlugin = require('esbuild-plugin-import-glob').default -const sveltePreprocess = require('svelte-preprocess') +const esbuild = require("esbuild") +const sveltePlugin = require("esbuild-svelte") +const importGlobPlugin = require("esbuild-plugin-import-glob").default +const sveltePreprocess = require("svelte-preprocess") const args = process.argv.slice(2) -const watch = args.includes('--watch') -const deploy = args.includes('--deploy') +const watch = args.includes("--watch") +const deploy = args.includes("--deploy") let optsClient = { - entryPoints: ['js/app.js'], - mainFields: ['svelte', 'browser', 'module', 'main'], + entryPoints: ["js/app.js"], + mainFields: ["svelte", "browser", "module", "main"], bundle: true, minify: false, - target: 'es2017', - outdir: '../priv/static/assets', - logLevel: 'info', + target: "es2017", + outdir: "../priv/static/assets", + logLevel: "info", plugins: [ importGlobPlugin(), sveltePlugin({ preprocess: sveltePreprocess(), compilerOptions: {hydratable: true, css: true}, - }) - ] + }), + ], } let optsServer = { - entryPoints: ['js/server.js'], - mainFields: ['svelte', 'module', 'main'], - platform: 'node', - format: 'cjs', + entryPoints: ["js/server.js"], + mainFields: ["svelte", "module", "main"], + platform: "node", + format: "cjs", bundle: true, minify: false, target: "node19.6.1", - outdir: '../priv/static/assets/server', - logLevel: 'info', + outdir: "../priv/static/assets/server", + logLevel: "info", plugins: [ importGlobPlugin(), sveltePlugin({ preprocess: sveltePreprocess(), - compilerOptions: {hydratable: true, generate: 'ssr', format: 'cjs'}, - }) - ] + compilerOptions: {hydratable: true, generate: "ssr", format: "cjs"}, + }), + ], } if (watch) { optsClient = { ...optsClient, watch, - sourcemap: 'inline' + sourcemap: "inline", } optsServer = { ...optsServer, watch, - sourcemap: 'inline' + sourcemap: "inline", } } if (deploy) { optsClient = { ...optsClient, - minify: true + minify: true, } optsServer = { ...optsServer, - minify: true + minify: true, } } @@ -74,7 +74,7 @@ const server = esbuild.build(optsServer) if (watch) { client.then(_result => { - process.stdin.on('close', () => { + process.stdin.on("close", () => { process.exit(0) }) @@ -82,7 +82,7 @@ if (watch) { }) server.then(_result => { - process.stdin.on('close', () => { + process.stdin.on("close", () => { process.exit(0) }) diff --git a/assets/copy/js/app.js b/assets/copy/js/app.js index 492b0c3..da762f0 100644 --- a/assets/copy/js/app.js +++ b/assets/copy/js/app.js @@ -21,8 +21,8 @@ import "phoenix_html" import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" -import {getHooks} from 'live_svelte' -import * as SvelteComponents from '../svelte/**/*' +import {getHooks} from "live_svelte" +import * as SvelteComponents from "../svelte/**/*" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, {hooks: getHooks(SvelteComponents), params: {_csrf_token: csrfToken}}) @@ -40,4 +40,3 @@ liveSocket.connect() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket - diff --git a/assets/copy/js/server.js b/assets/copy/js/server.js index a11fd25..1541676 100644 --- a/assets/copy/js/server.js +++ b/assets/copy/js/server.js @@ -1,4 +1,4 @@ -import * as Components from '../svelte/**/*' -import {exportSvelteComponents} from 'live_svelte' +import * as Components from "../svelte/**/*" +import {exportSvelteComponents} from "live_svelte" module.exports = exportSvelteComponents(Components) diff --git a/assets/js/live_svelte/hooks.js b/assets/js/live_svelte/hooks.js index 10b5e71..79ceb99 100644 --- a/assets/js/live_svelte/hooks.js +++ b/assets/js/live_svelte/hooks.js @@ -1,8 +1,8 @@ -import {detach, insert, noop} from 'svelte/internal' -import {exportSvelteComponents} from './utils' +import {detach, insert, noop} from "svelte/internal" +import {exportSvelteComponents} from "./utils" function base64ToElement(base64) { - let template = document.createElement('div') + let template = document.createElement("div") template.innerHTML = atob(base64).trim() return template } @@ -18,7 +18,7 @@ function createSlots(slots, ref) { return () => { return { getElement() { - return base64ToElement(dataAttributeToJson('data-slots', ref.el)[slotName]) + return base64ToElement(dataAttributeToJson("data-slots", ref.el)[slotName]) }, update() { const element = this.getElement() @@ -53,10 +53,10 @@ function createSlots(slots, ref) { function getProps(ref) { return { - ...dataAttributeToJson('data-props', ref.el), + ...dataAttributeToJson("data-props", ref.el), pushEvent: (event, data, callback) => ref.pushEvent(event, data, callback), - $$slots: createSlots(dataAttributeToJson('data-slots', ref.el), ref), - $$scope: {} + $$slots: createSlots(dataAttributeToJson("data-slots", ref.el), ref), + $$scope: {}, } } @@ -71,9 +71,9 @@ export function getHooks(Components) { const SvelteHook = { mounted() { - const componentName = this.el.getAttribute('data-name') + const componentName = this.el.getAttribute("data-name") if (!componentName) { - throw new Error('Component name must be provided') + throw new Error("Component name must be provided") } const Component = components[componentName] @@ -84,7 +84,7 @@ export function getHooks(Components) { this._instance = new Component({ target: this.el, props: getProps(this), - hydrate: true + hydrate: true, }) }, @@ -104,10 +104,10 @@ export function getHooks(Components) { // If we do a page navigation, this would remove the component in the DOM, // and then it would to the transition, causing a flicker of unrendered content // Since we're doing a page transition anyway, the component will be remove automatically - } + }, } return { - SvelteHook + SvelteHook, } } diff --git a/assets/js/live_svelte/index.js b/assets/js/live_svelte/index.js index f533829..cb99c98 100644 --- a/assets/js/live_svelte/index.js +++ b/assets/js/live_svelte/index.js @@ -1,3 +1,3 @@ export {getRender} from "./render" export {getHooks} from "./hooks" -export {exportSvelteComponents} from './utils' +export {exportSvelteComponents} from "./utils" diff --git a/assets/js/live_svelte/render.js b/assets/js/live_svelte/render.js index 8c8dcf1..150f5e3 100644 --- a/assets/js/live_svelte/render.js +++ b/assets/js/live_svelte/render.js @@ -5,17 +5,14 @@ export function getRender(componentPath) { function render(name, props = {}, slots = null) { // remove from cache in non-production environments // so that we can see changes - if ( - process.env.NODE_ENV !== 'production' && - require.resolve(componentPath) in require.cache - ) { + if (process.env.NODE_ENV !== "production" && require.resolve(componentPath) in require.cache) { delete require.cache[require.resolve(componentPath)] } const component = require(componentPath)[name].default const $$slots = Object.fromEntries(Object.entries(slots).map(([k, v]) => [k, () => v])) || {} - return component.render(props, { $$slots, context: new Map() }) + return component.render(props, {$$slots, context: new Map()}) } return render diff --git a/assets/js/live_svelte/utils.js b/assets/js/live_svelte/utils.js index b1d4806..ad1bab1 100644 --- a/assets/js/live_svelte/utils.js +++ b/assets/js/live_svelte/utils.js @@ -1,9 +1,7 @@ export function exportSvelteComponents(components) { - let { default: modules, filenames } = components + let {default: modules, filenames} = components - filenames = filenames - .map(name => name.replace('../svelte/', '')) - .map(name => name.replace('.svelte', '')) + filenames = filenames.map(name => name.replace("../svelte/", "")).map(name => name.replace(".svelte", "")) return Object.assign({}, ...modules.map((m, index) => ({[filenames[index]]: m.default}))) } diff --git a/example_project/README.md b/example_project/README.md index e528c20..b50b47b 100644 --- a/example_project/README.md +++ b/example_project/README.md @@ -2,8 +2,8 @@ To start your Phoenix server: - * Run `mix setup` to install and setup dependencies - * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` +- Run `mix setup` to install and setup dependencies +- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. @@ -11,8 +11,8 @@ Ready to run in production? Please [check our deployment guides](https://hexdocs ## Learn more - * Official website: https://www.phoenixframework.org/ - * Guides: https://hexdocs.pm/phoenix/overview.html - * Docs: https://hexdocs.pm/phoenix - * Forum: https://elixirforum.com/c/phoenix-forum - * Source: https://github.com/phoenixframework/phoenix +- Official website: https://www.phoenixframework.org/ +- Guides: https://hexdocs.pm/phoenix/overview.html +- Docs: https://hexdocs.pm/phoenix +- Forum: https://elixirforum.com/c/phoenix-forum +- Source: https://github.com/phoenixframework/phoenix diff --git a/example_project/assets/build.js b/example_project/assets/build.js index cf2599f..945ca04 100644 --- a/example_project/assets/build.js +++ b/example_project/assets/build.js @@ -1,71 +1,71 @@ -const esbuild = require('esbuild') -const sveltePlugin = require('esbuild-svelte') -const importGlobPlugin = require('esbuild-plugin-import-glob').default -const sveltePreprocess = require('svelte-preprocess') +const esbuild = require("esbuild") +const sveltePlugin = require("esbuild-svelte") +const importGlobPlugin = require("esbuild-plugin-import-glob").default +const sveltePreprocess = require("svelte-preprocess") const args = process.argv.slice(2) -const watch = args.includes('--watch') -const deploy = args.includes('--deploy') +const watch = args.includes("--watch") +const deploy = args.includes("--deploy") let optsClient = { - entryPoints: ['js/app.js'], - mainFields: ['svelte', 'browser', 'module', 'main'], + entryPoints: ["js/app.js"], + mainFields: ["svelte", "browser", "module", "main"], bundle: true, minify: false, - target: 'es2017', - outdir: '../priv/static/assets', - logLevel: 'info', + target: "es2017", + outdir: "../priv/static/assets", + logLevel: "info", plugins: [ importGlobPlugin(), sveltePlugin({ preprocess: sveltePreprocess(), compilerOptions: {hydratable: true, css: true}, - }) - ] + }), + ], } let optsServer = { - entryPoints: ['js/server.js'], - mainFields: ['svelte', 'module', 'main'], - platform: 'node', - format: 'cjs', + entryPoints: ["js/server.js"], + mainFields: ["svelte", "module", "main"], + platform: "node", + format: "cjs", bundle: true, minify: false, target: "node19.6.1", - outdir: '../priv/static/assets/server', - logLevel: 'info', + outdir: "../priv/static/assets/server", + logLevel: "info", plugins: [ importGlobPlugin(), sveltePlugin({ preprocess: sveltePreprocess(), - compilerOptions: {hydratable: true, generate: 'ssr', format: 'cjs'}, - }) - ] + compilerOptions: {hydratable: true, generate: "ssr", format: "cjs"}, + }), + ], } if (watch) { optsClient = { ...optsClient, watch, - sourcemap: 'inline' + sourcemap: "inline", } optsServer = { ...optsServer, watch, - sourcemap: 'inline' + sourcemap: "inline", } } if (deploy) { optsClient = { ...optsClient, - minify: true + minify: true, } optsServer = { ...optsServer, - minify: true + minify: true, } } @@ -74,7 +74,7 @@ const server = esbuild.build(optsServer) if (watch) { client.then(_result => { - process.stdin.on('close', () => { + process.stdin.on("close", () => { process.exit(0) }) @@ -82,7 +82,7 @@ if (watch) { }) server.then(_result => { - process.stdin.on('close', () => { + process.stdin.on("close", () => { process.exit(0) }) diff --git a/example_project/assets/js/app.js b/example_project/assets/js/app.js index 492b0c3..da762f0 100644 --- a/example_project/assets/js/app.js +++ b/example_project/assets/js/app.js @@ -21,8 +21,8 @@ import "phoenix_html" import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" -import {getHooks} from 'live_svelte' -import * as SvelteComponents from '../svelte/**/*' +import {getHooks} from "live_svelte" +import * as SvelteComponents from "../svelte/**/*" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, {hooks: getHooks(SvelteComponents), params: {_csrf_token: csrfToken}}) @@ -40,4 +40,3 @@ liveSocket.connect() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket - diff --git a/example_project/assets/js/server.js b/example_project/assets/js/server.js index a11fd25..1541676 100644 --- a/example_project/assets/js/server.js +++ b/example_project/assets/js/server.js @@ -1,4 +1,4 @@ -import * as Components from '../svelte/**/*' -import {exportSvelteComponents} from 'live_svelte' +import * as Components from "../svelte/**/*" +import {exportSvelteComponents} from "live_svelte" module.exports = exportSvelteComponents(Components) diff --git a/example_project/assets/svelte/BreakingNews.svelte b/example_project/assets/svelte/BreakingNews.svelte index 6b0f064..0856317 100644 --- a/example_project/assets/svelte/BreakingNews.svelte +++ b/example_project/assets/svelte/BreakingNews.svelte @@ -1,41 +1,41 @@ @@ -62,12 +62,12 @@
- +
- - + +
@@ -76,19 +76,16 @@
{item.body} -
+
{/each} - -
+ +
BREAKING NEWS
-
-
+/> diff --git a/example_project/assets/svelte/Chat.svelte b/example_project/assets/svelte/Chat.svelte index 3edf6f1..9426ccc 100644 --- a/example_project/assets/svelte/Chat.svelte +++ b/example_project/assets/svelte/Chat.svelte @@ -1,59 +1,59 @@
- + > + {message.name} + {message.body} + + {/each} + -
-
- - - - {charCount} - -
- -
+
+
+ + + + {charCount} + +
+ +
diff --git a/example_project/assets/svelte/LogList.svelte b/example_project/assets/svelte/LogList.svelte index 8c869c6..3f000c8 100644 --- a/example_project/assets/svelte/LogList.svelte +++ b/example_project/assets/svelte/LogList.svelte @@ -1,5 +1,5 @@
- - + +
diff --git a/example_project/assets/svelte/SimpleCounter.svelte b/example_project/assets/svelte/SimpleCounter.svelte index 525f5a4..81488a2 100644 --- a/example_project/assets/svelte/SimpleCounter.svelte +++ b/example_project/assets/svelte/SimpleCounter.svelte @@ -1,24 +1,24 @@ - Simple Counter + Simple Counter
-
-
- Server - {number} - -
+
+
+ Server + {number} + +
-
- Client - {other} - +
+ Client + {other} + +
-
diff --git a/example_project/assets/svelte/Svelte2.svelte b/example_project/assets/svelte/Svelte2.svelte index b79e7a0..906a0fe 100644 --- a/example_project/assets/svelte/Svelte2.svelte +++ b/example_project/assets/svelte/Svelte2.svelte @@ -7,9 +7,9 @@
- + {number} - +
- -
+ +
BREAKING NEWS
-
-
+/> diff --git a/examples/log_list/LogList.svelte b/examples/log_list/LogList.svelte index b3bfc0d..455f795 100644 --- a/examples/log_list/LogList.svelte +++ b/examples/log_list/LogList.svelte @@ -1,5 +1,5 @@
- - + +
@@ -45,4 +46,3 @@ {/each}
{/if} - diff --git a/examples/simple/Simple.svelte b/examples/simple/Simple.svelte index 65622c3..7166abc 100644 --- a/examples/simple/Simple.svelte +++ b/examples/simple/Simple.svelte @@ -3,11 +3,11 @@ export let pushEvent function increase() { - pushEvent('set_number', { number: number + 1 }) + pushEvent("set_number", {number: number + 1}) } function decrease() { - pushEvent('set_number', { number: number - 1 }) + pushEvent("set_number", {number: number - 1}) } diff --git a/examples/simple_chat/Chat.svelte b/examples/simple_chat/Chat.svelte index fa4b26a..b878154 100644 --- a/examples/simple_chat/Chat.svelte +++ b/examples/simple_chat/Chat.svelte @@ -18,14 +18,15 @@
- +
-
\ No newline at end of file +
diff --git a/examples/slots/Slots.svelte b/examples/slots/Slots.svelte index 2ff6f92..3c28909 100644 --- a/examples/slots/Slots.svelte +++ b/examples/slots/Slots.svelte @@ -1,19 +1,19 @@ - + {#if show}
-

Inner Block:

+

Inner Block:

Default
-

Updating named slot:

+

Updating named slot:

Another default
diff --git a/examples/store/StoreExample1.svelte b/examples/store/StoreExample1.svelte index e87cad8..902abaf 100644 --- a/examples/store/StoreExample1.svelte +++ b/examples/store/StoreExample1.svelte @@ -1,7 +1,8 @@ \ No newline at end of file + + {$store} + diff --git a/examples/store/StoreExample2.svelte b/examples/store/StoreExample2.svelte index 7a67e7a..902abaf 100644 --- a/examples/store/StoreExample2.svelte +++ b/examples/store/StoreExample2.svelte @@ -1,7 +1,8 @@ diff --git a/examples/store/store.js b/examples/store/store.js index eb119aa..5977631 100644 --- a/examples/store/store.js +++ b/examples/store/store.js @@ -1,13 +1,13 @@ -import {writable} from 'svelte/store' +import {writable} from "svelte/store" function createStore() { return writable(true) } function getStore() { - if (typeof window === 'undefined') return createStore() + if (typeof window === "undefined") return createStore() window.store = window.store || createStore() return window.store } -export default store = getStore() \ No newline at end of file +export default store = getStore()