chore: switch example project to vite

This commit is contained in:
Denis Donici 2026-03-04 07:59:58 +02:00
parent 77fc015ad9
commit 7108746946
10 changed files with 943 additions and 580 deletions

View file

@ -1,71 +0,0 @@
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")
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,
conditions: clientConditions,
alias: {svelte: "svelte"},
outdir: "../priv/static/assets",
logLevel: "info",
sourcemap: watch ? "inline" : false,
tsconfig: "./tsconfig.json",
plugins: [
importGlobPlugin(),
sveltePlugin({
preprocess: sveltePreprocess(),
compilerOptions: {dev: !deploy, css: "injected", generate: "client"},
}),
],
}
let optsServer = {
entryPoints: ["js/server.js"],
platform: "node",
bundle: true,
minify: false,
target: "node20",
conditions: serverConditions,
alias: {svelte: "svelte"},
outdir: "../priv/svelte",
logLevel: "info",
sourcemap: watch ? "inline" : false,
tsconfig: "./tsconfig.json",
plugins: [
importGlobPlugin(),
sveltePlugin({
preprocess: sveltePreprocess(),
compilerOptions: {dev: !deploy, css: "injected", generate: "server"},
}),
],
}
if (watch) {
esbuild
.context(optsClient)
.then(ctx => ctx.watch())
.catch(_error => process.exit(1))
esbuild
.context(optsServer)
.then(ctx => ctx.watch())
.catch(_error => process.exit(1))
} else {
esbuild.build(optsClient)
esbuild.build(optsServer)
}

View file

@ -1,86 +0,0 @@
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import {createLiveJsonHooks} from "live_json"
import {getHooks} from "live_svelte"
import * as Components from "../svelte/**/*.svelte"
function formatPayload(str) {
if (!str || str === "—") return "—"
try {
return JSON.stringify(JSON.parse(str), null, 2)
} catch {
return str
}
}
const PropsDiffPayloadDisplay = {
mounted() {
this.updateDisplays()
const root = this.el
const diffOnEl = root.querySelector("[data-name='PropsDiffDemo'][data-use-diff='true']")
const diffOffEl = root.querySelector("[data-name='PropsDiffDemo'][data-use-diff='false']")
const observer = new MutationObserver(() => this.updateDisplays())
if (diffOnEl) observer.observe(diffOnEl, {attributes: true, attributeFilter: ["data-props"]})
if (diffOffEl) observer.observe(diffOffEl, {attributes: true, attributeFilter: ["data-props"]})
this._observer = observer
},
updated() {
this.updateDisplays()
},
destroyed() {
this._observer?.disconnect()
},
updateDisplays() {
const root = this.el
const diffOnEl = root.querySelector("[data-name='PropsDiffDemo'][data-use-diff='true']")
const diffOffEl = root.querySelector("[data-name='PropsDiffDemo'][data-use-diff='false']")
const preOn = root.querySelector("#payload-display-diff-on")
const preOff = root.querySelector("#payload-display-diff-off")
if (preOn) preOn.textContent = formatPayload(diffOnEl?.getAttribute("data-props") ?? "—")
if (preOff) preOff.textContent = formatPayload(diffOffEl?.getAttribute("data-props") ?? "—")
},
}
const Hooks = {
...createLiveJsonHooks(),
...getHooks(Components),
PropsDiffPayloadDisplay,
}
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page
liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket

View file

@ -1,6 +1,6 @@
// Vite dev server entry point for HMR development.
// Replaces the esbuild-plugin-import-glob syntax in app.js with Vite's native
// import.meta.glob. The esbuild build (app.js) remains unchanged for production.
// Client-side entry point for both Vite dev server (HMR) and production builds.
// Uses virtual:live-svelte-components (provided by liveSveltePlugin) instead of
// the esbuild-plugin-import-glob glob syntax used by the old esbuild app.js entry.
import "phoenix_html"
import {Socket} from "phoenix"

View file

@ -1,4 +0,0 @@
import * as Components from "../svelte/**/*.svelte"
import {getRender} from "live_svelte"
export const render = getRender(Components)

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,15 @@
{
"type": "module",
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@types/lodash": "^4.14.192",
"daisyui": "^5.0.0",
"dynamic-marquee": "^2.6.2",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"esbuild": "^0.24.0",
"vite": "^6.0.0",
"esbuild-plugin-import-glob": "^0.1.1",
"esbuild-svelte": "^0.9.0",
"lodash": "^4.17.21",
"stylus": "^0.55.0",
"svelte": "^5.19.8",
"svelte-preprocess": "^6.0.3",
"typescript": "^5.6.3"
"typescript": "^5.6.3",
"vite": "^6.0.0"
},
"dependencies": {
"live_json": "file:../deps/live_json",

View file

@ -7,7 +7,10 @@ import path from "path"
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({
plugins: [svelte(), liveSveltePlugin({ entrypoint: "./js/server.vite.js" })],
plugins: [
svelte({ compilerOptions: { css: "injected" } }),
liveSveltePlugin({ entrypoint: "./js/server.vite.js" }),
],
resolve: {
// Explicit alias so Vite always resolves live_svelte to library TypeScript
// source, regardless of package.json export condition availability.
@ -15,6 +18,24 @@ export default defineConfig({
live_svelte: path.resolve(__dirname, "../../assets/js/live_svelte/index.ts"),
},
},
build: {
commonjsOptions: { include: [/vendor\//, /node_modules\//] },
target: "es2020",
outDir: "../priv/static/assets",
emptyOutDir: true,
sourcemap: false,
manifest: false,
rollupOptions: {
input: {
app: path.resolve(__dirname, "./js/app.vite.js"),
},
output: {
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
assetFileNames: "[name][extname]",
},
},
},
server: {
host: "localhost",
port: 5173,

View file

@ -0,0 +1,34 @@
import { defineConfig } from "vite"
import { svelte } from "@sveltejs/vite-plugin-svelte"
import liveSveltePlugin from "live_svelte/vitePlugin"
import { fileURLToPath } from "url"
import path from "path"
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({
plugins: [
svelte(),
liveSveltePlugin({ entrypoint: "./js/server.vite.js" }),
],
resolve: {
alias: {
live_svelte: path.resolve(__dirname, "../../assets/js/live_svelte/index.ts"),
},
},
ssr: {
// Bundle all dependencies into the output file so it works as a standalone
// Node.js module (mirrors the old esbuild `bundle: true` behavior).
noExternal: true,
},
build: {
ssr: "./js/server.vite.js",
outDir: "../priv/svelte",
rollupOptions: {
output: {
entryFileNames: "server.js",
format: "es",
},
},
},
})

View file

@ -12,7 +12,8 @@ config :example, Example.Repo,
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with esbuild to bundle .js and .css sources.
# with tailwind to rebuild CSS. For JS/Svelte assets, run
# `cd assets && npx vite` in a separate terminal (see comment below).
config :example, ExampleWeb.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
@ -21,11 +22,8 @@ config :example, ExampleWeb.Endpoint,
code_reloader: true,
debug_errors: true,
secret_key_base: "cZ19cfxyAQ7Nr/qlKBKxr/jRRgW6wk8MQEgJrMNFjfOPEo6hSY1v50sFb0vIjv3P",
# NOTE: When using Vite dev server (see vite_host config below), comment out
# the `node:` watcher — Vite handles JS compilation. Run `cd assets && npx vite`
# in a separate terminal instead.
# JS assets: run `cd assets && npx vite` in a separate terminal for HMR.
watchers: [
node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)],
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
]

View file

@ -70,13 +70,18 @@ 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.js": ["cmd --cd assets node build.js"],
"assets.js": [
"cmd --cd assets npx vite build",
"cmd --cd assets npx vite build --config vite.ssr.config.js",
"tailwind default"
],
"test.e2e": ["assets.js", "ecto.create --quiet", "ecto.migrate --quiet", "test"],
"assets.setup": ["tailwind.install --if-missing"],
"assets.build": ["tailwind default"],
"assets.deploy": [
"cmd --cd assets npx vite build",
"cmd --cd assets npx vite build --config vite.ssr.config.js",
"tailwind default --minify",
"cmd --cd assets node build.js --deploy",
"phx.digest"
]
]