Skip to content

Inline Common Chunks ​

Inline Common Chunks reduces the number of small common chunks generated by Automatic Code Splitting. 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 ​

rolldown.config.mjs
js
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 prevents its entire common chunk from being inlined.

Inlining requires:

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 defines three entries: A, B and C. All three import a large library; A and B also import a small helper with a counter:

js
import './library.js';
import { inc } from './helper.js';

console.log(inc());
js
import './library.js';
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 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:

cluster_beforeSeparate common chunkscluster_afterHelper inlinedaAhelperhelpera->helperlibrarylibrarya->librarybBb->helperb->librarycCc->librarya_inlineA + helperlibrary_inlinelibrarya_inline->library_inlineb_inlineB + helperb_inline->library_inlinec_inlineCc_inline->library_inline
cluster_beforeSeparate common chunkscluster_afterHelper inlinedaAhelperhelpera->helperlibrarylibrarya->librarybBb->helperb->librarycCc->librarya_inlineA + helperlibrary_inlinelibrarya_inline->library_inlineb_inlineB + helperb_inline->library_inlinec_inlineCc_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 helperBehavior
__shareRegisters a factory without calling it. The first registration for an identifier wins.
__share_requireRuns the factory on first use and caches its exports object.
__esmMinMakes the module initializer run once.
__share_exportExposes 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 scenarioSeparate helper chunkHelper inlined into A and B
Cold load of AFetch the helper separately.Fetch the helper inside A.
Then load BReuse the loaded helper.Fetch another copy inside B.
Load an updated A; the unchanged helper is cachedReuse 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:

aA + helper factorydetaildetail.jsuses the registered factorya->detailimport()bB + helper factoryb->detailimport()
aA + helper factorydetaildetail.jsuses the registered factorya->detailimport()bB + helper factoryb->detailimport()
detail.js
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 and modules of every chunk containing a copy. CSS extraction and SSR manifest plugins must account for a module belonging to multiple chunks.

    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 for affected modules.

For more background, see the design RFC. Application results and bug reports are welcome in the feedback issue.

Was this page helpful?