Build Tooling
npm and lockfiles, bundling with Vite, and the linting and formatting that stop reviews being about whitespace.
What the Build Is For
Three jobs, essentially. Resolve the module graph from your entry point. Transform syntax the target browsers do not support. Optimise — bundle, minify, split and hash for caching.
Modern tools split development from production deliberately. In development, native ES modules give near-instant startup with no bundling at all; for production, everything is bundled and optimised. That asymmetry is why dev servers became fast.
npm, Lockfiles & semver
package.json states what you want; the lockfile records exactly what you got. Both matter, and the lockfile is the one that makes builds reproducible.
| Range | Accepts | Meaning |
|---|---|---|
1.2.3 | Exactly that | Pinned |
~1.2.3 | 1.2.x | Patches only |
^1.2.3 | 1.x.x | Minors and patches — the default |
* | Anything | Never do this |
Semver promises that major breaks, minor adds, patch fixes. It is a convention, not a guarantee — plenty of minor releases have broken things — which is exactly why the lockfile is committed.
npm ci in CI, not npm install. It installs precisely what the lockfile says and fails if the lockfile disagrees with package.json. install may quietly resolve something newer, so CI tests a different tree than your laptop.dependencies ship to production; devDependencies are build and test only. Getting that wrong either bloats the install or breaks the build with a missing package.
Vite & Module Bundling
A bundler follows imports from an entry point, builds the module graph, and emits files for the browser. The optimisations along the way are what make it worth the complexity.
| Step | Does | Gains |
|---|---|---|
| Tree shaking | Drops exports nobody imports | Smaller bundle |
| Code splitting | Separate chunk per route | Load only this page's code |
| Minification | Strips names and whitespace | Fewer bytes |
| Content hashing | Filename changes when content does | Cache forever, safely |
| Transpiling | Rewrites new syntax | Older browsers work |
Tree shaking only works on static ES module imports — the tool must see at build time what is used. CommonJS require and dynamic imports of computed paths defeat it, which is one practical reason ESM won.
app.4f2a1c.js can be cached for a year, because changing the file changes its name. Any asset served immutably needs this, or a fix ships to nobody.ESLint & Prettier
They do different jobs and the distinction matters. Prettier formats — where the line breaks, how it indents. ESLint finds problems — an unused variable, a missing effect dependency, an await that was forgotten.
Formatting should never be discussed in review. Adopt the defaults, run on save and in CI, and the entire category of comment disappears.
| Runs | Where | On failure |
|---|---|---|
| Format | On save, and a pre-commit hook | Fixes it silently |
| Lint | Editor, and CI | Blocks the merge |
| Type check | CI | Blocks the merge |
| Tests | CI | Blocks the merge |
Interview Questions
Why commit a lockfile?
package.json records ranges; the lockfile records the exact resolved tree. Without it two installs a week apart can produce different dependencies, so a build passing locally fails in CI for reasons nothing in the diff explains.
npm ciornpm installin CI?
ci. It installs exactly the lockfile and fails if it disagrees with package.json. install may resolve something newer, so CI would be testing a tree nobody else has.
What is tree shaking, and when does it fail?
Dropping exports nothing imports. It needs static analysis, so it works on ES module imports and fails on CommonJS require or dynamic imports with computed paths.
Why hash filenames?
So the file can be cached indefinitely. The name changes when the content does, so a new deploy produces a new URL and nobody is served a stale copy. Without it you must choose between stale assets and no caching.
ESLint and Prettier — why both?
Prettier decides formatting, which stops style discussion in review. ESLint catches real problems — unused variables, missing dependencies, unhandled promises. Formatting is not correctness, and correctness is not formatting.
Quick Quiz
^1.2.3 accepts…