Elixir Phoenix 1.6 Esbuild + SCSS

One day I was trying to upgrade one of my Phoenix 1.5 apps to Phoenix 1.6. In that app, I customize Bootstrap styles using SCSS. I want to talk about what I found and stumbled on when setting up SCSS.

TIL

phoenixdiff.org

If we want to migrate an app from Phoenix 1.5 to Phoenix 1.6, phoenixdiff.org is a nice tool. It reveals all the changes between different versions.

phoenixframework/esbuild

  • builds and bundles JS and CSS files
  • preconfigured in a Phoenix 1.6 app
  • installation of the esbuild executable is done automatically as long as phoenixframework/esbuild is configured correctly
  • for SCSS we need use either:
  • typically in development, we use watcher so esbuild can build assets every time we make changes in our assets
  • typically in production, we compile assets

configure esbuild

  • Three files are involved for configuration
    • mix.exs
      • elixir-related settings
    • config/config.exs
      • esbuild-related settings
      • we need to specify as args:
        • the version of the esbuild executable we want to use
        • input file
        • output dir
        • etc
    • config/dev.exs
      • watcher-specific settings for development

For example, here is what we would do when we want to use flatpickr (javascript datetime picker).

install the library from NPM

cd my-phoenix-app

npm install --prefix assets --save flatpickr

import the library in JS

  • Esbuild will find libraries from node_modules.
  • CSS can be imported to a JS file as well.
// some js file where we want to use flatpickr

// import js
import flatpickr from 'flatpickr'

// import css
import 'flatpickr/dist/flatpickr.css'

// do something with flatpickr
flatpickr(this.el, {})

It is a lot easier than I expected. But one is that by default exbuild does not know how to handle SCSS files.

CargoSense/dart_sass

Initially I just copied and pasted all the snippets thoughtlessly and one issue occurred. Because I use both phoenixframework/esbuild and CargoSense/dart_sass and they both output the CSS build result to the same file priv/static/assets/app.css, which means they keep on overriding that same file.

After all, I ended up on this strategy.

  • make assets/scss directory and put all the .scss files in it
  • let CargoSense/dart_sass compile assets/scss/index.scss and output assets/scss/index.css
  • import assets/scss/index.css in assets/js/app.js

This way there will be no conflict between phoenixframework/esbuild and CargoSense/dart_sass. CargoSense/dart_sass is in the position of helping phoenixframework/esbuild with comiling SCSS, and once SCSS is converted to CSS, the rest of work is done by phoenixframework/esbuild.

Finally, my config/config.exs looks like this.

# config/config.exs

...

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

# https://github.com/CargoSense/dart_sass
config :dart_sass,
  version: "1.44.0",
  default: [
    args: ["scss/index.scss", "scss/index.css"],
    cd: Path.expand("../assets", __DIR__)
  ]

...

And I add the intermediate assets/scss/index.css file in .gitignore

# Ignore assets that are produced by build tools.
/priv/static/assets/
/assets/scss/*.css

:tada::tada::tada: