Shared Modules

Shared code between different bundles

Shared Modules

Sometimes you need to share code between bundles without exposing it as its own entry or export. Packem's shared module convention bundles such code into a single shared chunk that the different entry bundles reference.

Overview

Name a source file using the convention [name].[layer]-runtime.[ext] and Packem will pull it into a separate layer chunk that matches the layer in the file name. That chunk is built once and referenced by every entry that imports it.

This is especially valuable across multiple runtime bundles (for example default and react-server) where a module must keep a single shared instance across runtimes.

:::note Shared Modules is an experimental feature. The layer in the file name (shared in util.shared-runtime.js) determines the shared chunk it lands in. :::

Project Structure

shared-modules-example/
├── src/
│   ├── index.ts
│   ├── lite.ts
│   └── util.shared-runtime.ts
├── package.json
├── packem.config.ts
└── tsconfig.json

Source Files

src/util.shared-runtime.ts

export function sharedUtil(): string {
  return 'shared'
}

src/index.ts

import { sharedUtil as sharedUtility } from './util.shared-runtime'

export function full() {
  return sharedUtility()
}

src/lite.ts

import { sharedUtil as sharedUtility } from './util.shared-runtime'

export function lite() {
  return sharedUtility()
}

Packem bundles util.shared-runtime into a separate shared layer chunk, which both index and lite reference instead of each inlining their own copy.

Sharing Across Runtimes

The same convention keeps a single instance of a module across different runtime bundles, such as default and react-server:

src/app-context.shared-runtime.ts

'use client'

import { createContext } from 'react'

export const AppContext = createContext(null)

src/index.ts

import { AppContext } from './app-context.shared-runtime'

src/index.react-server.ts

import { AppContext } from './app-context.shared-runtime'

app-context.shared-runtime is bundled into one chunk with a single instance and is shared between the default and react-server bundles.

Configuration

packem.config.ts

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

export default defineConfig({
  transformer,
  declaration: true
})

package.json

{
  "name": "@myorg/shared-modules",
  "version": "1.0.0",
  "type": "module",
  "files": ["dist"],
  "exports": {
    ".": {
      "import": "./dist/index.mjs"
    },
    "./lite": {
      "import": "./dist/lite.mjs"
    }
  },
  "scripts": {
    "build": "packem build"
  },
  "devDependencies": {
    "@visulima/packem": "^2"
  }
}

Notes

  • The shared chunk is created only when a file matches the [name].[layer]-runtime.[ext] convention.
  • Pair this with Multi-Runtime when the shared module must keep one instance across react-server/edge-light bundles.
  • For lazy on-demand splitting instead of always-shared chunks, use Dynamic Imports.
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