Upgrade esbuild V0.15 to V0.18

esbuild · JavaScript

Esbuild is moving quickly. When i installed it for my build process last year i took a chance with it. Having quick builds was tempting. I never updated it until a few days ago.

This led me to update from v0.15 to v0.18, That’s 3 minor versions with potential breaking changes. In fact, each single minor release deliberately introduced breaking changes:

Luckily my setup was not too complex and the switch just took an hour of reading the API docs and trying out new features. :)

My setup until that point

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* eslint-disable no-console */
import esbuild from "esbuild";

esbuild
	.build({
		bundle: true,
		entryPoints: ["src/index.ts"],
		minify: true,
		outfile: "dist/bundle.js",
		sourcemap: true,
		watch: {
			onRebuild(error) {
				if (error) {
					console.log("Build failed:", error);
				} else {
					console.log("Build succeeded!\nWaiting for changes...");
				}
			},
		},
	})
	.then(() => {
		console.log("esbuild is in watch mode!");
	});

The configuration itself, i guess, is self-explanatory, we bundle, minify and sourcemap the code.

The only special thing about this setup is the watch. This makes the esbuild process watch the source code directory for file changes and rebuild when they occur. One had to add quite a bit of code to have such log output.

I served the application via the ubiquitous http-server package. Nothing special.

The new setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import esbuild from "esbuild";
import "dotenv/config";
import fs from "node:fs";

const esbuildContext = await esbuild.context({
	bundle: true,
	entryPoints: ["src/index.ts"],
	define: {
		"process.env.ENV_DEV": process.env.ENV_DEV,
	},
	logLevel: "info",
	metafile: true,
	minify: true,
	outfile: "dist/bundle.js",
	sourcemap: true,
	treeShaking: true,
});

await esbuildContext.watch();

await esbuildContext.serve({
	certfile: process.env.CERTFILE,
	keyfile: process.env.KEYFILE,
	onRequest: ({ method, status, path }) =>
		console.log(`${method} ${status} ${path}`),
	port: 8080,
	servedir: "./",
});

There’s quite a few changes. By far the most convenient one is the missing setup code for the watch. In order to watch, one sets up a build context, which has the watch() method. It does everything i setup myself before but much nicer and more coherent.

One great addition is the serve() function the build context exposes. It replaced running http-server completely and allows for secure HTTP as well. So it’s a integrated replacement and fulfills all my needs. Logging the requests put against the server has to be added via the onRequest event handler.

The define property on line 8 is used to enable reloading the application when esbuild rebuilds the application via its live reload feature:

1
2
3
4
5
if (process.env.ENV_DEV) {
	new EventSource("/esbuild").addEventListener("change", () => {
		location.reload();
	});
}

If the “environment variable” ENV_DEV is present and true the application will listen to the event, triggering the page to reload. The esbuild change event will be fired upon each build that is performed.

Note that the aforementioned “environment variable” is not actually an environment variable. It’s a placeholder that will be fully replaced by whatever value the process.env.ENV_DEV contains. In my case this is true when the build watch script is run, so the if-statement will be turned into if(true) { ... }. Refer to the define documentation for more information.

Metafile

One new feature that’s very interesting is the built-in Metafile support. This files enables one to analyze the bundled files. The Analyze functionality enables a human readable summary of the build and it’s composition.

If you like visuals more, esbuild has you covered with its Bundle Size Analyzer (Go try it out, it comes with an example bundle).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const build = await esbuild.build({
	bundle: true,
	entryPoints: ["src/index.ts"],
	define: {
		"process.env.ENV_DEV": process.env.ENV_DEV,
	},
	logLevel: "info",
	metafile: true,
	minify: true,
	outfile: "dist/bundle.js",
	sourcemap: true,
	treeShaking: true,
});

fs.writeFileSync("meta.json", JSON.stringify(build.metafile));
console.log(await esbuild.analyzeMetafile(build.metafile));