A Simple, Understandable Production Ready Preact Project Setup
I wrote a similar post in the past, it's time for an updated version. This is a setup similar to the one I'm using with GlooData.
Setup
First we need to create our project, change folder names accordingly:
Create a package.json file at the root of the folder, we will need it later to install one or more build tools:
fetch deps (you can put this in a makefile, a justfile, a shell script or whatever)
Now let's do our first and last edit on our index.html:
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, maximum-scale=1.0, user-scalable=no"> <title>My App</title> <script type=module src="./js/app.js?r=dev"></script> <link rel="shortcut icon" href="img/favicon.png"> </head> <body> <div id="app"></div> </body> </html>
Here's a simple entry point that shows how to use preact without transpilers, modify to your tastes, js/app.js:
import {h, Fragment, render} from '../lib/preact.js'; const c = (tag) => (attrs, ...childs) => h(tag, attrs, ...childs), // some tags here, you get the idea button = c('button'), div = c('div'), span = c('span'); function rCounter(count) { return div( {class: 'counter'}, button({id: 'dec'}, '-'), span(null, count), button({id: 'inc'}, '+') ); } function main() { const rootNode = document.getElementById('app'), dom = rCounter(0); render(dom, rootNode); } main();
Also, I'm not covering state management in this post, I may do it in a future one if there's interest in it.
Serving
For development I use basic-http-server, I just start it at the root of the project, open the browser at the address it logs and just edit, save, reload, no transpilation, no waiting, no watchers.
If you don't want to install it you can use any other, you probably have python at hand, I find it a little slower to load, specially when you have many js modules:
Building
You could just publish it as it is, since any modern browser will load it.
If not there are three steps you can do, bundle it into a single file, minify it and provide it as an old style js file instead of an ES module. Let's do that.
First the bundling, there are two options I use, one if you have deno at hand, the other if you have npm at hand.
First the common part, create a dist folder to put the build artifacts:
Bundling with Deno
Bundling with Rollup
Do this once at the root of your project:
Then:
Minifying with Uglify-js
Do this once at the root of your project:
Then:
And that's it.
Now some extra/optional things.
Reproducible Dev Environments with Nix
How do you get this environment up and running consistently and reproducible?
I use nix, you can if you want too.
First you need to install it.
Then create a shell.nix file at the root of your project with the following:
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-22.05.tar.gz") {} }: pkgs.mkShell { LOCALE_ARCHIVE_2_27 = "${pkgs.glibcLocales}/lib/locale/locale-archive"; buildInputs = [ pkgs.glibcLocales pkgs.wget pkgs.nodejs ]; shellHook = '' export LC_ALL=en_US.UTF-8 export PATH=$PWD/node_modules/.bin:$PATH ''; }
Then whenever you want to develop in this project run the following command at the root of your project:
Now you have a shell with all the tools you need.
Task automation with just
I use just to automate tasks, here are some snippets, you should check the docs for more details:
set shell := ["bash", "-uc"] cdn := "https://cdn.jsdelivr.net/npm/" ver_immutable := "4.1.0" ver_preact := "10.11.2" ROLLUP := "node_modules/.bin/rollup" UGLIFY := "node_modules/.bin/uglifyjs" default: @just --list server: deno run --allow-all server.js static-serve: basic-http-server -a 127.0.0.1:8000 fetch-deps: clean-deps create-deps just fetch immutable.js immutable@{{ver_immutable}}/dist/immutable.es.js just fetch preact.js preact@{{ver_preact}}/dist/preact.module.js clean-deps: rm -rf deps create-deps: mkdir -p deps fetch-full NAME URL: wget {{URL}} -O deps/{{NAME}} fetch NAME URL: wget {{cdn}}/{{URL}} -O deps/{{NAME}} clear-dist: rm -rf dist mkdir-dist: mkdir -p dist/js dist/img dist-bundle: {{ROLLUP}} js/app.js --file dist/app.bundle.js --format iife -n app {{UGLIFY}} dist/app.bundle.js -m -o dist/js/app.js rm dist/app.bundle.js cp img/favicon.png dist/img/ sed "s/type=module //g;s/r=dev/r=$(git describe --long --tags --dirty --always --match 'v[0-9]*\.[0-9]*')/g" index.html > dist/index.html dist: fetch-deps clear-dist mkdir-dist dist-bundle
Older browser and cache busting
The script tag with type=module will not work for really old browsers, you may also want to make sure the browsers load the latest bundle after a deploy, for that you can run a line similar to this to replace the script tag in the index.html in your dist folder with one that achieves the two objectives:
The Proxy trick to not repeat yourself ™️
Above you saw something like this:
import {h, Fragment, render} from '../lib/preact.js'; const c = (tag) => (attrs, ...childs) => h(tag, attrs, ...childs), // some tags here, you get the idea button = c('button'), div = c('div'), span = c('span');
There's a trick to avoid writing the tag name twice, it avoids mistakes and minifies better, here it is: