Replacing Webpack with Snowpack in a Phoenix Application
Snowpack 3 was recently released and now comes with esbuild support built-in which should mean much faster builds with fewer dependencies, so I replaced Webpack in a fresh phoenix app to see the difference.
tldr; It is incredibly fast and lightweight. See this commit for all the changes required to get this working.
I created a new Phoenix application with LiveView to test this out. LiveView so all the asset config is already set up and we can switch out Webpack for Snowpack.
Firstly I removed all node_modules
, webpack.config.js
, yarn.lock
, .babelrc
and all dependencies in package.json
except phoenix_html
and nprogress
which we’ll keep.
Then I ran yarn add --dev snowpack
to add Snowpack, the final package.json
looks like this.
// /assets/package.json
{
"repository": {},
"description": " ",
"license": "MIT",
"scripts": {
"deploy": "snowpack build",
"watch": "snowpack build --no-bundle --watch"
},
"dependencies": {
"phoenix_html": "file:../deps/phoenix_html"
},
"devDependencies": {
"nprogress": "^0.2.0",
"snowpack": "^3.0.11"
}
}
I also added the equivalent deploy
and watch
commands to replace the Webpack ones.
Next was to create a config file for Snowpack that matches the default asset file structure for Phoenix, and configures esbuild
to optimize the assets for production.
// /assets/snowpack.config.js
module.exports = {
mount: {
js: { url: "/js" },
css: { url: "/css" },
static: { url: "/", static: true, resolve: false },
},
buildOptions: {
out: "../priv/static/",
},
optimize: {
entrypoints: ["./static/index.html"],
bundle: true,
minify: true,
target: "es2018",
},
}
Only odd thing here is the fake HTML entrypoint I had to set up which references the app.js
and app.css
. I found without this the optimizer didn’t run for app.css
, maybe there is a better way of doing this.
Snowpack creates a _snowpack
folder in the output directory so we need to add this to the Static Plug in /lib/snowball_web/endpoint.ex
.
Snowpack creates ES modules for your JS assets so we need to update the script
tag for app.js
from type="text/javascipt"
to type="module"
.
Then we need to update the asset watcher that Phoenix starts up to run Snowpack, which now looks like this.
# /config/dev.exs
watchers: [
node: [
"node_modules/snowpack/index.bin.js",
"build",
"--watch",
cd: Path.expand("../assets", __DIR__)
]
]
The final hurdle was getting the phoenix
and phoenix_live_view
assets to load. Snowpack didn’t like the default import that Phoenix generates so I ended up linking to the Snowpack skypack
CDN to get ES module versions of those dependencies and that works great.
The result is incredibly fast start and rebuild times for assets in development and in production.
# development
$ snowpack build --no-bundle
[snowpack] ! building source files...
[snowpack] ✔ build complete [0.01s]
[snowpack] ! building dependencies...
[snowpack] ✔ dependencies ready! [0.09s]
[snowpack] ! verifying build...
[snowpack] ✔ verification complete [0.00s]
[snowpack] ! writing build to disk...
[snowpack] ! optimizing build...
[snowpack] ✔ optimize complete [0.09s]
[snowpack] ▶ Build Complete!
✨ Done in 0.68s.
# production (on Heroku)
$ snowpack build
[snowpack] ! building source files...
[snowpack] ✔ build complete [0.03s]
[snowpack] ! building dependencies...
[snowpack] ✔ dependencies ready! [0.29s]
[snowpack] ! verifying build...
[snowpack] ✔ verification complete [0.00s]
[snowpack] ! writing build to disk...
[snowpack] ! optimizing build...
[snowpack] ✔ optimize complete [0.05s]
[snowpack] ▶ Build Complete!
Done in 0.84s.
That is a vast improvement over the equivalent Webpack setup and results in 17MB of node_modules vs 225MB before the change.
There are caveats to using the built-in esbuild
as it says it isn’t quite ready for production use yet, but you can add other tried-and-tested plugins to Snowpack if you prefer.
Overall the future is looking good, and I’ll be using this from now on.
If you have any comments please add them to this thread on Twitter.