Skip to main content

Command Palette

Search for a command to run...

Phoenix 1.6.0 LiveView + esbuild + Tailwind JIT + AlpineJS - A brief tutorial.

Published
S

I write open source software, check my Github!

Ping me for collabs

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

Configuring the Javascript pipeline.

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);
      }
    },
  },
});

Configuring the CSS pipeline

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__)
    ]
  ]

At this point you should have nice working development environment.

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.

P
Pau Riosa3y ago

Thank you for this one Sergio!

1
S

Pau! Of course man hope it helped you out, there is a cleaner way out officially from Phoenix, I need to update this article.

J
joi mann4y ago

Thanks Sergio, this helped me a lot. I created a script to do all the above steps automatically.

script at gitlab

1
C

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__)
    ],
P

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.

S

Unfortunately I don't use Windows - it probably has something to do with the file observer setup.

J

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.

1
J

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!

S

Hey Jason Jones I recommend checking out the source code link and compare to what you have. That would be your easiest bet.

S

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.

S

just declaring an empty hooks seemed to fix it all up

let hooks = {};
1
S

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!

F
Florin4y ago

Just to confirm, your project works on OSX (BigSur) on an M1 chip, and having the following:

  • asdf: v0.8.1-a1ef92a
  • elixir 1.12.2
  • erlang 24.0.2
  • nodejs 16.6.2

So glad there is hope to never needing again to deal with node-sass, webpack and other similar ... "life improving" tools.

1
F
Florin4y ago

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)

1
S

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

1