rollup-plugin-node-externals
A Rollup/Vite plugin that automatically declares NodeJS built-in modules as external
. Also handles npm dependencies, devDependencies, peerDependencies and optionalDependencies.
Works in npm/yarn/pnpm/lerna monorepos too!
Why you need this
(click to read)
By default, Rollup doesn't know a thing about NodeJS, so trying to bundle simple things like import path from 'node:path'
in your code generates an Unresolved dependencies
warning.
The solution here is quite simple: you must tell Rollup that the node:path
module is in fact external. This way, Rollup won't try to bundle it in and rather leave the import
statement as is (or translate it to a require()
call if bundling for CommonJS).
However, this must be done for each and every NodeJS built-in you happen to use in your program: node:path
, node:os
, node:fs
, node:url
, etc., which can quickly become cumbersome when done manually.
So the primary goal of this plugin is simply to automatically declare all NodeJS built-in modules as external.
As an added bonus, this plugin will also allow you to declare your dependencies (as per your local or monorepo package.json
file(s)) as external.
Installation
Use your favorite package manager. Mine is npm.
npm install --save-dev rollup-plugin-node-externals
Usage
Import
The plugin is available both as the default export and as a named export:
import nodeExternals from 'rollup-plugin-node-externals'
and
import { nodeExternals } from 'rollup-plugin-node-externals'
will both work.
Options
You generally want to have your runtime dependencies (those that will be imported/required at runtime) listed under dependencies
in package.json
, and your development dependencies (those that should be bundled in by Rollup) listed under devDependencies
.
If you follow this simple rule, then the default settings are just what you need:
export default {
...
plugins: [
nodeExternals(),
]
}
This will bundle your devDependencies
in while leaving your dependencies
, peerDependencies
and optionalDependencies
external.
Should the defaults not suit your case, here is the full list of options.
import nodeExternals from 'rollup-plugin-node-externals'
export default {
...
plugins: [
nodeExternals({
builtins?: boolean
builtinsPrefix?: 'add' | 'strip' | 'ignore'
packagePath?: string | string[]
deps?: boolean
devDeps?: boolean
peerDeps?: boolean
optDeps?: boolean
include?: string | RegExp | (string | RegExp)[]
exclude?: string | RegExp | (string | RegExp)[]
})
]
}
builtins?: boolean = true
Set the builtins
option to false
if you'd like to use some shims/polyfills for those. You'll most certainly need an other plugin as well.
builtinsPrefix?: 'add' | 'strip' | 'ignore' = 'add'
How to handle the node:
scheme used in recent versions of Node (i.e., import path from 'node:path'
).
- If
add
(the default, recommended), the node:
scheme is always added. In effect, this dedupes your imports of Node builtins by homogenizing their names to their schemed version. - If
strip
, the scheme is always removed. In effect, this dedupes your imports of Node builtins by homogenizing their names to their unschemed version. Schemed-only builtins like node:test
are not stripped. ignore
will simply leave all builtins imports as written in your code.
Note that scheme handling is always applied, regardless of the builtins
options being enabled or not.
packagePath?: string | string[] = []
If you're working with monorepos, the packagePath
option is made for you. It can take a path, or an array of paths, to your package.json file(s). If not specified, the default is to start with the current directory's package.json then go up scan for all package.json
files in parent directories recursively until either the root git directory is reached or until no other package.json
can be found.
deps?: boolean = true
devDeps?: boolean = false
peerDeps?: boolean = true
optDeps?: boolean = true
Set the deps
, devDeps
, peerDeps
and optDeps
options to false
to prevent the corresponding dependencies from being externalized, therefore letting Rollup bundle them with your code.
include?: string | RegExp | (string | RegExp)[] = []
Use the include
option to force certain dependencies into the list of externals, regardless of other settings:
nodeExternals({
deps: false,
include: 'fsevents'
})
exclude?: string | RegExp | (string | RegExp)[] = []
Conversely, use the exclude
option to remove certain dependencies from the list of externals, regardless of other settings:
nodeExternals({
deps: true,
exclude: 'electron-reload'
})
Notes
1/ This plugin is smart
- Falsy values in
include
and exclude
are silently ignored. This allows for conditional constructs like exclude: process.env.NODE_ENV === 'production' && 'my-prod-only-dep'
. - Subpath imports are supported with regexes, meaning that
include: /^lodash/
will externalize lodash
and also lodash/map
, lodash/merge
, etc.
2/ This plugin is not that smart
It uses an exact match against your imports as written in your code. No resolving of path aliases or substitutions is made:
import something from '@/lib'
If you don't want lib
bundled in, then write:
nodeExternals({
include: '@/mylib'
})
3/ Order matters
If you're also using @rollup/plugin-node-resolve
, make sure this plugin comes before it in the plugins
array:
import nodeExternals from 'rollup-plugin-node-externals'
import nodeResolve from '@rollup/plugin-node-resolve'
export default {
...
plugins: [
nodeExternals(),
nodeResolve(),
]
}
As a general rule of thumb, you will want to always make this plugin the first one in the plugins
array.
4/ Rollup rules
Rollup's own external
configuration option always takes precedence over this plugin. This is intentional.
5/ Using with Vite
While this plugin has always been compatible with Vite, it was previously necessary to use the following vite.config.js
to make it work reliably in every situations:
import { defineConfig } from 'vite'
import nodeExternals from 'rollup-plugin-node-externals'
export default defineConfig({
...
plugins: [
{ enforce: 'pre', ...nodeExternals() },
]
})
Since version 7.1, this is no longer necessary and you can use the normal syntax instead. You still want to keep this plugin early in the plugins
array, though.
import { defineConfig } from 'vite'
import nodeExternals from 'rollup-plugin-node-externals'
export default defineConfig({
...
plugins: [
nodeExternals()
]
})
Breaking changes
Breaking changes in version 7
Breaking changes in previous versions
Previous versions -- click to expand
Breaking changes in version 6
- This package is now esm-only and requires NodeJS v16+.
If you need CommonJS or older NodeJS support, please stick to v5. - This plugin now has a peer-dependency on Rollup
^3.0.0 || ^4.0.0
.
If you need Rollup 2 support, please stick to v5.
Breaking changes in version 5
- In previous versions, the
devDeps
option defaulted to true
.
This was practical, but often wrong: devDependencies are meant just for that: being used when developping. Therefore, the devDeps
option now defaults to false
, meaning Rollup will include them in your bundle. - As anticipated since v4, the
builtinsPrefix
option now defaults to 'add'
. - The deprecated
prefixedBuiltins
option has been removed. Use builtinsPrefix
instead. rollup-plugin-node-externals
no longer depends on the Find-Up package (while this is not a breaking change per se, it can be in some edge situations).- The plugin now has a peer dependency on
rollup ^2.60.0 || ^3.0.0
.
Breaking changes in version 4
- In previous versions, the
deps
option defaulted to false
.
This was practical, but often wrong: when bundling for distribution, you want your own dependencies to be installed by the package manager alongside your package, so they should not be bundled in the code. Therefore, the deps
option now defaults to true
. - Now requires Node 14 (up from Node 12 for previous versions).
- Now has a peer dependency on
rollup ^2.60.0
.
Licence
MIT