Multi-Runtime

Target multiple runtimes with conditional exports

Multi-Runtime

Packem can target multiple runtimes from a single build: default, node, browser, edge-light, and react-server. Runtime-specific output is driven by the conditional exports map in your package.json, together with a source-file naming convention that lets you provide a different implementation per runtime.

Runtime-specific source files

Special platforms such as react-server, react-native, and edge-light may need different exports or different code paths. Packem provides an override source-file convention: name a source file after the export condition and Packem builds it as a separate bundle.

For example, this exports map produces three distinct bundles:

{
  "exports": {
    "react-server": "./dist/react-server.mjs",
    "edge-light": "./dist/edge-light.mjs",
    "import": "./dist/index.mjs"
  }
}

Each condition is matched to its own source file under src (for example src/index.react-server.ts for the react-server condition), so you can ship a tailored implementation for each runtime.

The edge-light export injects process.env.EdgeRuntime as true; for all other runtimes it evaluates to false. You can branch on it in your code to remove runtime-incompatible paths.

The runtime option

The global build runtime can also be set in your config or via the CLI. The init command writes a runtime value into the generated config:

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

export default defineConfig({
  transformer,
  runtime: 'node', // 'node' | 'browser'
})

Or from the command line:

packem build --runtime browser

The --runtime CLI flag accepts node or browser. The finer-grained react-server, edge-light, and react-native runtimes are selected through the conditional exports map and source-file convention described above.

production and development conditions

If you separate production and development exports conditions, Packem injects process.env.NODE_ENV for you (when present), so you do not need to inject it manually:

{
  "exports": {
    "development": "./dist/index.development.mjs",
    "production": "./dist/index.production.mjs"
  }
}
  • When the production condition is defined and the file ends with *.production.*, the bundle is minified.
  • When the development condition is defined and the file ends with *.development.*, the bundle is not minified.

Sharing modules across runtimes

Modules that must exist as a single instance across multiple runtime bundles (for example a React context shared between default and react-server) use the shared module convention [name].shared-runtime.[ext]:

// src/util.shared-runtime.js
export function sharedUtil() {
  /* ... */
}
// src/index.js
import { sharedUtil } from './util.shared-runtime'
// src/index.react-server.js
import { sharedUtil } from './util.shared-runtime'

The shared module is bundled into a separate chunk referenced by every runtime bundle. See React Support for the React-specific use of this convention.

Next steps

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