This is a quick setup for phoenix 1.6, live view and tailwind for CSS.
File tree:
├── assets
│ ├── build.js
│ ├── css
│ │ └── app.css
│ ├── js
│ │ └── app.js
│ ├── Makefile
│ ├── package.json
│ ├── tailwind.config.js
├── Makefile
├── priv
│ └── static
│ ├── favicon.ico
│ ├── images
│ │ └── phoenix.png
│ └── robots.txt
This is a summary of the main files required for this setup.
All static assets (images...) go into /priv/static
app.css
@tailwind base;
@tailwind components;
@tailwind utilities;
app.js
import 'phoenix_html'
// Establish Phoenix Socket and LiveView configuration.
import { Socket } from 'phoenix'
import { LiveSocket } from 'phoenix_live_view'
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
const liveSocket = new LiveSocket('/live', Socket, { params: { _csrf_token: csrfToken } })
// connect if there are any LiveViews on the page
liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
package.json
{
"dependencies": {
"autoprefixer": "^10.4.2",
"chokidar": "^3.5.3",
"csso-cli": "^3.0.0",
"esbuild": "^0.14.11",
"esbuild-style-plugin": "^1.2.0",
"hsluv": "^0.1.0",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view",
"postcss-import": "^14.0.2",
"postcss-nested": "^5.0.6",
"postcss-url": "^10.1.3",
"tailwindcss": "^3.0.12"
},
"license": "MIT"
}
build.js
#!/usr/bin/env node
const postCssPlugin = require('esbuild-style-plugin')
const path = require('path')
const chokidar = require('chokidar')
const watch = process.argv.indexOf('watch') >= 0
const css = process.argv.indexOf('css') >= 0
const js = process.argv.indexOf('js') >= 0
const entries = []
const paths = ['./js/**/*.{html,js}']
if (js) {
entries.push('js/app.js')
}
if (css) {
paths.push('../lib/**/*.{heex,ex,exs}')
paths.push('./css/**/*.{html,js,css}')
entries.push('css/app.css')
}
if (watch) {
process.stdin.on('end', () => process.exit(0))
process.stdin.resume()
}
async function run () {
const builder = await require('esbuild')
.build({
plugins: [
postCssPlugin({
postcss: [
require('postcss-import')({
path: [
path.join(__dirname, 'assets/css'),
path.join(__dirname, 'assets')
]
}),
require('postcss-url')({ url: 'inline' }),
require('postcss-nested'),
require('autoprefixer'),
require('tailwindcss')()
]
})
],
logLevel: 'info',
entryPoints: entries,
bundle: true,
minify: process.env.NODE_ENV == 'production',
sourcemap: process.env.NODE_ENV == 'production' ? false : 'both',
outdir: '../priv/static/assets/',
outbase: './',
incremental: watch
})
.catch(() => process.exit(1))
let building = false
if (watch) {
await chokidar.watch(paths).on('all', (event, path) => {
if (building === false) {
building = true
builder.rebuild().then(() => building = false)
}
})
}
// await builder.rebuild()
if (!watch) {
process.exit(0)
}
}
run()
taildinw.config.js
The hsluv
and genShades
part is of course optional, but I share this tips as you
might find is practical.
Only content
is required for the setup to work.
The rest are additions I like (spacing...).
const hsluv = require('hsluv')
function hsluvaToRgba (h, s, l, a) {
if (a === undefined) {
a = 1
}
const rgb = hsluv.hsluvToRgb([h, s, l])
return 'rgba(' +
(Math.round(255 * rgb[0])) + ', ' +
(Math.round(255 * rgb[1])) + ', ' +
(Math.round(255 * rgb[2])) + ', ' +
a + ')'
}
function genShades (h, s) {
const res = {}
for (let i = 1; i < 20; i++) {
res[50 * i] = hsluv.hsluvToHex([h, s, 100 - 5 * i])
}
return res
}
module.exports = {
content: [
'../lib/**/*.{heex,ex,exs}',
'./js/**/*.{html,js}',
'./css/**/*.{html,js}'
],
theme: {
extend: {
colors: {
red: genShades(100, 60),
blue: genShades(120, 60)
},
spacing: {
72: '18rem',
84: '21rem',
96: '24rem'
},
width: {
128: '32rem',
192: '48rem',
256: '64rem'
},
maxWidth: {
'2xs': '10rem'
}
}
}
}
in config/dev.exs
change watchers to
watchers: [
make: ["js-watch"],
make: ["css-watch"],
]
Finally, the makefiles (you are not forced to use makefiles, but I like them).
Root Makefile
:
# ...
.PHONY: assets
assets:
cd assets && make build
.PHONY: js-watch
js-watch:
cd assets && make js-watch
.PHONY: css-watch
css-watch:
cd assets && make css-watch
# ...
assets/Makefile
SHELL := /bin/sh
PATH := ./node_modules/.bin:$(PATH)
.PHONY: build
build: export NODE_ENV=production
build: node_modules js css optimize
.PHONY: js-watch
js-watch: node_modules
./build.js watch js
.PHONY: css-watch
css-watch: node_modules
./build.js watch css
.PHONY: js
js: node_modules
./build.js js
.PHONY: css
css: node_modules
./build.js css
.PHONY: optimize
optimize: node_modules
csso ../priv/static/assets/css/app.css --output ../priv/static/assets/css/app.css
.PHONY: clean
clean:
rm -fr node_modules
rm -fr ../priv/static/assets/
rm -fr npm-debug.log*
yarn.lock:
yarn
node_modules: yarn.lock package.json
yarn
.PHONY: deps
deps: node_modules