what’s a build system?
Build systems can do lots of useful things, like:
- combining 100s of JS files into one big bundle (for efficiency reasons)
- typechecking Typescript
- adding polyfills to support older browsers
- compiling JSX
- treeshaking (remove unused JS code to reduce file sizes)
- building CSS (like tailwind does)
- and probably lots of other important things
Because of this, if you’re building a complex frontend project today, probably you’re using a build system like webpack, rollup, esbuild, parcel, or vite.
Lots of those features are appealing to me, and I’ve used build systems in the past for some of these reasons: Mess With DNS uses
esbuild to translate Typescript and combine lots of files into one big file, for example.
the goal: easily make changes to old tiny websites
My goal is that if I have a site that I made 3 or 5 years ago, I’d like to be able to, in 20 minutes:
- get the source from github on a new computer
- make some changes
- put it on the internet
And because most of my websites are pretty small, the advantage of using a
build system is pretty small – I don’t really need Typescript or JSX. I can
just have one 400-line
script.js file and call it a day.
example: trying to build the SQL playground
One of my sites (the sql playground) uses a build system (it’s using Vue). I last edited that project 2 years ago, on a different machine.
Let’s see if I can still easily build it today on my machine. To start out, we have to run
npm install. Here’s the output I get.
$ npm install
[lots of output redacted]
npm ERR! code 1
npm ERR! path /Users/bork/work/sql-playground.wizardzines.com/node_modules/grpc
npm ERR! command failed
npm ERR! command sh /var/folders/3z/g3qrs9s96mg6r4dmzryjn3mm0000gn/T/install-b52c96ad.sh
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/surface/init.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/avl/avl.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/backoff/backoff.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_args.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_stack.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_stack_builder.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_trace.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channelz.o
There’s some kind of error building
grpc. No problem. I don’t
really need that dependency anyway, so I can just take 5 minutes to tear it out
and rebuild. Now I can
npm install and everything works.
Now let’s try to build the project:
$ npm run build
? Building for production...Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:130:10)
at module.exports (/Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/util/createHash.js:135:53)
at NormalModule._initBuildHash (/Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/NormalModule.js:414:16)
at handleParseError (/Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/NormalModule.js:467:10)
at iterateNormalLoaders (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
at iterateNormalLoaders (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:221:10)
at runSyncOrAsync (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:130:11)
at iterateNormalLoaders (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
at Array.<anonymous> (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:205:4)
at Storage.finished (/Users/bork/work/sql-playground.wizardzines.com/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:43:16)
This stack overflow answer suggests running
export NODE_OPTIONS=--openssl-legacy-provider to fix this error.
That works, and finally I can
npm run build to build the project.
This isn’t really that bad (I only had to remove a dependency and pass a slightly mysterious node option!), but I would rather not be derailed by those build errors.
for me, a build system isn’t worth it for small projects
esbuild seems a little more stable
I want to give a quick shoutout to esbuild: I learned about esbuild in 2021 and used for a project, and so far it does seem a more reliable way to build JS projects.
I just tried to build an
esbuild project that I last touched 8 months ago on
a new computer, and it worked. But I can’t say for sure if I’ll be able to
easily build that project in 2 years. Maybe it will, I hope so!
not using a build system is usually pretty easy
Here’s what the part of nginx playground code that imports all the libraries looks like:
<link rel="stylesheet" href="codemirror-5.63.0/lib/codemirror.css">
<script src="script.js "></script>
This project is also using Vue, but it just uses a
<script src to load Vue –
there’s no build process for the frontend.
a no-build-system template for using Vue
Here’s a tiny template I built for starting a Vue 3 project with no build system. It’s just 2 files and ~30 lines of HTML/JS.
some libraries require you to use a build system
This build system stuff is on my mind recently because I’m using CodeMirror 5 for a new project this week, and I saw there was a new version, CodeMirror 6.
So I thought – cool, maybe I should use CodeMirror 6 instead of CodeMirror 5. But – it seems like you can’t use CodeMirror 6 without a build system (according to the migration guide). So I’m going to stick with CodeMirror 5.
(edit: it looks like Tailwind released a standalone CLI in 2021 which seems like a nice option)
I’m not totally sure why some libraries don’t provide a no-build-system version – maybe distributing a no-build-system version would add a lot of additional complexity to the library, and the maintainer doesn’t think it’s worth it. Or maybe the library’s design means that it’s not possible to distribute a no-build-system version for some reason.
My main strategies so far are:
- use https://unpkg.com to see if the library has a built version I can use
- host my own version of libraries instead of relying on a CDN that might go down
- write my own simple integrations instead of pulling in another dependency (for example I wrote my own CodeMirror component for Vue the other day)
- if I want a build system, use esbuild
A couple of other things that look interesting but that I haven’t looked into:
- ES modules generally