-
Notifications
You must be signed in to change notification settings - Fork 363
How Microbundle decides which dependencies to bundle
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.
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 usingrequire()
orimport
. - 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.
So, how do you use Microbundle to bundle dependencies? Here's a quick reference:
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
}
}
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
}
}
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$'
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"
}
}