sergio

sergio

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

I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into the next-gen of Phoenix!

Most Liked

praveenperera

praveenperera

For people following this guide if you get errors with AlpineJS you need to change the target from es2016 to a minimum of es2017.

# Configure esbuild (the version is required)
config :esbuild,
  version: "0.13.10",
  default: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

For google the errors you’ll see are:

module.esm.js:1635 Alpine Expression Error: func(...).catch is not a function

module.esm.js:1701 Uncaught TypeError: func(...).catch is not a function
    at module.esm.js:1701
    at tryCatch (module.esm.js:1628)
    at evaluate (module.esm.js:1646)
    at module.esm.js:2998
    at Function.<anonymous> (module.esm.js:2390)
    at flushHandlers (module.esm.js:1757)
    at stopDeferring (module.esm.js:1762)
    at deferHandlingDirectives (module.esm.js:1765)
    at initTree (module.esm.js:1953)
    at module.esm.js:1921
reisub

reisub

Just to offer an alternative solution how to add a font file, you can also include the font using the postcss-url plugin as follows. (cc @cvkmohan )

1. Place the font in assets/fonts

I only need the one variable font file in WOFF2 format so for me it’s at assets/fonts/Inter.var.woff2.

2. Add postcss-url dependency

npm install postcss-url --save-dev

3. Add the plugin to assets/postcss.config.js

Mine looks like this after adding it:

module.exports = {
  plugins: {
    'postcss-import': {},
    'postcss-url': { url: 'inline' },
    tailwindcss: {},
    autoprefixer: {},
  }
}

4. Add the @font-face definition to your app.css

@font-face {
  font-family: 'Inter';
  font-weight: 1 999;
  src:
    url('../fonts/Inter.var.woff2') format('woff2-variations');
}

The above configuration will inline the font into your CSS like this:

@font-face { font-family: "Inter"; src: url("data:font/woff2;base64,d09G[...]d6eMC") format("woff2-variations"); font-weight: 1 999; }

If you prefer to keep the font(s) in separate files, use copy instead of inline.

outlog

outlog

not quite as easy as @thomas.fortes suggests…

but close if you are OK with a seperate css file for the font (face) and thus using esbuild for it…

  • download the font Inter font family
  • copy the Inter Web folder into say /assets/vendor/fonts and rename the folder to InterWeb (need to avoid spaces!)
  • change the esbuild args in config.exs to:
    ~w(js/app.js vendor/fonts/InterWeb/inter.css --bundle --loader:.woff2=file --loader:.woff=file --target=es2016 --outdir=../priv/static/assets),
  • notice that the app.js is now outputted inside a js folder
    go to your layout and change the js script src to Routes.static_path(@conn, "/assets/js/app.js")
  • above your app.css stylesheet do a:
    <link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/assets/vendor/fonts/InterWeb/inter.css") %>"/>

for bonus points:
add https://github.com/semencov/tailwindcss-font-inter by doing:
npm install --save-dev tailwindcss-font-inter (in the assets folder)
and then add the plugin in the tailwind config:

  plugins: [require('tailwindcss-font-inter')({ 
    importFontFace: false,
  })]

now you can use the css class font-inter wherever you want…

thiagomajesk

thiagomajesk

Great job @sergio! :clap:

Now that Phoenix 1.6.0-rc.0 is out, I tried to migrate my app and this other post also provided me some insights: https://www.mitchellhanberg.com/how-i-handle-static-assets-in-my-phoenix-apps.

I ended up with something like this:

package.json

"scripts": {
    "deploy": "npm-run-all --parallel deploy-styles deploy-files",
    "deploy-styles": "NODE_ENV=production tailwindcss -i css/app.css -o ../priv/static/assets/app.css --minify",
    "deploy-files": "cpx 'static/**/*' ../priv/static",
    "watch": "npm-run-all watch-styles watch-files",
    "watch-styles": "tailwindcss --input=css/app.css --output=../priv/static/assets/app.css --postcss --watch",
    "watch-files": "cpx 'static/**/*' ../priv/static --watch"
  }
[...]
"devDependencies": {
    "autoprefixer": "^10.2.6",
    "npm-run-all": "^4.1.5",
    "postcss-import": "^14.0.2",
    "tailwindcss": "^2.2.4"
  }

dev.exs

watchers: [
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
    npm: ["run", "watch", cd: Path.expand("../assets", __DIR__)]
  ]

PS.: Even though there’s too much configuration for my taste still, the recompilations are fast, really really fast now with esbuild.

Update: BTW: It seems you don’t need to have postcss installed at all if you are using just Tailwind for your styles. You can replace the deploy script with: NODE_ENV=production tailwindcss -i css/app.css -o ../priv/static/assets/app.css --minify

sergio

sergio

I couldn’t trigger that error on Alpine 3.5.1 (latest at time of writing) but the change to fix is straightforward. Thanks for pointing it out!

Where Next?

Popular in Guides/Tuts Top

anuragg
Hi everyone! I’m the founder of Render, a new cloud provider with native support for Elixir. When we launched Elixir support the most po...
New
OndrejValenta
Me and my boys started a new website specifically designed for other ASP.NET programmers that struggle, as we do, with their transformati...
New
marcelo
I wrote a small article on how to use current_user with coherence on Phoenix. There are already a couple of blogposts about it but I thin...
New
sergio
https://sergiotapia.me/generate-images-with-name-initials-using-elixir-and-imagemagick-374eca4d14ff Hope this saves you guys some time!...
New
jtormey
Hello! Having written a lot of LiveView code, I’ve made some VS Code snippets to speed up writing callbacks for LiveViews and LiveCompon...
New
zenw0lf
Hello all! For those wanting to try your hands at Elixir / Phoenix, I wrote a comprehensive tutorial on doing a simple JSON API with sai...
New
jswny
Hello everyone, I recently redesigned my entire deployment process for Phoenix apps based on Docker. I really like the strategy that I ca...
New
dennisreimann
I wrote a guide for implementing Passwordless Authentication a.k.a. "Magic Login Links": Feedback welcome!
New
fuelen
Hi all! Just want to share a small code snippet which allows writing CASE expressions using macro which is similar to cond. Here is an ...
New
KoviRobi
Hi, I’ve written the following to debug function calls, not sure if it’s useful for anyone else, and if so should I put it somewhere? i...
New

Other popular topics Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement