mindreframer
A proof-of-concept integration of Vite.js (modern JS/assets bundler) with Phoenix + Liveview
Hey everyone, yesterday I have played around with Vite.js and it feels really nice for frontend dev in Phoenix. The setup is currently a bit involved, so I have documented the process here:
Please share your feedback if there are some questions / suggestions!
Most Liked
50kudos
Hi, I switched from Snowpack to Vite recently, and it works great on my production. I’d like to provide my setup here which is, I think, the easiest way.
--- a/assets/package.json
+++ b/assets/package.json
@@ -3,8 +3,8 @@
"scripts": {
+ "build": "vite build",
+ "watch": "vite build --watch --minify false --emptyOutDir false --clearScreen false --mode development"
},
"devDependencies": {
+ "vite": "^2.2.4"
}
}
--- /dev/null
+++ b/assets/vite.config.js
@@ -0,0 +1,19 @@
+export default {
+ publicDir: "./static",
+ build: {
+ target: "es2018",
+ minify: true,
+ outDir: "../priv/static",
+ emptyOutDir: true,
+ rollupOptions: {
+ input: ["js/app.js", "css/app.css"],
+ output: {
+ entryFileNames: "js/[name].js",
+ chunkFileNames: "js/[name].js",
+ assetFileNames: "[ext]/[name][extname]"
+ }
+ },
+ assetsInlineLimit: 0
+ }
+}
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -23,7 +23,7 @@ config :fset, FsetWeb.Endpoint,
watchers: [
- node: ["whatever", "ever", cd: Path.expand("../assets", __DIR__)]
+ yarn: ["run", "watch", cd: Path.expand("../assets", __DIR__)]
]
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,7 +27,7 @@ ENV NODE_ENV=production
COPY lib lib
COPY priv priv
COPY assets assets
+RUN yarn --cwd ./assets run build
RUN mix phx.digest
thiagomajesk
Hi @50kudos! I tried Vite today for the first time with Phoenix 1.6 and your response was really helpful. In the end, I had to do a little bit more work, but It seems everything is working as expected. Here’s my setup…
package.json
{
"repository": {},
"description": " ",
"scripts": {
"build": "vite build",
"watch": "vite build --watch --minify false --emptyOutDir false --clearScreen false --mode development"
},
"dependencies": {
"@popperjs/core": "^2.10.1",
"bootstrap": "^5.1.1",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
},
"devDependencies": {
"sass": "^1.42.1",
"vite": "^2.5.10"
}
}
Fixes problems with import resolution for “phoenix”, “phoenix_html” and “phoenix_live_view” and includes support for Sass.
vite.config.js
export default {
publicDir: "./static",
build: {
target: "es2018",
minify: true,
outDir: "../priv/static",
emptyOutDir: true,
assetsInlineLimit: 0,
rollupOptions: {
input: ["js/app.js", "css/app.scss"],
output: {
entryFileNames: "js/[name].js",
chunkFileNames: "js/[name].js",
assetFileNames: "[ext]/[name][extname]"
}
},
}
}
dev.exs
config :my_app, MyAppWeb.Endpoint,
http: [ip: {127, 0, 0, 1}, port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false,
watchers: [
npm: ["run", "watch", cd: Path.expand("../assets", __DIR__)]
]
mix.exs
"assets.deploy": ["cmd --cd assets npm run build", "phx.digest"]
endpoint.ex
plug Plug.Static,
at: "/",
from: :my_app,
gzip: false,
only: ~w(css js fonts images favicon.ico robots.txt)
Differs from the default esbuild config which outputs directly to
priv/static/assets.
root.html.heex
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")}/>
<script defer phx-track-static type="module" src={Routes.static_path(@conn, "/js/app.js")}></script>
Updates public path and adds
type="module"to properly load the exposed javascript module.
PS.: It seems that Vite supports older browsers with a plugin but I didn’t seem to work for me.
Bonus: If you are using VS Code with ElixirLS and your *.heex files are not properly highlighted you can add the following setting to your user config: "files.associations": {"*.heex": "html-eex" }
thepeoplesbourgeois
Early on, I had the idea that I wanted separate CSS files for separate pages of my app*, and as a result of that, found so much success in just decoupling the styling from the bundling altogether, running brew install sass/sass/sass on my dev box, and then feeling the glorious, glorious lifting of a burden when I deleted so many CSS-based plugins from package.json and webpack.config.
Then Phoenix migrated everything over to esbuild, and rocky takeoff notwithstanding, I have everything running smoothly and snappily now.
# config/dev.exs
watchers: [
sass: ~w[--watch --no-source-map assets/css/:priv/static/css],
esbuild: {Esbuild, :install_and_run, [:default, ~w(--watch)]}
]
I’ve also got a mix task for wrapping all of my assets up in a nice, self-contained digest when it’s deploy time. I call it assets.deploy
defmodule Mix.Tasks.Assets.Deploy do
use Mix.Task
@shortdoc "Packages assets for the browser"
@moduledoc """
Packages assets for the browser.
If the `DEV_MODE` environment variable is set, will not minify the CSS and JS,
or create a sourcemap for either.
"""
def run(_args) do
dev_mode? = env("MIX_ENV") in [:dev, :test, nil]
shell! "rm -rf priv/static/*"
shell! "cp -R assets/static/* priv/static/"
cmd "sass assets/css/app.scss:priv/static/css/app.css" <> (unless dev_mode?, do: " --source-map", else: "")
cmd "mix esbuild default" <> (unless dev_mode?, do: " --minify --sourcemap", else: "")
unless dev_mode? do
cmd "mix phx.digest -o priv/digest"
shell! "rm -rf priv/static/*"
shell! "mv priv/digest/* priv/static/"
end
end
defp cmd(command, collectible \\ IO.stream(:stdio, :line)) when is_binary(command) do
[app | options] = String.split(command, ~r/\s+/)
cmd(app, options, collectible)
end
defp cmd(app, options, collectible) do
[app | options] |> IO.inspect
System.cmd(app, options, into: collectible)
end
defp shell!(command, opts \\ [into: IO.stream()]) do
command |> IO.inspect
System.shell(command, opts)
end
defp env(var), do: System.get_env(var)
end
(the cmd, shell!, and env patterns are brought over from my actual deployment-deploying task, sort of a bespoke take on Capistrano for publishing to gigalixir through its’ git endpoint… possibly from a tty box idk life’s wild sometimes. I call it giggitty. But I’m way off topic now!
EDIT: * I did, of course, realize the awful idea that would’ve been, and now roll all of my CSS, with the comparatively gigantic phoenix.css taffy monster beneath my app.scss file and everything, into one, huge, abomination.







