Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rubysolo/74b817d34536165066cf2a7dbfa0c982 to your computer and use it in GitHub Desktop.
Save rubysolo/74b817d34536165066cf2a7dbfa0c982 to your computer and use it in GitHub Desktop.
Phoenix with esbuild, fortawesome, and tailwindcss

Using fortawesome (font-awesome) and TailwindCSS (JIT mode) with Phoenix 1.6 and esbuild

Setup

  1. Navigate to the assets dir cd assets/
  2. Install fortawesome, esbuild esbuild-sass-plugins, chokidar (used for watching), tailwind, postcss, and the phoenix deps npm install --save-dev fs path chokidar esbuild esbuild-sass-plugin postcss autoprefixer tailwindcss @fortawesome/fontawesome-free ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view
  3. Create a build.js file with the contents of the build.js file in this gist (I prefer to put this inside of a scripts directory inside of assets. If you leave it at the root, you will need to update the build.js file relative paths)
  4. Create the tailwind.config.js in assets root with the contents of the tailwind.config.js file in this gist
  5. Rename app.css to app.scss
  6. At the top of the app.scss file, add the following (also in this gist as app.scss)
$fa-font-path: "@fortawesome/fontawesome-free/webfonts/";
@import "@fortawesome/fontawesome-free/scss/fontawesome";
@import "@fortawesome/fontawesome-free/scss/regular";
@import "@fortawesome/fontawesome-free/scss/solid";
@import "@fortawesome/fontawesome-free/scss/brands";
@import "@fortawesome/fontawesome-free/scss/v4-shims";
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
  1. Update the phoenix configs to use the script file instead of esbuild cli. config/config.exs
config :esbuild,
  version: "0.12.18",
  node: [
    "build.js",
    cd: Path.expand("../assets/scripts/", __DIR__),
    env: %{"ESBUILD_LOG_LEVEL" => "silent", "ESBUILD_WATCH" => "1", "NODE_ENV" => "development"}
  ]

config/dev.exs

watchers: [
    node: [
      "build.js",
      cd: Path.expand("../assets/scripts/", __DIR__),
      env: %{"ESBUILD_LOG_LEVEL" => "silent", "ESBUILD_WATCH" => "1", "NODE_ENV" => "development"}
    ]

mix.exs

defp aliases do
    [
      setup: ["deps.get"],
      "assets.deploy": [
        "cmd --cd assets NODE_ENV=production node scripts/build.js",
        "phx.digest"
      ]
    ]
  end
  1. Update assets/js/app.js for the new scss file extension import ../css/app.scss
  2. Use the icons in your html <i class="fas fa-{ICON}></i>
  3. Use tailwind in your eex/leex/heex/html templates or inside of css:
<main class="container">content</main>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  Button
</button>
main {
  @apply container;
}

.btn-blue {
  @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
}
$fa-font-path: "@fortawesome/fontawesome-free/webfonts/";
@import "@fortawesome/fontawesome-free/scss/fontawesome";
@import "@fortawesome/fontawesome-free/scss/regular";
@import "@fortawesome/fontawesome-free/scss/solid";
@import "@fortawesome/fontawesome-free/scss/brands";
@import "@fortawesome/fontawesome-free/scss/v4-shims";
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
const fs = require('fs');
const path = require('path');
const { watch } = require('chokidar');
const { sassPlugin } = require("esbuild-sass-plugin");
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const tailwindcss = require('tailwindcss');
// config
const ENTRY_FILE = 'app.js';
const OUTPUT_DIR = path.resolve(__dirname, '../../priv/static/assets');
const OUTPUT_FILE = 'app.js';
const MODE = process.env['NODE_ENV'] || 'production';
const TARGET = 'es2016'
// build
function build(entryFile, outFile) {
console.log(`[+] Starting static assets build with esbuild. Build mode ${MODE}...`)
require('esbuild').build({
entryPoints: [entryFile],
outfile: outFile,
minify: MODE === 'dev' || MODE === 'development' ? false : true, // if dev mode, don't minify
watch: false,
bundle: true,
target: TARGET,
logLevel: 'silent',
loader: { // built-in loaders: js, jsx, ts, tsx, css, json, text, base64, dataurl, file, binary
'.ttf': 'file',
'.otf': 'file',
'.svg': 'file',
'.eot': 'file',
'.woff': 'file',
'.woff2': 'file'
},
plugins: [
sassPlugin({
async transform(source, resolveDir) {
const { css } = await postcss(
autoprefixer,
tailwindcss(path.resolve(__dirname, "../tailwind.config.js"))
).process(source)
return css
}
})
], // optional
define: {
'process.env.NODE_ENV': MODE === 'dev' || MODE === 'development' ? '"development"' : '"production"',
'global': 'window'
},
sourcemap: MODE === 'dev' || MODE === 'development' ? true : false
})
.then(() => { console.log(`[+] Esbuild ${entryFile} to ${outFile} succeeded.`) })
.catch((e) => {
console.log('[-] Error building:', e.message);
process.exit(1)
})
}
// helpers
function mkDirSync(dir) {
if (fs.existsSync(dir)) {
return;
}
try {
fs.mkdirSync(dir);
} catch (err) {
if (err.code === 'ENOENT') {
mkDirSync(path.dirname(dir))
mkDirSync(dir)
}
}
}
// make sure build directory exists
mkDirSync(OUTPUT_DIR);
// build initial
build(path.join(__dirname, '..', "js", ENTRY_FILE), `${OUTPUT_DIR}/${OUTPUT_FILE}`)
// watcher
if (MODE === 'dev' || MODE === 'development') {
const watcher = watch(['../../lib/**/*.*eex*', '../js/*.js*', '../css/*.*css*']);
watcher.on('change', () => {
build(path.join(__dirname, '..', "js", ENTRY_FILE), `${OUTPUT_DIR}/${OUTPUT_FILE}`);
})
}
Phoenix esbuild with Tailwind+Fontawesome
module.exports = {
mode: 'jit',
purge: [
'../../lib/**/*.ex',
'../../lib/**/*.leex',
'../../lib/**/*.heex',
'../../lib/**/*.lexs',
'../../lib/**/*.exs',
'../lib/**/*.eex',
'../js/**/*.js'
],
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment