Skip to content

Turbopack: migrate CommonJS tree-shaking snapshot tests to execution tests - #101228

Merged
sokra merged 18 commits into
fleet/turbopack-exec-tests-2-comptimefrom
fleet/turbopack-exec-tests-3-cjs-tree-shaking
Nov 9, 2026
Merged

sokra merged 18 commits into
fleet/turbopack-exec-tests-2-comptimefrom
fleet/turbopack-exec-tests-3-cjs-tree-shaking

Conversation

@sokra

@sokra sokra commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Stack (migrating Turbopack snapshot tests to execution tests), part 3 of 7:

  1. Turbopack: migrate import and resolution snapshot tests to execution testsΒ #101211: imports, import.meta, TypeScript config, Node
  2. Turbopack: migrate comptime snapshot tests to execution testsΒ #101227: comptime
  3. Turbopack: migrate CommonJS tree-shaking snapshot tests to execution testsΒ #101228: CommonJS tree shaking ← this PR
  4. Turbopack: migrate ESM export optimization snapshot tests to execution testsΒ #101229: ESM export optimizations
  5. Turbopack: migrate tree-shaking and scope-hoisting snapshot tests to execution testsΒ #101230: tree shaking and scope hoisting
  6. Turbopack: migrate basic snapshot tests to execution testsΒ #101231: basic/chunking
  7. Turbopack: migrate CSS snapshot tests to execution testsΒ #101232: CSS

What?

Migrates the cjs-remove-unused-exports/* and cjs-remove-unused-imports/* Turbopack snapshot tests to execution tests. Test-only change.

Why?

These tests check CommonJS tree shaking: which exports.* assignments are dropped and which are kept. In a snapshot, that property is buried in the whole bundle. Execution tests can assert it directly and still check the runtime values.

How?

  • The tests enable cjsTreeShaking. Each one asserts the values the consumer reads, then inspects the CommonJS module factory source to confirm unused exports were dropped and used ones kept. Cases covered:
    • Object.defineProperty exports, including getters that read another export
    • exports assigned multiple times
    • top-level this exports, and this read from an arrow function
    • module.exports object literals: plain, with getters and methods, with __esModule interop, and with later assignments
    • named ESM imports of CommonJS: impure export writes keep their side effects
    • require consumers: aliased requires, require bindings that escape or are narrowed to members, destructuring, and wholesale requires keeping all exports
  • cjs-remove-unused-imports: discarded require()s of side-effect-free modules are removed. A module that defines properties on a foreign object is kept.
  • require-destructure and require-member are merged into one require-narrow execution test. require-binding-escape is removed: the require-binding execution test covers it.
  • For each test I flipped cjsTreeShaking and confirmed the test fails.

Verified on this layer: 385 execution and 88 snapshot tests pass. Prettier is clean.

@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 3857cd0
View workflow run

@sokra
sokra added this pull request to stack #101233 November 5, 2026 16:28

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vercel Agent reviewed 17235c0 and found no issues.

vercel-fleet-prod Bot and others added 17 commits November 9, 2026 13:35
…eads-export snapshot test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
… test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…t test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
… snapshot test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…snapshot test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…exports snapshot test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…est to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…t test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…t to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…est to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…st to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
… test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…apshot test, covered by the require-binding execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…shot test to the require-narrow execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…test to the require-narrow execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…st to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…erty snapshot test to an execution test

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
@sokra
sokra force-pushed the fleet/turbopack-exec-tests-3-cjs-tree-shaking branch from 17235c0 to 3857cd0 Compare November 9, 2026 13:35

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vercel Agent reviewed 3857cd0 and found no issues.

Comment on lines -3 to -5
+ v-----------------------------v
1 + import { used } from './lib.js'
+ ^-----------------------------^

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up: This is a bug. used exists in the module. this.used should also be detected similar to exports.used.

@sokra
sokra marked this pull request as ready for review November 9, 2026 16:25
@sokra
sokra merged commit 2bf4ea9 into canary Nov 9, 2026
107 checks passed
@sokra
sokra deleted the fleet/turbopack-exec-tests-3-cjs-tree-shaking branch November 9, 2026 16:27
sokra added a commit that referenced this pull request Nov 9, 2026
…tests (#101211)

**Stack** (migrating Turbopack snapshot tests to execution tests), part
1 of 7:

1. #101211: imports, `import.meta`, TypeScript config, Node ← this PR
2. #101227: comptime
3. #101228: CommonJS tree shaking
4. #101229: ESM export optimizations
5. #101230: tree shaking and scope hoisting
6. #101231: basic/chunking
7. #101232: CSS

### What?

Migrates the `imports/*`, `import-meta/*`, `typescript/*` and `node/*`
Turbopack snapshot tests
(`turbopack/crates/turbopack-tests/tests/snapshot/**`) to execution
tests (`tests/execution/turbopack/**`), and removes the stale
`imports/import-meta-glob` snapshot output (it had no `input/`).
Test-only change. The one harness change is small: `tests/execution.rs`
now puts static assets inside the output root, so they are written to
disk with the chunks. No compiler or runtime code changes.

This is the first PR of a stack that moves 104 of the 128 snapshot tests
to execution tests, one category group per PR.

### Why?

Snapshot tests store the whole emitted bundle. Any unrelated codegen or
runtime change touches many output files, and the snapshot doesn't say
which property the test is meant to check. Execution tests run the
bundled code and assert that property directly.

### How?

For each snapshot test I worked out its intent and asserted it,
preferring in this order:

1. **Runtime behavior**: values, module identity, evaluation order,
thrown errors.
2. **`fn.toString()` / module factory source** via
`__turbopack_modules__`, for optimizations not observable at runtime.
3. **Emitted output files**, as a last resort.

A new shared helper,
`tests/execution/turbopack/__helpers__/module-factories.js`, finds a
module's factories in `__turbopack_modules__` by its path under `input/`
and returns their source. Tests across the stack use it instead of
copies of the same lookup. Fixture discovery doesn't pick it up as a
test.

In this PR:

- **Imports:**
- dynamic imports and modules shared between static and dynamic imports.
A dynamic import of a module that is already available resolves within
one microtask, so no chunk is loaded (`productionChunking`)
- static asset URLs, and that the asset file is emitted with the right
content
- JSON imports (including huge and invalid JSON), and that huge JSON is
parsed with `JSON.parse`
  - extension resolution order
  - duplicate bindings
- ignore comments, which keep ignored `import()`/`require()` in the
output
  - optional-import comments
  - unresolvable CJS/ESM requests, which throw when evaluated
  
  Reported issues stay as the execution tests' `issues/` snapshots.
- **`import.meta`:** `import.meta.url` in ESM and CommonJS, multiple
usages, the exact properties of the object (`url` and the `env` object),
mutation, using the object as a value, and `new URL(...,
import.meta.url)` assets.
- **TypeScript config:** `paths`/`baseUrl` from
`jsconfig.json`/`tsconfig.json` and every `extends` variant, asserted
through the modules they resolve to.
- **Node:** `node:` and Bun builtins are externals, and `child_process`
spawns with a dynamic program or an inline `node -e` script.
- Snapshot tests default to all optimizations off and execution tests to
most of them on. Each migrated test sets the options its intent depends
on.
- The Web Worker cases of `imports/ignore-comments` stay as a snapshot
test, because the execution harness only runs Node.js.
- The stale `imports/import-meta-glob` output only had the source in its
source map. That case (lazy matches split into async chunks, eager ones
kept in the entry chunk) is already covered by the existing
`resolving/import-meta-glob-code-splitting` execution test.

Verified with `cargo test -p turbopack-tests --test execution --test
snapshot` on this layer: 360 execution and 116 snapshot tests pass.
Prettier is clean.

<!-- NEXT_JS_LLM -->


<!-- fleet a144b357-ef78-451c-8231-ec24b8a59cec -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
sokra added a commit that referenced this pull request Nov 9, 2026
**Stack** (migrating Turbopack snapshot tests to execution tests), part
2 of 7:

1. #101211: imports, `import.meta`, TypeScript config, Node
2. #101227: comptime ← this PR
3. #101228: CommonJS tree shaking
4. #101229: ESM export optimizations
5. #101230: tree shaking and scope hoisting
6. #101231: basic/chunking
7. #101232: CSS

### What?

Migrates the `comptime/*` Turbopack snapshot tests to execution tests.
Test-only change.

### Why?

Same motivation as the rest of the stack: the snapshots store whole
bundles, but these tests are about compile-time evaluation. An execution
test can assert that directly.

### How?

- Each test checks its runtime result. It then inspects the module
factory source (`fn.toString()` via `__turbopack_modules__`) to confirm
the constant was inlined or the dead code removed. Covered cases:
- cross-module constant inlining, including constants computed from
other constants and constants reexported through side-effect-free
barrels
- `turbopackConstants: true/false` import attributes, and the issue for
non-constant exports. The branches that must stay require modules that
don't exist, so the `issues/` snapshot proves they were kept at compile
time
  - long literals, which aren't inlined
  - constant/dynamic export cycles not hanging
- code after an early `return` being removed (top level, if/else, class
members, nested functions)
  - `typeof` on a CommonJS module
- Tests that inspect per-module factories disable scope hoisting, so
modules stay separate.
- For each test I flipped the relevant option and confirmed the test
fails.
- `comptime/define` stays a snapshot test. It needs custom compile-time
defines that the execution harness doesn't configure.
- Two missed optimizations are tracked as expected-failure tests in
`comptime/__skipped__/`. The harness requires them to fail until they
are implemented:
- `cross-module-imported-reexports`: values a module imports and
(re)exports are constants too, but aren't inlined.
- `cross-module-long-literal-conditions`: long literals, `NaN` and
`Infinity` aren't inlined, but could still be used to evaluate
conditions at compile time.

Known gap, not addressed here: `comptime/cross-module-strict` needs
`scopeHoisting: false`. With module merging on, `other.js`'s module
factory goes missing at runtime.

Verified on this layer: 372 execution and 106 snapshot tests pass.
Prettier is clean.

<!-- NEXT_JS_LLM -->


<!-- fleet a144b357-ef78-451c-8231-ec24b8a59cec -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
sokra added a commit that referenced this pull request Nov 9, 2026
…n tests (#101229)

**Stack** (migrating Turbopack snapshot tests to execution tests), part
4 of 7:

1. #101211: imports, `import.meta`, TypeScript config, Node
2. #101227: comptime
3. #101228: CommonJS tree shaking
4. #101229: ESM export optimizations ← this PR
5. #101230: tree shaking and scope hoisting
6. #101231: basic/chunking
7. #101232: CSS

### What?

Migrates the ESM export optimization snapshot tests to execution tests:
`export-alls/*`, `ns-value-reexport/*`, `reexport-drop/*`,
`remove-unused-imports/*`, `mangle-exports/*` and
`reexport-registration/*`. Test-only change.

### Why?

These tests cover export-level optimizations: dropping unused
re-exports, export mangling, and how re-exports are registered. A
snapshot of the whole bundle doesn't show which of these each test
checks. Execution tests assert it directly.

### How?

- **Runtime assertions** cover:
- `export *` of CommonJS through a chain, and from a module with side
effects
  - namespaces reexported as values
- reexports registered from several sources, with names with and without
commas, in separate statements, or from a module scope-hoisted into the
importer. `facade-scope-hoisted-shared` also asserts the current
hoisting shape: the facade of `x.js` is merged into `index.js`, and
`x.js <locals>` and the dynamically imported `reexports.js` keep their
own factories
  - namespace key order: `plain-reexports` checks it without sorting
- **Module factory inspection** confirms that unused static, dynamic and
renamed reexports are dropped, and that imports used only by unused
exports are removed, transitively. A namespace import accessed with a
string-literal key is narrowed.
- **Options:** the tests set `followReexports`/`removeUnusedExports`
explicitly where their intent depends on them.
- **Export mangling** is on by default in execution tests.
`exports/mangle-single-export` checks the value and the fixed
single-export key, not the original export name.
- Removed as duplicates:
  - `reexport-drop/basic` is identical to `reexport-drop/dynamic`.
- `mangle-exports/escaping-namespace` and
`mangle-exports/named-and-preserved` are covered by the
`exports/mangle-*` execution tests.
- `reexport-registration/{comma-name,facade-all-reexports,two-sources}`
are covered by existing `reexport-registration` execution tests.
- For the optimization tests I flipped the relevant option and confirmed
the test fails.

Known gap, not addressed here: a namespace that re-exports names from
several modules lists them module by module, not in the code unit order
ESM requires. `mixed-encoding` gives `b, has,comma, c`. That test still
sorts the keys. The correct order is checked by the expected-failure
test
`reexport-registration/__skipped__/mixed-encoding-namespace-key-order`.

Verified on this layer: 398 execution and 70 snapshot tests pass.
Prettier is clean.

<!-- NEXT_JS_LLM -->


<!-- fleet a144b357-ef78-451c-8231-ec24b8a59cec -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
sokra added a commit that referenced this pull request Nov 9, 2026
…execution tests (#101230)

**Stack** (migrating Turbopack snapshot tests to execution tests), part
5 of 7:

1. #101211: imports, `import.meta`, TypeScript config, Node
2. #101227: comptime
3. #101228: CommonJS tree shaking
4. #101229: ESM export optimizations
5. #101230: tree shaking and scope hoisting ← this PR
6. #101231: basic/chunking
7. #101232: CSS

### What?

Migrates the tree-shaking and scope-hoisting snapshot tests to execution
tests: `basic-tree-shake/*`, `intermediate-tree-shake/*`,
`tree-shaking/dce`, `scope-hoisting/*` and `minification/paren-remover`.
Test-only change.

### Why?

These tests are about which code survives tree shaking and how modules
are merged. The snapshots store the whole bundle without stating that
property. Execution tests run the code and inspect the relevant module
factories.

### How?

- **Tree shaking:** `basic-tree-shake` and `intermediate-tree-shake`
enable `moduleFragmentsEnabled`. They assert side effects and imported
values, and check that unused exports are removed, for named, renamed,
namespace, side-effect, `require` and dynamic imports.
`intermediate-tree-shake` also checks importing locals of a module that
has reexports, skipping a side-effect-free facade, and exporting only
the used function.
- **DCE:** `tree-shaking/dce` asserts a module only used in dead code is
removed.
- **Scope hoisting:** `scope-hoisting/*` check that merged modules
importing the same module evaluate it once and in order, and that a
merged module is shared between async chunks and merged only within one
chunk.
- `scope-hoisting/direct-eval` moves into the existing `direct-eval`
execution test, which now also checks that indirect eval is still
hoisted.
- `minification/paren-remover` is removed: the existing `paren-remover`
execution test now runs minified and covers it.
- For each optimization test I flipped the relevant option and confirmed
the test fails.

Known bug, not fixed here: with module fragments, a mutated variable's
side effects all see its final value, so `dog!!!` is logged three times
instead of `dog!`, `dog!!`, `dog!!!`. The old snapshot output already
showed this. The migrated tests assert that the side effects happen. The
correct order is in `basic-tree-shake/__skipped__/side-effect-order`,
which the harness requires to fail until the bug is fixed.

Verified on this layer: 414 execution and 53 snapshot tests pass.
Prettier is clean.

<!-- NEXT_JS_LLM -->


<!-- fleet a144b357-ef78-451c-8231-ec24b8a59cec -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
sokra added a commit that referenced this pull request Nov 9, 2026
**Stack** (migrating Turbopack snapshot tests to execution tests), part
6 of 7:

1. #101211: imports, `import.meta`, TypeScript config, Node
2. #101227: comptime
3. #101228: CommonJS tree shaking
4. #101229: ESM export optimizations
5. #101230: tree shaking and scope hoisting
6. #101231: basic/chunking ← this PR
7. #101232: CSS

### What?

Migrates the `basic/*` Turbopack snapshot tests to execution tests.
Test-only change.

### Why?

These tests cover chunking and basic code generation: async chunks,
minification, `export default`, shebangs, top-level await and parse
errors. Execution tests assert the behavior directly, instead of storing
the whole emitted bundle.

### How?

- **Chunking:** `async-chunk`, `async-chunk-build` and
`nested-async-availability` load (nested) async chunks that share
modules with their parent chunk. `chunked` imports from `node_modules`.
- **Minification:** the `ecmascript-minify*` tests enable minification.
They check that behavior is kept and that the module factory source is
compressed or minified per module, and that CommonJS modules minified
before chunking stay in sloppy mode while ES modules stay strict. That
is checked at runtime, not by looking for a `"use strict"` directive.
- **Codegen:**
  - `export-default` imports a default-exported expression.
  - `shebang` handles shebangs in the entry and in `node_modules`.
  - `top-level-await` waits for a module using top-level await.
- **Errors:** `ts-parse-error` throws when the broken module is
required, and keeps its parse error as an `issues/` snapshot. Its
`.prettierignore` entry moves with the fixture.
- **Removed:** `basic/use-strict`, which the
`minification/strict-mode-layouts` execution tests cover.
- **Still snapshots**, because the browser chunk format is what they
test: `basic/chunk_loading_global` and `basic/single-chunk-entry`.

Verified on this layer: 425 execution and 41 snapshot tests pass.
Prettier is clean.

<!-- NEXT_JS_LLM -->


<!-- fleet a144b357-ef78-451c-8231-ec24b8a59cec -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
sokra added a commit that referenced this pull request Nov 9, 2026
**Stack** (migrating Turbopack snapshot tests to execution tests), part
7 of 7:

1. #101211: imports, `import.meta`, TypeScript config, Node
2. #101227: comptime
3. #101228: CommonJS tree shaking
4. #101229: ESM export optimizations
5. #101230: tree shaking and scope hoisting
6. #101231: basic/chunking
7. #101232: CSS ← this PR

### What?

Migrates the `css/*` and `cssmodules/*` Turbopack snapshot tests to
execution tests. This is the last PR of the stack. Test-only change.

### Why?

The CSS snapshots store whole emitted chunks, but each test checks one
property, such as import order or `url()` rewriting. These execution
tests assert that property directly.

### How?

- **CSS modules:** `composes` (also from another CSS module or a
package), static and dynamic imports, and deduplication are asserted at
runtime through the exported class names.
- **Emitted CSS:** the other tests read the emitted CSS chunks from
`output/` (which the harness cleans before every run) and check one
property each, not the whole file. A new shared helper,
`__helpers__/css.js`, reads the chunks and parses their source comments,
so the tests don't each copy that code:
- absolute-URL `@import`s are hoisted without duplicates, and relative
ones inlined in order
  - protocol-relative imports stay external
  - bare `@import`s resolve relative to the importing file
- `layer()`, media and supports attributes of nested imports are chained
- `url()` is rewritten to the emitted asset; unresolvable `url()`s and
`url()` in `@supports` are kept unchanged
  - legacy nesting is flattened
  - output is minified
- `@import` cycles and modules shared between chunks keep their order
(most fixtures already encode the expected position in `content: 'N'`)
- **Errors:** after an invalid rule, the valid rules around it are kept.
Issues stay as `issues/` snapshots, and `css-parse-error`'s
`.prettierignore` entry moves with the fixture.

**Kept as snapshot tests across the stack** (24), because the output
itself is under test or the Node.js-only execution harness can't express
them:

- browser runtime/chunk format: `runtime/*`, `workers/*`,
`basic/chunk_loading_global`, `basic/single-chunk-entry`, `example`,
`evaluated_entrry`
- source maps and debug IDs: `source_maps/*`, `debug-ids/*`
- transforms the execution harness doesn't configure:
`swc_transforms/*`, `emotion`, `styled_components`
- harness-specific setup: `comptime/define`, `env/env`,
`dynamic-request/very-dynamic`, and the Web Worker cases of
`imports/ignore-comments`

Known gap, not addressed here: the snapshot harness doesn't discover
`swc_transforms/mono_transforms`. It uses a custom entry and has no
`input/index.*`. This predates the stack.

Verified on this layer, the top of the stack: 442 execution and 24
snapshot tests pass. Prettier is clean.

<!-- NEXT_JS_LLM -->


<!-- fleet a144b357-ef78-451c-8231-ec24b8a59cec -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant