Joshua's Docs - JavaScript Bundlers - Notes and TIps


What & Link Type
Bundlers Tooling Report
- This is a great new resource (2020) for a comparison of common bundlers and build tools, especially based on supported feature sets.
Comparison / Analysis
Elevator Pitches: Bundlers List / Comparison


*** --- WARNING!!! --- ***
When you are on the docs pages for Parcel, make sure you are on the correct version (V1 vs V2)!

And make sure you have the version of Parcel you mean to have (parcel --version if you want to check).

Parcel Troubleshooting

  • I'm not getting source maps!
    • Things to make sure:
      • Don't use --no-source-maps
      • Don't use --experimental-scope-hoisting


Webpack Resources

What & Link Type
Webpack Docs - "Getting Started" Guide
Flavio - "Introduction to Webpack" Guide
Andrew Welch - "An Annotated Webpack 4 Config for Frontend Web Development" Guide

Webpack Config File

If you want to use a config file with Webpack, you do so by making webpack.config.js in the root of your project, and having a single export:

module.exports = {
	// ...

For seeing all the options, and comments on what they do, reference the configuration page from the official docs.

You can use this interactive config builder tool, or this one, to help you get started.

Webpack Config File Intellisense / Autocomplete

If you are trying to get Intellisense to work in a webpack.config.js file, add a type annotation so VSCode can understand the config object:

/** @type {import('webpack').Configuration} */
module.exports = {
	mode: 'development',
	// ...

See my blog post on the topic, for details.

Webpack Troubleshooting

  • Issues building to a subdirectory, with relative URLs (for example, lots of ChunkLoadError messages, bundles not loading)
  • Using ts-loader causes all of node_modules imports to be type-checked if tsconfig has checkJs on, regardless of exclude settings.
    • One way around this is setting the transpileOnly option to true, and then running tsc --noEmit separately, for the type-checking part
      • This also has the benefit of speeding up the bundling process
    • Since this is caused by the "checkJs": true setting for TSC (which I would call a bug, since it shouldn't ignore the exclude globs), you can work around it with a separate config just for WebPack / ts-loader.
      • So that you can leave that setting as-is in your main config, you can create a separate tsconfig file just for WebPack, and then pass it to ts-loader via the options sub object.


Rollup CLI

Although rollup supports a ton of arguments directly via CLI instead of through a config file, I generally would recommend sticking to a dedicated config file.

You can tell Rollup to explicitly read all values from the config file, and even switch between configs, by passing the config file name via -c or -config.


	"scripts": {
		"build": "rollup -c rollup.config.js",
		"build-debug": "rollup -c rollup-debug.config.js"

Rollup Config File Intellisense / Autocomplete

If you are trying to get Intellisense to work in a rollup.config.js file, add a type annotation so VSCode can understand the config object:

/** @type {import('rollup').RollupOptions} */
const config = {
export default config;

Or, for an array of configs:

/** @type {import('rollup').RollupOptions[]} */
const config = [
	// ...
export default config;

Rollup - Multi-Entry and Nested ESM Output

By default, Rollup bundles your code into single file exports, to reduce the number of chunks. E.g. output.umd.js and output.esm.js.

However, for multiple reasons, a package author dev might prefer to output in a way that more closely mirrors the input directory - with a 1:1 mapping that allows for users of the generated code to use nested imports (e.g. import {myFunc} from 'my-lib/nested/funcs').

To do this in Rollup, you need to use the output.preserveModules option, set to true.

There is also a semi-related issue with Rollup - unless explicitly passed via config.input, Rollup will optimize out nested index.js files. If you want those files / directories to be able to be drilled into from a consumer of your lib, you would need to explicitly mark them as entry point to Rollup. See Rollup Issue #3916 for details.


🚨 Given the speed of Vite's development, these notes are not likely to stay up-to-date. Always refer to Vite's official docs as a first resort.

Vite - Building a Library

Guide: Library Mode

Some random notes on building a library with Vite:

Condensed example for a React library:

import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import { glob } from 'glob';

export default defineConfig({
	plugins: [
			copyDtsFiles: true,
			entryRoot: './nested-lib',
	build: {
		minify: false,
		lib: {
			name: 'my-library',
			entry: glob.sync(resolve(__dirname, 'client/**/*.{jsx,js,ts,tsx}')),
		commonjsOptions: {
			transformMixedEsModules: true,
		rollupOptions: {
			external: ['react'],
			output: {
				preserveModules: true,
				preserveModulesRoot: 'nested-lib',
				globals: {
					react: 'React',
			plugins: [],
		outDir: 'dist',
		emptyOutDir: true,

Vite - Subdirectory Deployment

If you know the subdirectory path in advance, you can use the Public Base Path feature. Provide a value for the base config file option or --base CLI config option and Vite will handle rewrites automatically.

However, if you want to create a build that works in any subdirectory, without knowing it in advance, it takes a bit more setup. A few changes to make:

  • Use base: '' in your vite.config.js file, which forces relative paths in the HTML
  • Change any src="/{path}" attributes to use a relative path
  • In CSS files, and/or style blocks in SFCs, this gets a little more complicated
    • Because CSS gets bundled into {out}/dist/{bundle}.css, any relative URLs point to /dist, instead of the subdirectory root... This means that a CSS value of url('./images/my_img.png') or url('images/my_img.png') will point to {out}/dist/images/my_img.png instead of {out}/images/my_img.png
    • Options are:
      • Move CSS declaration from SFC into a CSS file that does not get bundled, but instead copied as-is, into root. This could be accomplished by using something like {Project_Root}/public/global.css
      • Run a transformer script over the generated CSS to replace the URLs
      • Write a Vite plugin to fix this. Something like this
      • Closely related issues: Issue #3650, #2394, #1115, and #762.
  • I hacked together a quick script to fix paths in JS, HTML, and CSS dist files for subdirectory deployment - not very robust, but worked for my needs

Vite - Miscellaneous Troubleshooting

Reloading and Hot Module Replacement (HMR)

An important distinction to make; when considering the ability of a development environment to automatically refresh the browser / device when your local code changes, there are two popular mechanisms:

  • Live Reloading:
    • This typically reloads the entire app, regardless of what has changed. In practice, this means that editing one component 30 layers deep in your UI will cause the entire page to reload, and your app state will be destroyed / reset
      • Some live-reloaders are at least able to avoid a full refresh if only CSS has changed, as that can easily be re-injected
    • This is sometimes referred to as Hot Reloading, if discussed within the same context as Hot Module Reloading (HMR), but this seems confusing to me
  • Hot Module Replacement:
    • This only replaces the chunks of code that have actually changed, as opposed to the entire app. In practice, this often means that you can make changes to a component and only have that part of the app reload; everything else keeps it state.
    • This is far more complex than whole-page reloads, but also greatly speeds up the development process when it can be used.
    • HMR is only used for local development, and not actual production builds, so it often is implemented separately from the build task - e.g. rollup for builds, but snowpack for HMR and local dev.
Markdown Source Last Updated:
Tue Apr 30 2024 05:43:02 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Wed May 13 2020 21:55:42 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby