Why I Still Write JavaScript Without a Build System

Every time I start a new frontend project, I face the same question: do I set up a build system or not? Most writing about JavaScript these days assumes you're using one, which makes it hard to find guidance for the kind of small, simple projects I work on most of the time.

What Build Systems Actually Do

A build system transforms your source JavaScript or TypeScript into different JavaScript before it reaches the browser. These tools — webpack, rollup, esbuild, parcel, vite — can handle a lot of useful work:

  • Combining hundreds of JS files into one bundle for efficiency
  • Translating TypeScript to JavaScript
  • Typechecking TypeScript
  • Minification
  • Adding polyfills for older browser support
  • Compiling JSX
  • Tree-shaking to remove unused code
  • Building CSS (as Tailwind does)

For large, complex frontend projects, a build system is essentially mandatory. I've used them myself — for instance, Mess With DNS relies on esbuild to translate TypeScript and merge many files into one.

The Real Constraint: Old Projects Must Stay Buildable

I maintain a lot of small websites with roughly zero maintenance budget. Many of them I touch only every few years. My requirement is simple: if I revisit a project I last worked on three to five years ago, I should be able to pull the source from GitHub on a fresh machine, make changes, and deploy — all within about twenty minutes.

Build systems tend to break that promise. A project that builds fine today often fails to build five years later due to dependency drift, deprecated APIs, or Node version changes. And for my small sites, the benefits of a build system are marginal anyway — I rarely need TypeScript or JSX. A single 400-line script.js file is usually sufficient.

A Concrete Example: Rebuilding the SQL Playground

The SQL playground is a Vue project I last touched two years ago on a different machine. Let me walk through what happened when I tried to build it recently on a new computer.

First, npm install fails with an error while building grpc — a dependency I don't even need. After tearing that out and retrying, the install succeeds. Then npm run build fails with an OpenSSL-related error, which a Stack Overflow answer tells me to fix by setting export NODE_OPTIONS=--openssl-legacy-provider.

That fix works, and the build finally succeeds. It's not catastrophic — just removing an unwanted dependency and passing a mysterious Node option — but it's exactly the kind of friction I'd rather avoid when I'm trying to make a quick change to a small site.

Build Tools Aren't All the Same

I should give credit where it's due: esbuild has been noticeably more reliable in my experience. I recently rebuilt an esbuild-based project I hadn't touched in eight months on a fresh machine, and it worked without issue. Whether it will still build cleanly in two years remains to be seen, but so far it's held up better than other tools I've used.

Life Without a Build System Is Simpler Than You'd Think

For example, the nginx playground also uses Vue, but it loads the framework via a plain <script src tag — no bundling, no transpilation step. You can build a perfectly functional Vue 3 app with just two files and about 30 lines of HTML and JavaScript. For anyone wanting to start without a build system, that's a workable template.

When Libraries Force Your Hand

The frustrating part is when otherwise good libraries stop offering a no-build-system path. I recently looked at upgrading from CodeMirror 5 to CodeMirror 6 for a new project. According to the migration guide, CodeMirror 6 can't be used without a build system — so I'm sticking with version 5.

Tailwind has gone a similar direction. You used to be able to download a prebuilt CSS file; Tailwind 3 no longer offers that option without a JavaScript build step. The standalone CSS file is only about 300KB gzipped, which is an acceptable cost for me to avoid adding a build system. I'll keep using Tailwind 2 for now, though a standalone CLI Tailwind released in 2021 does look like a reasonable middle ground.

I can't say for certain why maintainers drop no-build-system distribution — it plausibly adds complexity they don't want to support, or their library's architecture may genuinely prevent it.

Practical Strategies for No-Build-System Development

These are the approaches that work for me:

  • Search for "CDN" on a library's site to find a standalone JavaScript file
  • Check https://unpkg.com to see if a library ships a prebuilt version
  • Self-host libraries rather than depending on a third-party CDN that could disappear
  • Write small custom integrations instead of pulling in extra dependencies — for instance, I recently wrote my own Vue wrapper for CodeMirror
  • When I do need a build system, reach for esbuild

Two other developments look promising for the no-build-system crowd, though I haven't explored them deeply yet: the TypeScript proposal for type syntax in JavaScript comments, and ES modules generally.