---
url: /in-depth/inline-common-chunks.md
---
# Inline Common Chunks

Inline Common Chunks reduces the number of small common chunks generated by [Automatic Code Splitting](./automatic-code-splitting.md). Rolldown copies their code into the chunks that use them and removes the separate common chunk files. A shared runtime makes the copies use the same module state and exports.

## Usage

```js [rolldown.config.mjs]
import { defineConfig } from 'rolldown';

export default defineConfig({
  input: ['src/a.js', 'src/b.js', 'src/c.js'],
  preserveEntrySignatures: false,
  output: {
    dir: 'dist',
    format: 'es',
    codeSplitting: {
      experimentalInlineCommonChunks: {
        maxSize: 10 * 1024, // < 10 KiB
      },
    },
  },
});
```

`maxSize` uses transformed source sizes, summed per chunk before rendering, minification and compression. It defaults to `0` (disabled); `Infinity` removes the size limit.

Entries, dynamic import targets and manual chunks are not inlined. Matching any module with [`exclude`](/reference/TypeAlias.ExperimentalInlineCommonChunksOptions#exclude) prevents its entire common chunk from being inlined.

Inlining requires:

* ESM output ([`output.format: 'es'`](/reference/OutputOptions.format)).
* [`preserveEntrySignatures: false`](/reference/InputOptions.preserveEntrySignatures), which allows unused entry exports to be removed.
* [`strictExecutionOrder`](/reference/OutputOptions.strictExecutionOrder) enabled. Rolldown enables it automatically when omitted.

## How it works

Automatic code splitting follows two rules inherited from Rollup's output model:

1. A module's code appears in only one output chunk.
2. An entry only loads application modules that are reachable from that entry.

The configuration in [Usage](#usage) defines three entries: A, B and C. All three import a large library; A and B also import a small helper with a counter:

::: code-group

```js [src/a.js and src/b.js]
import './library.js';
import { inc } from './helper.js';

console.log(inc());
```

```js [src/c.js]
import './library.js';
```

```js [src/helper.js]
console.log('helper initialized');
let count = 0;
export function inc() {
  return ++count;
}
```

:::

The library and helper go into different common chunks because they have different sets of importing entries, regardless of size. Dynamic import targets count as entries too, so the same situation arises between lazy routes.

[Manual code splitting](./manual-code-splitting.md) could merge them, but then C would load the helper. Inlining copies the helper into A and B. With the helper below `maxSize` and the library above it:

```dot
digraph {
    bgcolor="transparent";
    rankdir=TB;
    node [shape=box, style="filled,rounded", fontname="Arial", fontsize=12, margin="0.2,0.1", color="${#3c3c43|#dfdfd6}", fontcolor="${#3c3c43|#dfdfd6}"];
    edge [color="${#3c3c43|#dfdfd6}"];

    subgraph cluster_before {
        label="Separate common chunks";
        fontname="Arial";
        fontcolor="${#3c3c43|#dfdfd6}";
        color="${#3c3c43|#dfdfd6}";
        a [label="A", fillcolor="${#fff0e0|#4a2a0a}"];
        b [label="B", fillcolor="${#fff0e0|#4a2a0a}"];
        c [label="C", fillcolor="${#fff0e0|#4a2a0a}"];
        helper [label="helper", fillcolor="${#dcfce7|#14532d}"];
        library [label="library", fillcolor="${#dbeafe|#1e3a5f}"];
        a -> helper;
        b -> helper;
        { a b c } -> library;
    }

    subgraph cluster_after {
        label="Helper inlined";
        fontname="Arial";
        fontcolor="${#3c3c43|#dfdfd6}";
        color="${#3c3c43|#dfdfd6}";
        a_inline [label="A + helper", fillcolor="${#fff0e0|#4a2a0a}"];
        b_inline [label="B + helper", fillcolor="${#fff0e0|#4a2a0a}"];
        c_inline [label="C", fillcolor="${#fff0e0|#4a2a0a}"];
        library_inline [label="library", fillcolor="${#dbeafe|#1e3a5f}"];
        { a_inline b_inline c_inline } -> library_inline;
    }
}
```

Arrows represent imports; the shared runtime is omitted.

### Sharing state between copies

Loading A and then B in the same JavaScript environment produces:

```text
helper initialized
1
2
```

Pasting the helper's body into both files would create two counters, both starting at `0`. Each file would log `helper initialized` and `1`. Exported objects and functions would also have different identities.

Rolldown wraps the common chunk in a *factory* function and registers each copy with a shared runtime. Both A and B contain this code (names simplified; library import and entry wrappers omitted):

```js
import {
  __share,
  __share_require,
  __share_export,
  __esmMin,
} from './rolldown-runtime.js';

__share('helper', (exports) => {
  let count;
  function inc() {
    return ++count;
  }
  const init_helper = __esmMin(() => {
    console.log('helper initialized');
    count = 0;
  });
  __share_export(exports, {
    inc: () => inc,
    init_helper: () => init_helper,
  });
});

const helper = __share_require('helper');
helper.init_helper();
console.log((0, helper.inc)());
```

| Runtime helper    | Behavior                                                                               |
| ----------------- | -------------------------------------------------------------------------------------- |
| `__share`         | Registers a factory without calling it. The first registration for an identifier wins. |
| `__share_require` | Runs the factory on first use and caches its exports object.                           |
| `__esmMin`        | Makes the module initializer run once.                                                 |
| `__share_export`  | Exposes exports through getters, preserving live bindings.                             |

A's call to `init_helper` initializes the shared `count`, and `inc()` returns `1`. B gets the same functions: its initializer does nothing, and `inc()` returns `2`.

Separating registration from module initialization makes factories available before consumers run, while `strictExecutionOrder` controls when the module's top-level code executes.

All copies must import the same runtime chunk to share state.

### Loading and caching

Registering the helper from A does not remove the copy embedded in B's file; fetching B still transfers that code.

| Loading scenario                                  | Separate helper chunk        | Helper inlined into A and B              |
| ------------------------------------------------- | ---------------------------- | ---------------------------------------- |
| Cold load of A                                    | Fetch the helper separately. | Fetch the helper inside A.               |
| Then load B                                       | Reuse the loaded helper.     | Fetch another copy inside B.             |
| Load an updated A; the unchanged helper is cached | Reuse the cached helper.     | Fetch the helper again inside the new A. |

Saving a request may justify duplicating a small helper. With `maxSize: Infinity`, the same tradeoff applies to the large library shared by A, B and C: if it is eligible for inlining, loading all three entries transfers three copies of the library. The runtime also adds a shared file and execution overhead.

`maxSize` tests a chunk's source size, not the total bytes its copies add to a user's session. Fewer chunks therefore do not guarantee faster loading. Compare loading times with and without inlining for initial loads and later navigation in your application, including cache reuse, before choosing whether to enable it or raise the threshold.

## Optimizations

### Avoiding unnecessary copies

A and B each need a factory because either entry can load first. Now suppose both can dynamically import `detail.js`, which also uses the helper. By the time either import starts, A or B has already registered the factory, so `detail.js` does not need another copy:

```dot
digraph {
    bgcolor="transparent";
    rankdir=TB;
    node [shape=box, style="filled,rounded", fontname="Arial", fontsize=12, margin="0.2,0.1", color="${#3c3c43|#dfdfd6}", fontcolor="${#3c3c43|#dfdfd6}"];
    edge [fontname="Arial", fontsize=10, color="${#3c3c43|#dfdfd6}", fontcolor="${#3c3c43|#dfdfd6}"];

    a [label="A + helper factory", fillcolor="${#fff0e0|#4a2a0a}"];
    b [label="B + helper factory", fillcolor="${#fff0e0|#4a2a0a}"];
    detail [label="detail.js\nuses the registered factory", fillcolor="${#dcfce7|#14532d}"];
    { rank=same; a; b; }
    a -> detail [label="import()"];
    b -> detail [label="import()"];
}
```

```js [detail.js]
import { __share_require } from './rolldown-runtime.js';

const helper = __share_require('helper');
helper.init_helper();
console.log((0, helper.inc)());
```

Rolldown omits a copy only when every known path from a configured or plugin-emitted entry supplies the factory first. An earlier static dependency can supply it too.

### Keeping modules in the same scope

Rolldown wraps the whole common chunk in one factory, so its modules can refer to each other directly. A minifier can rename and inline code within that scope. References from outside the factory go through its exports object, which limits optimization across the boundary.

## Compatibility

### Chunk selection

A common chunk is not inlined, even below `maxSize`, if:

* It contains top-level await or statically depends on a module that does.
* It contains `import.meta` that remains after tree shaking.
* It contains an unresolved dynamic import or imports an external module.
* Another output chunk re-exports one of its bindings.
* It contains declaration modules or builtin `asset` or `copy` modules.

Direct `eval` and static cycles involving a chunk that is not inlined can also prevent inlining.

### Plugins

* An inlined module appears in the [`moduleIds`](/reference/Interface.RenderedChunk#moduleids) and [`modules`](/reference/Interface.RenderedChunk#modules) of every chunk containing a copy. CSS extraction and SSR manifest plugins must account for a module belonging to multiple chunks.

  ::: info Vite
  For Vite builds, add this `exclude` matcher to `experimentalInlineCommonChunks` to prevent CSS from being duplicated across output stylesheets:

  ```js
  exclude: /\.(css|scss|sass|less|styl|stylus|pcss|postcss|sss)(\?|$)/,
  ```

  :::

* If a `renderChunk` hook gives copies different values, the first registered copy determines the value for all consumers. Use [`exclude`](/reference/TypeAlias.ExperimentalInlineCommonChunksOptions#exclude) for affected modules.

For more background, see the [design RFC](https://github.com/rolldown/rolldown/discussions/10693). Application results and bug reports are welcome in the [feedback issue](https://github.com/rolldown/rolldown/issues/11040).
