Externals

Configure external dependencies that should not be bundled

External Dependencies

External dependencies are modules that should not be bundled with your library. Instead, they are expected to be provided by the consuming application. Externalizing dependencies keeps your bundle small and avoids shipping duplicate copies of packages like React or Node.js built-ins.

Packem exposes two related controls:

  • externals — a top-level array that force-marks specific specifiers (or patterns) as external.
  • rollup.resolveExternals — automatic classification of package.json dependency groups and Node.js built-ins, plus an exclude list to force-bundle specific specifiers.

Default Behavior

You usually need no externals configuration at all. By default Packem already externalizes your dependencies, peerDependencies, optionalDependencies, and Node.js built-in modules (fs, path, node:url, …).

The default classification is:

GroupExternalized by default?
dependenciesyes (deps: true)
peerDependenciesyes (peerDeps: true)
optionalDependenciesyes (optDeps: true)
Node.js built-insyes (builtins: true)
devDependenciesno (devDeps: false)

Because of this, most libraries only need to declare their runtime packages in package.json and let Packem do the rest. Reach for the options below when you need to deviate from the defaults.

The externals Array

Use the top-level externals option to force-externalize additional specifiers that would not otherwise be classified as external. It accepts an array of strings and/or RegExp values — it is not a function.

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  externals: ['react', 'react-dom'],
})
  • Strings are matched against the import specifier.
  • RegExp values are tested against the import id, which is handy for whole scopes or families of packages.
import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  externals: [
    'react',
    'react-dom',
    /^@myscope\//, // every package under the @myscope/ namespace
  ],
})

React library example

A typical React component library declares react and react-dom as peer dependencies. They are then externalized automatically. If you also want to make sure the JSX runtime entry points stay external, list them explicitly:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  externals: ['react', 'react-dom', 'react/jsx-runtime'],
})

Monorepo scope example

To keep every workspace package under a shared scope external (so each one is resolved at runtime rather than inlined), use a RegExp:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  externals: [/^@mycompany\//],
})

Automatic Classification: rollup.resolveExternals

rollup.resolveExternals controls how Packem classifies dependency groups and Node.js built-ins. You only need it when you want to change the defaults shown above.

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    resolveExternals: {
      deps: true,
      peerDeps: true,
      optDeps: true,
      builtins: true,
      devDeps: false,
    },
  },
})

Options

OptionTypeDefaultDescription
builtinsbooleantrueMark Node.js built-ins (path, fs, …) as external. Set to false to use shims/polyfills instead.
builtinsPrefix"add" | "ignore" | "strip""add"How to handle the node: prefix on built-in imports.
depsbooleantrueMark dependencies as external.
devDepsbooleanfalseMark devDependencies as external.
optDepsbooleantrueMark optionalDependencies as external.
peerDepsbooleantrueMark peerDependencies as external.
exclude(RegExp | string)[][]Specifiers forced to be bundled, overriding every other rule.

Force-bundling with exclude

The exclude list does the opposite of externals: matching specifiers are forced into the bundle even if they would otherwise be classified as external (for example a dependency or peerDependency). This is useful for tiny, ESM-only packages you would rather inline.

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    resolveExternals: {
      // Bundle this package even though it is a dependency:
      exclude: ['some-tiny-esm-only-pkg', /^@internal\//],
    },
  },
})
  • Strings are matched as exact specifiers.
  • RegExp values are tested against the import id.

exclude overrides all other rules — deps, peerDeps, optDeps, and builtins — so a matched specifier is always bundled.

Bundle everything (for IIFE / standalone output)

When producing a self-contained bundle (such as an IIFE for the browser), turn off every classification so nothing is left external:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    resolveExternals: {
      deps: false,
      peerDeps: false,
      optDeps: false,
      builtins: false,
    },
  },
})

If you only need to bundle a few specific packages rather than everything, list them in exclude instead of disabling whole groups.

Node.js Built-ins and the node: Prefix

builtinsPrefix controls how Packem rewrites imports of Node.js built-in modules:

  • "add" (default) — turns 'path' into 'node:path'.
  • "strip" — turns 'node:path' into 'path'.
  • "ignore" — leaves names exactly as written.
import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    resolveExternals: {
      builtins: true,
      builtinsPrefix: 'add', // 'path' -> 'node:path'
    },
  },
})

To bundle Node.js built-ins with shims/polyfills instead of externalizing them, set builtins: false.

Validating Externals

You can verify your externalization is working by inspecting the output in the build:done hook:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  externals: ['react', 'react-dom'],
  hooks: {
    'build:done': async () => {
      const { readFile } = await import('node:fs/promises')
      const bundle = await readFile('dist/index.mjs', 'utf-8')

      if (bundle.includes('React.createElement')) {
        throw new Error('react was bundled instead of being left external')
      }
    },
  },
})

Troubleshooting

A package is being bundled when it should be external

  • Make sure it is listed in your package.json under dependencies, peerDependencies, or optionalDependencies — those groups are externalized by default.
  • If it is not a declared dependency, add it to the top-level externals array.
  • Check that it is not matched by an exclude pattern in rollup.resolveExternals, which would force it to be bundled.

A package is external when it should be bundled

  • Add the specifier (or a matching RegExp) to rollup.resolveExternals.exclude. exclude overrides all other classification rules.
  • Alternatively, disable the relevant group (deps, peerDeps, optDeps, builtins) if you want to change classification wholesale.

Node.js built-ins appear with an unexpected node: prefix

  • Adjust builtinsPrefix to "add", "strip", or "ignore" depending on the prefix style you want in the output.

Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues