Phoenix 1.6.0 LiveView + esbuild + Tailwind JIT + AlpineJS - A brief tutorial.
I write open source software, check my Github!
Ping me for collabs
Search for a command to run...
I write open source software, check my Github!
Ping me for collabs
Pau! Of course man hope it helped you out, there is a cleaner way out officially from Phoenix, I need to update this article.
I've got tailwindcss ^2.2.19 when I setup my project. I notice that jit didn't work. The output app.css size is 4 MB. I had to add --jit to my watch command:
npx: [
"tailwindcss",
"--input=css/app.css",
"--output=../priv/static/assets/app.css",
"--postcss",
"--watch",
"--jit",
cd: Path.expand("../assets", __DIR__)
],
This section:
npx: [
"tailwindcss",
"--input=css/app.css",
"--output=../priv/static/assets/app.css",
"--postcss",
"--watch",
cd: Path.expand("../assets", __DIR__)
]
causes Google Chrome to crash with an Aw Snap message. I'm using WSL 2 with Windows 10 and I can see Live reload reloading the app.css many, many times.
Unfortunately I don't use Windows - it probably has something to do with the file observer setup.
For anyone coming here and using Alpine 3.5 or later, you also need to modify your config.exs to change the config :esbuild section from --target=es2016 to --target=es2017 as Alpine no longer supports transpiring to es2016.
Thanks for putting this together. I'm stumped on the alpine stuff. I've npm installed it and added the setup code to "assets/js/japp.js" (via a statement like import "alpinejs" and the rest of the config snipped from the article), but the only way I can get Alpine to work is if I also load it directly into the layout html via
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
then it works. If I comment that script tag out, then no go. Any tips for what I might be missing? Thanks!
Hey Jason Jones I recommend checking out the source code link and compare to what you have. That would be your easiest bet.
Excellent and straight to the point - thanks.
Im having issues with app.js - the part where its assigning
hooks: hooks,
Is throwing a js error "hooks is not defined"
just trying to debug that today.
You're right! Scribe of the Ziggurat
I had that in my code but forgot to update the article, it's all fixed. Thank you!
thank you for the writeup. Can you please add a few more details about how you generated the phx project (did you use phx 1.6 or was it created by hand and 1.6 was added after?!) and also what nodejs version is used, if you don't mind? Thank you!
(will try your project on osx m1, with asdf and report back)
Yes, this project was generated using Phoenix 1.6.0-dev, built from latest master branch. Phoenix 1.6.0 is not out yet but I wanted to benefit from the new auth generators and esbuild.
Here's how you can generate your own Phoenix 1.6.0 project: https://github.com/phoenixframework/phoenix#generating-a-phoenix-project-from-unreleased-versions
AppSignal deploy markers are a way to keep track of what git commit the bug happened, or when the slowdown started appearing in your app. https://docs.appsignal.com/application/markers/deploy-markers.html As long as you have an APP_REVISION environm...
Hey there, we're going to walk through deploying a Phoenix app to a DigitalOcean droplet, manually - no tools no nothing. Just straight up BASH! But don't worry, I've done the hard work so you don't have to. It occurred to me this week after seeing s...
It's generally a good idea to use binary ids / UUIds for the primary keys in your database. Here's how to easily do it in Rails 7, using Postgres! Configure your generators to use UUID for keys. Create a file in config/initializers/generators.rb Rail...
It's been really hard to figure this out and I had to cobble together many sources to get it working. Enjoy! We're going to build a direct image upload to Cloudflare B2 from the browser. That way the file doesn't travel to your servers and slow them...

Uploads files from your browser directly to your bucket.

Phoenix 1.6.0 is nearing release, and when it comes out it will ditch Webpack for esbuild. It's a smaller integration, more predictable and just more productive.
There's very little material out there describing how to set up a liveview, tailwind jit and alpine project using Phoenix 1.6.0
Here's a little guide I pieced together out of forum posts, tweets, github issues and IRC. I can't thank everyone who helped me enough, they did the work, I just compiled it here for posterity.
If you're impatient just look at the code commits, it's this article step by step.
https://github.com/sergiotapia/golden
Update #1, November 11, 2021:
Alpine ^3.5.0 now targets es2017 so make sure you update
config/config.exs. Check the last commit in the repo for a quick fix.
At the time of writing, Phoenix 1.6.0-rc.0 is out! https://www.phoenixframework.org/blog/phoenix-1.6-released
Create your project.
mix phx.new golden --live
In the assets folder, install dev dependencies, also install alpinejs as a prod dependency.
cd assets/
npm install autoprefixer postcss postcss-import postcss-cli tailwindcss --save-dev
npm install alpinejs
We use esbuild to build our javascript payload.
Make sure you comment out line 3 of app.js, if you don't esbuild will also compile your css and will thrash any CSS pipelines you set up for tailwind jit. We will use postcss for CSS building later in this article.
// We import the CSS which is extracted to its own file by esbuild.
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
// import "../css/app.css"
Add alpinejs to your app.js file and make sure you configure the livesocket to call alpine on dom updates.
// import Alpine
import Alpine from "alpinejs";
// Add this before your liveSocket call.
window.Alpine = Alpine;
Alpine.start();
---
// Add dom update support for Alpine.
// before:
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
// after:
let hooks = {};
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
hooks: hooks,
dom: {
onBeforeElUpdated(from, to) {
if (from._x_dataStack) {
window.Alpine.clone(from, to);
}
},
},
});
This one is pretty complicated but bear with me.
Create postcss.config.js file in assets folder.
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
}
}
Create tailwind.config.js file in assets folder.
module.exports = {
mode: "jit",
purge: ["./js/**/*.js", "../lib/*_web/**/*.*ex"],
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};
Add Tailwind's basic css imports to the top of your app.css file.
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
Finally, add a watcher to dev.exs so your compiled app.css is automatically reloaded as you work on your project.
# dev.exs should ultimately end up looking like this:
config :golden, GoldenWeb.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
http: [ip: {127, 0, 0, 1}, port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false,
watchers: [
# Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
npx: [
"tailwindcss",
"--input=css/app.css",
"--output=../priv/static/assets/app.css",
"--postcss",
"--watch",
cd: Path.expand("../assets", __DIR__)
]
]
Just run mix phx.server and visit https://localhost:4000
Let's figure out deployment to production next.
Create a deploy script in package.json
"scripts": {
"deploy": "NODE_ENV=production postcss css/app.css -o ../priv/static/assets/app.css"
},
Modify the assets.deploy alias in mix.exs
defp aliases do
[
setup: ["deps.get", "ecto.setup", "cmd --cd assets npm install"],
"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.deploy": [
"cmd --cd assets npm run deploy",
"esbuild default --minify",
"phx.digest"
]
]
end
Finally let's build a build.sh at root folder deploy script. This will build our app using Elixir releases in prod MIX_ENV and digest our static js and css assets.
#!/usr/bin/env bash
# exit on error
set -o errexit
# Install deps
npm install --prefix ./assets
mix deps.get --only prod
# Initial setup
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix compile
# Migrate the database
MIX_ENV=prod mix ecto.migrate
# Build the release and overwrite the existing release directory
MIX_ENV=prod mix release --overwrite
Give that script run permissions.
chmod a+x build.sh
Run the build script.
./build.sh
Note: This command WILL FAIL because you need to set up a database url environment variable for prod MIX_ENV. Set that up using your database of choosing.