Svelte 5 support

- LiveJson stopped working
- Experimental slots stopped working
This commit is contained in:
Wout De Puysseleir 2024-11-11 11:40:13 -08:00
parent 2710cd7157
commit 0d754ff169
16 changed files with 1141 additions and 1106 deletions

View file

@ -27,7 +27,7 @@ Svelte inside Phoenix LiveView with seamless end-to-end reactivity
- 🦄 **Tailwind** Support
- 💀 **Dead View** Support
- 🤏 **live_json** Support
- 🦥 **Slot Interoperability** _(Experimental)_
- 🦥 **Slot Interoperability**
## Resources

75
assets/build.js Normal file
View file

@ -0,0 +1,75 @@
const esbuild = require("esbuild")
const sveltePlugin = require("esbuild-svelte")
const args = process.argv.slice(2)
const watch = args.includes("--watch")
const deploy = args.includes("--deploy")
let moduleOpts = {
entryPoints: ["js/live_svelte/index.js"],
bundle: true,
format: "esm",
outfile: "../priv/static/live_svelte.esm.js",
external: ["svelte"],
logLevel: "info",
sourcemap: true,
plugins: [sveltePlugin({})],
}
let mainOpts = {
entryPoints: ["js/live_svelte/index.js"],
bundle: true,
conditions: ["svelte", "browser"],
format: "cjs",
outfile: "../priv/static/live_svelte.cjs.js",
logLevel: "info",
external: ["svelte"],
sourcemap: true,
plugins: [sveltePlugin({})],
}
let cdnOpts = {
entryPoints: ["js/live_svelte/index.js"],
bundle: true,
target: "es2016",
format: "iife",
globalName: "LiveSvelte",
outfile: "../priv/static/live_svelte.js",
logLevel: "info",
plugins: [sveltePlugin({})],
}
let cdnMinOpts = {
entryPoints: ["js/live_svelte/index.js"],
bundle: true,
minify: true,
target: "es2016",
format: "iife",
globalName: "LiveSvelte",
outfile: "../priv/static/live_svelte.min.js",
logLevel: "info",
plugins: [sveltePlugin({})],
}
if (watch) {
esbuild
.context(moduleOpts)
.then(ctx => ctx.watch())
.catch(_error => process.exit(1))
esbuild
.context(mainOpts)
.then(ctx => ctx.watch())
.catch(_error => process.exit(1))
esbuild
.context(cdnOpts)
.then(ctx => ctx.watch())
.catch(_error => process.exit(1))
esbuild
.context(cdnMinOpts)
.then(ctx => ctx.watch())
.catch(_error => process.exit(1))
} else {
esbuild.build(moduleOpts)
esbuild.build(mainOpts)
esbuild.build(cdnOpts)
esbuild.build(cdnMinOpts)
}

View file

@ -7,12 +7,20 @@ const args = process.argv.slice(2)
const watch = args.includes("--watch")
const deploy = args.includes("--deploy")
let clientConditions = ["svelte", "browser"]
let serverConditions = ["svelte"]
if (!deploy) {
clientConditions.push("development")
serverConditions.push("development")
}
let optsClient = {
entryPoints: ["js/app.js"],
bundle: true,
minify: deploy,
target: "es2017",
conditions: ["svelte", "browser"],
conditions: clientConditions,
alias: {svelte: "svelte"},
outdir: "../priv/static/assets",
logLevel: "info",
sourcemap: watch ? "inline" : false,
@ -21,7 +29,7 @@ let optsClient = {
importGlobPlugin(),
sveltePlugin({
preprocess: sveltePreprocess(),
compilerOptions: {dev: !deploy, hydratable: true, css: "injected"},
compilerOptions: {dev: !deploy, css: "injected", generate: "client"},
}),
],
}
@ -32,7 +40,8 @@ let optsServer = {
bundle: true,
minify: false,
target: "node19.6.1",
conditions: ["svelte"],
conditions: serverConditions,
alias: {svelte: "svelte"},
outdir: "../priv/svelte",
logLevel: "info",
sourcemap: watch ? "inline" : false,
@ -41,7 +50,7 @@ let optsServer = {
importGlobPlugin(),
sveltePlugin({
preprocess: sveltePreprocess(),
compilerOptions: {dev: !deploy, hydratable: true, generate: "ssr"},
compilerOptions: {dev: !deploy, css: "injected", generate: "server"},
}),
],
}

View file

@ -1,134 +0,0 @@
import {normalizeComponents} from "./utils"
function getAttributeJson(ref, attributeName) {
const data = ref.el.getAttribute(attributeName)
return data ? JSON.parse(data) : {}
}
function detach(node) {
node.parentNode?.removeChild(node)
}
function insert(target, node, anchor) {
target.insertBefore(node, anchor || null)
}
function noop() {}
function getSlots(ref) {
const slots = {}
for (const slotName in getAttributeJson(ref, "data-slots")) {
const slot = () => {
return {
getElement() {
const base64 = getAttributeJson(ref, "data-slots")[slotName]
const element = document.createElement("div")
element.innerHTML = atob(base64).trim()
return element
},
update() {
detach(this.savedElement)
this.savedElement = this.getElement()
insert(this.savedTarget, this.savedElement, this.savedAnchor)
},
c: noop,
m(target, anchor) {
this.savedTarget = target
this.savedAnchor = anchor
this.savedElement = this.getElement()
insert(this.savedTarget, this.savedElement, this.savedAnchor)
},
d(detaching) {
if (detaching) detach(this.savedElement)
},
l: noop,
}
}
slots[slotName] = [slot]
}
return slots
}
function getLiveJsonProps(ref) {
const json = getAttributeJson(ref, "data-live-json")
// On SSR, data-live-json is the full object we want
// After SSR, data-live-json is an array of keys, and we'll get the data from the window
if (!Array.isArray(json)) return json
const liveJsonData = {}
for (const liveJsonVariable of json) {
const data = window[liveJsonVariable]
if (data) liveJsonData[liveJsonVariable] = data
}
return liveJsonData
}
function getProps(ref) {
return {
...getAttributeJson(ref, "data-props"),
...getLiveJsonProps(ref),
live: ref,
$$slots: getSlots(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)
}
export function getHooks(components) {
components = normalizeComponents(components)
const SvelteHook = {
mounted() {
const componentName = this.el.getAttribute("data-name")
if (!componentName) {
throw new Error("Component name must be provided")
}
const Component = components[componentName]
if (!Component) {
throw new Error(`Unable to find ${componentName} component.`)
}
for (const liveJsonElement of Object.keys(getAttributeJson(this, "data-live-json"))) {
window.addEventListener(`${liveJsonElement}_initialized`, event => this._instance.$set(getProps(this)), false)
window.addEventListener(`${liveJsonElement}_patched`, event => this._instance.$set(getProps(this)), false)
}
this._instance = new Component({
target: this.el,
props: getProps(this),
hydrate: this.el.hasAttribute("data-ssr"),
})
},
updated() {
// Set the props
this._instance.$set(getProps(this))
// Set the slots
const slotCtx = findSlotCtx(this._instance)
for (const key in slotCtx) {
slotCtx[key][0]().update()
}
},
destroyed() {
if (this._instance) {
window.addEventListener("phx:page-loading-stop", () => this._instance.$destroy(), {once: true})
}
},
}
return {
SvelteHook,
}
}

View file

@ -0,0 +1,105 @@
import {normalizeComponents} from "./utils"
import {mount, hydrate, unmount, createRawSnippet} from "svelte"
function getAttributeJson(ref, attributeName) {
const data = ref.el.getAttribute(attributeName)
return data ? JSON.parse(data) : {}
}
function getSlots(ref) {
let snippets = {}
for (const slotName in getAttributeJson(ref, "data-slots")) {
const base64 = getAttributeJson(ref, "data-slots")[slotName]
const element = document.createElement("div")
element.innerHTML = atob(base64).trim()
const snippet = createRawSnippet(name => {
return {
render: () => element.outerHTML,
}
})
if (slotName === "default") snippets["children"] = snippet
else snippets[slotName] = snippet
}
return snippets
}
function getLiveJsonProps(ref) {
const json = getAttributeJson(ref, "data-live-json")
// On SSR, data-live-json is the full object we want
// After SSR, data-live-json is an array of keys, and we'll get the data from the window
if (!Array.isArray(json)) return json
const liveJsonData = {}
for (const liveJsonVariable of json) {
const data = window[liveJsonVariable]
if (data) liveJsonData[liveJsonVariable] = data
}
return liveJsonData
}
function getProps(ref) {
return {
...getAttributeJson(ref, "data-props"),
...getLiveJsonProps(ref),
...getSlots(ref),
live: ref,
}
}
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)
}
function update_state(ref) {
const newProps = getProps(ref)
for (const key in newProps) {
ref._instance.state[key] = newProps[key]
}
}
export function getHooks(components) {
components = normalizeComponents(components)
const SvelteHook = {
mounted() {
let state = $state(getProps(this))
const componentName = this.el.getAttribute("data-name")
if (!componentName) throw new Error("Component name must be provided")
const Component = components[componentName]
if (!Component) throw new Error(`Unable to find ${componentName} component.`)
for (const liveJsonElement of Object.keys(getAttributeJson(this, "data-live-json"))) {
window.addEventListener(`${liveJsonElement}_initialized`, _event => update_state(this), false)
window.addEventListener(`${liveJsonElement}_patched`, _event => update_state(this), false)
}
const hydrateOrMount = this.el.hasAttribute("data-ssr") ? hydrate : mount
this._instance = hydrateOrMount(Component, {
target: this.el,
props: state,
})
this._instance.state = state
},
updated() {
update_state(this)
},
destroyed() {
if (this._instance) window.addEventListener("phx:page-loading-stop", () => unmount(this._instance), {once: true})
},
}
return {
SvelteHook,
}
}

View file

@ -1,2 +1,2 @@
export {getRender} from "./render"
export {getHooks} from "./hooks"
export {getHooks} from "./hooks.svelte"

View file

@ -1,11 +1,23 @@
import {normalizeComponents} from "./utils"
import {render} from "svelte/server"
import {createRawSnippet} from "svelte"
export function getRender(components) {
components = normalizeComponents(components)
return function render(name, props, slots) {
const Component = components[name]
const $$slots = Object.fromEntries(Object.entries(slots).map(([k, v]) => [k, () => v]))
return Component.render(props, {$$slots})
return function r(name, props, slots) {
const snippets = Object.fromEntries(
Object.entries(slots).map(([slotName, v]) => {
const snippet = createRawSnippet(name => {
return {
render: () => v,
}
})
if (slotName === "default") return ["children", snippet]
else return [slotName, snippet]
})
)
return render(components[name], {props: {...props, ...snippets}})
}
}

715
assets/package-lock.json generated Normal file
View file

@ -0,0 +1,715 @@
{
"name": "assets",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
},
"devDependencies": {
"esbuild": "^0.24.0",
"esbuild-svelte": "^0.9.0"
}
},
"../deps/phoenix": {
"version": "1.7.0",
"license": "MIT"
},
"../deps/phoenix_html": {
"version": "3.3.1"
},
"../deps/phoenix_live_view": {
"version": "0.18.15",
"license": "MIT"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
"integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
"integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
"integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
"integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
"integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
"integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
"integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
"integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
"integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
"integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
"integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
"integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
"integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
"integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
"integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
"integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
"integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
"integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
"integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
"integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
"integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
"integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
"integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
"integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-typescript": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
"integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"acorn": ">=8.9.0"
}
},
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
"integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.24.0",
"@esbuild/android-arm": "0.24.0",
"@esbuild/android-arm64": "0.24.0",
"@esbuild/android-x64": "0.24.0",
"@esbuild/darwin-arm64": "0.24.0",
"@esbuild/darwin-x64": "0.24.0",
"@esbuild/freebsd-arm64": "0.24.0",
"@esbuild/freebsd-x64": "0.24.0",
"@esbuild/linux-arm": "0.24.0",
"@esbuild/linux-arm64": "0.24.0",
"@esbuild/linux-ia32": "0.24.0",
"@esbuild/linux-loong64": "0.24.0",
"@esbuild/linux-mips64el": "0.24.0",
"@esbuild/linux-ppc64": "0.24.0",
"@esbuild/linux-riscv64": "0.24.0",
"@esbuild/linux-s390x": "0.24.0",
"@esbuild/linux-x64": "0.24.0",
"@esbuild/netbsd-x64": "0.24.0",
"@esbuild/openbsd-arm64": "0.24.0",
"@esbuild/openbsd-x64": "0.24.0",
"@esbuild/sunos-x64": "0.24.0",
"@esbuild/win32-arm64": "0.24.0",
"@esbuild/win32-ia32": "0.24.0",
"@esbuild/win32-x64": "0.24.0"
}
},
"node_modules/esbuild-svelte": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.0.tgz",
"integrity": "sha512-ebGQYTuM4U1Tfx9HdkNtfBjaxY7t7LirlD1yylpSIkhRW+zLzff1wOK1jhuM7ZCnBVCGpt6sGZqiPb5c99KzJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.19"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"esbuild": ">=0.17.0",
"svelte": ">=4.2.1 <6"
}
},
"node_modules/esm-env": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz",
"integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/esrap": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
"integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1"
}
},
"node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/magic-string": {
"version": "0.30.12",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
"integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/phoenix": {
"resolved": "../deps/phoenix",
"link": true
},
"node_modules/phoenix_html": {
"resolved": "../deps/phoenix_html",
"link": true
},
"node_modules/phoenix_live_view": {
"resolved": "../deps/phoenix_live_view",
"link": true
},
"node_modules/svelte": {
"version": "5.1.13",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.13.tgz",
"integrity": "sha512-xVNk8yLsZNfkyqWzVg8+nfU9ewiSjVW0S4qyTxfKa6Y7P5ZBhA+LDsh2cHWIXJQMltikQAk6W3sqGdQZSH58PA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@types/estree": "^1.0.5",
"acorn": "^8.12.1",
"acorn-typescript": "^1.4.13",
"aria-query": "^5.3.1",
"axobject-query": "^4.1.0",
"esm-env": "^1.0.0",
"esrap": "^1.2.2",
"is-reference": "^3.0.2",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
"zimmerframe": "^1.1.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/zimmerframe": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
"dev": true,
"license": "MIT",
"peer": true
}
}
}

11
assets/package.json Normal file
View file

@ -0,0 +1,11 @@
{
"devDependencies": {
"esbuild": "^0.24.0",
"esbuild-svelte": "^0.9.0"
},
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
}
}

View file

@ -4,25 +4,25 @@ config :live_svelte,
ssr_module: LiveSvelte.SSR.NodeJS,
ssr: true
if Mix.env() == :dev do
esbuild = fn args ->
[
args: ~w(./js/live_svelte --bundle) ++ args,
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
end
config :esbuild,
version: "0.17.11",
module: esbuild.(~w(--format=esm --sourcemap --outfile=../priv/static/live_svelte.esm.js)),
main: esbuild.(~w(--format=cjs --sourcemap --outfile=../priv/static/live_svelte.cjs.js)),
cdn:
esbuild.(
~w(--format=iife --target=es2016 --global-name=LiveSvelte --outfile=../priv/static/live_svelte.js)
),
cdn_min:
esbuild.(
~w(--format=iife --target=es2016 --global-name=LiveSvelte --minify --outfile=../priv/static/live_svelte.min.js)
)
end
# if Mix.env() == :dev do
# esbuild = fn args ->
# [
# args: ~w(./js/live_svelte --bundle) ++ args,
# cd: Path.expand("../assets", __DIR__),
# env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
# ]
# end
#
# config :esbuild,
# version: "0.17.11",
# module: esbuild.(~w(--format=esm --sourcemap --outfile=../priv/static/live_svelte.esm.js)),
# main: esbuild.(~w(--format=cjs --sourcemap --outfile=../priv/static/live_svelte.cjs.js)),
# cdn:
# esbuild.(
# ~w(--format=iife --target=es2016 --global-name=LiveSvelte --outfile=../priv/static/live_svelte.js)
# ),
# cdn_min:
# esbuild.(
# ~w(--format=iife --target=es2016 --global-name=LiveSvelte --minify --outfile=../priv/static/live_svelte.min.js)
# )
# end

View file

@ -7,12 +7,20 @@ const args = process.argv.slice(2)
const watch = args.includes("--watch")
const deploy = args.includes("--deploy")
let clientConditions = ["svelte", "browser"]
let serverConditions = ["svelte"]
if (!deploy) {
clientConditions.push("development")
serverConditions.push("development")
}
let optsClient = {
entryPoints: ["js/app.js"],
bundle: true,
minify: deploy,
target: "es2017",
conditions: ["svelte", "browser"],
conditions: clientConditions,
alias: {svelte: "svelte"},
outdir: "../priv/static/assets",
logLevel: "info",
sourcemap: watch ? "inline" : false,
@ -21,7 +29,7 @@ let optsClient = {
importGlobPlugin(),
sveltePlugin({
preprocess: sveltePreprocess(),
compilerOptions: {dev: !deploy, hydratable: true, css: "injected"},
compilerOptions: {dev: !deploy, css: "injected", generate: "client"},
}),
],
}
@ -32,7 +40,8 @@ let optsServer = {
bundle: true,
minify: false,
target: "node19.6.1",
conditions: ["svelte"],
conditions: serverConditions,
alias: {svelte: "svelte"},
outdir: "../priv/svelte",
logLevel: "info",
sourcemap: watch ? "inline" : false,
@ -41,7 +50,7 @@ let optsServer = {
importGlobPlugin(),
sveltePlugin({
preprocess: sveltePreprocess(),
compilerOptions: {dev: !deploy, hydratable: true, generate: "ssr"},
compilerOptions: {dev: !deploy, css: "injected", generate: "server"},
}),
],
}

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@
"esbuild-svelte": "^0.9.0",
"lodash": "^4.17.21",
"stylus": "^0.55.0",
"svelte": "^4.2.19",
"svelte": "^5.1.13",
"svelte-preprocess": "^6.0.3",
"typescript": "^5.6.3"
},

View file

@ -67,8 +67,8 @@ defmodule Example.MixProject do
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
"assets.build": ["tailwind default", "esbuild default"],
"assets.setup": ["tailwind.install --if-missing"],
"assets.build": ["tailwind default"],
"assets.deploy": ["cmd --cd assets node build.js --deploy", "phx.digest"]
]
end

View file

@ -106,7 +106,7 @@ defmodule LiveSvelte do
phx-hook="SvelteHook"
class={@class}
>
<style><%= raw(@ssr_render["css"]["code"]) %></style>
<%= raw(@ssr_render["head"]) %>
<%= raw(@ssr_render["html"]) %>
</div>
</.live_json>

10
mix.exs
View file

@ -55,7 +55,6 @@ defmodule LiveSvelte.MixProject do
defp deps do
[
{:esbuild, "~> 0.5", only: :dev},
{:ex_doc, "~> 0.19", only: :dev, runtime: false},
{:jason, "~> 1.2"},
{:nodejs, "~> 3.1"},
@ -67,13 +66,8 @@ defmodule LiveSvelte.MixProject do
defp aliases do
[
"assets.build": ["esbuild module", "esbuild cdn", "esbuild cdn_min", "esbuild main"],
"assets.watch": [
"esbuild module --watch",
"esbuild cdn --watch",
"esbuild cdn_min --watch",
"esbuild main --watch"
]
"assets.build": ["cmd --cd assets node build.js"],
"assets.watch": ["cmd --cd assets node build.js --watch"]
]
end
end