Skip to content

How Microbundle decides which dependencies to bundle

Jason Miller edited this page Dec 18, 2020 · 1 revision

Bundling of dependencies has a lot of value in certain situations. It's a technique that can allow libraries to import modules while mutating their behavior (through constant inlining or transforms), or to inline specific known working versions of modules that would otherwise be too difficult to offload to the module consumer. However, bundled dependencies become impossible to de-duplicate, which can result in consumers of your npm module ending up with hundreds of copies of certain libraries or polyfills.

Note: the following only applies to the default web target, --target node doesn't inline dependencies by default.

How Microbundle decides inline vs external

Microbundle decides whether a dependency should be inlined or left as an import / require() based on how you declare dependencies in your package.json.

When you import a module in your library, Microbundle checks to see how that module was added to your package.json.

  • If the module is listed in the "peerDependencies" or "dependencies" fields, it will be considered external and won't be inlined into your bundled code. External modules remain runtime dependencies of your bundle using require() or import.
  • Conversely, if the imported module is only referenced in the "devDependencies" package.json field, that dependency module will be inlined into the bundle.

This behaviour makes sense when we think about what those fields express: when someone installs your npm package, your listed "dependencies" are downloaded too - the expectation is that your package will import or require those modules. The same is true for "peerDependencies", which are automatically downloaded by npm version 7, and produced warnings in earlier npm versions if not already installed.

That's a lot of text, just tell me what to do

So, how do you use Microbundle to bundle dependencies? Here's a quick reference:

1. I want to bundle a dependency

In your package.json, install that dependency as a "devDependency". Your package only uses this at build time, because it will be inlined when users install the package, not referenced as an import from your bundled code. Here's what that looks like:

{
  "main": "dist/index.js",
  "module": "dist/index.module.js",
  "scripts": {
    "build": "microbundle my-lib.js"
  },
  "devDependencies": {
    "lib-to-bundle": "^1.2.3"  // gets bundled into dist/index.js
  }
}

2. I don't want to bundle a dependency

In your package.json, install that dependency as a "dependency" (the default). The output generated by Microbundle will try to import or require() this dependency, and listing it in "dependencies" is the way to ensure consumers of your package have it installed. Here's what that looks like:

{
  "main": "dist/index.js",
  "module": "dist/index.module.js",
  "scripts": {
    "build": "microbundle my-lib.js"
  },
  "dependencies": {
    "lib-to-bundle": "^1.2.3"  // will be require()'d by dist/index.js
  }
}

3. I want to bundle specific dependencies

Sometimes projects have more than one build configuration or run microbundle multiple times. This might be to bundle multiple different packages/subpackages in the same npm module, or to provide separate "development" and "production" builds. In these situations, you can explicitly tell Microbundle which dependencies should be inlined and which to leave as external imports using the --external list option:

{
  "source": "my-lib.js",
  "scripts": {
    // bundle everything: (ignores package.json fields)
    "build:standalone": "microbundle --external none --dist standalone.js",
    // bundle everything except pretty-format:
    "build:development": "microbundle --external pretty-format my-lib.development.js"
  },
  "dependencies": {
    "pretty-format": "^1.2.3",
    "debug": "^1.2.3"
  }
}

Notice how, in the above example's development build, only pretty-format is left external. Even though debug is listed in the "dependencies" field, it will be inlined because it is not listed in the value passed to --external. When --external is specified, it overrides Microbundle's default behavior.

You can pass multiple library names as a comma-separated list: --external a,b,c.

Regular expressions are also supported: --external '\.jpe?g$'

4. I want to explicitly bundle all dependencies

In some very specific cases, you may want to produce a bundle where all dependencies are inlined, regardless of whether they're specified as dependencies or peerDependencies in the package.json. Bundling a GitHub Action is one common scenario where this is used. Another example is preact-redux, which inlines various dependencies in order to transform them at build time. For this, Microbundle's --external none option force all dependencies to be inlined:

{
  "source": "generic.js",
  "scripts": {
    "build:preact": "microbundle --external none --define PREACT=1 --dist preact.js",
    "build:react": "microbundle --dist react.js"
  },
  "dependencies": {
    "prop-types": "^1.2.3",
    "some-other-lib": "^1.2.3"
  }
}