cmd / esbuild
I use esbuild to bundle, minify, and transpile JavaScript and CSS.
It's fast, has sensible defaults, and works well without a complex configuration.
Setup
npm install --save-dev esbuild esbuild-sass-plugin typescript
Example package.json:
{
"name": "app",
"private": "true",
"dependencies": {
"esbuild": "^0.17.15",
"esbuild-sass-plugin": "^2.8.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"scripts": {
"build": "node build.mjs"
},
"devDependencies": {
"typescript": "^5.0.3"
}
}
Build script
Example build.mjs:
import * as esbuild from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
let opts = {
entryPoints: ["js/app.ts", "css/app.scss"],
plugins: [sassPlugin()],
bundle: true,
external: ["fonts/*"],
loader: {
".woff2": "dataurl",
},
tsconfig: "tsconfig.json",
outdir: "public",
};
await esbuild.build({
...opts,
minify: true,
keepNames: true,
entryNames: "[dir]/[name]-[hash]",
});
This:
- Bundles TypeScript and Sass entry points
- Minifies output for production
- Preserves function names for better stack traces
- Fingerprints each output file with a content hash
(
public/css/app-2H67SL6V.css,public/js/app-JBTSFCY2.js) for CDN cache busting
Discover the hashed filename at server startup so templates can link to it. Ruby example:
app_css_path = Dir.glob("public/css/app*.css").first&.split("public")&.last
Run with:
npm run build
Development mode
For development with watch mode:
let ctx = await esbuild.context({
...opts,
sourcemap: true,
});
await ctx.watch();
console.log("Watching...");
Or use esbuild's built-in dev server:
await ctx.serve({
servedir: "public",
port: 3000,
});
Deployment
During deployment, build before starting the web server. Example Render build command:
npm install && npm run build
For a Go-only server that fingerprints in-process at startup instead of at build time, see go/fingerprint.