Skip to content

feat: add output.strict option to control "use strict" directive emission#8489

Merged
shulaoda merged 8 commits intomainfrom
copilot/add-use-strict-to-cjs-builds
Mar 3, 2026
Merged

feat: add output.strict option to control "use strict" directive emission#8489
shulaoda merged 8 commits intomainfrom
copilot/add-use-strict-to-cjs-builds

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

  • Create StrictMode enum in Rust (rolldown_common)
  • Add strict field to BundlerOptions struct
  • Add strict field to NormalizedBundlerOptions struct
  • Map strict in prepare_build_context.rs
  • Add strict to BindingOutputOptions (NAPI bindings)
  • Map strict in normalize_binding_options.rs
  • Extract is_use_strict_directive helper in format utils
  • Update ecma_generator.rs to handle strict option using shared helper
  • Update ESM formatter to use shared is_use_strict_directive helper
  • Add strict to TypeScript OutputOptions interface with JSDoc
  • Add strict to validator schema
  • Pass strict through bindingify-output-options.ts
  • Add strict to ConfigVariant for Rust tests
  • Add test cases: strict_always_cjs, strict_never_cjs, strict_always_esm
  • Add deduplication test: strict_always_cjs_dedup
  • Replace From<String> with TryFrom<&str> for StrictMode; update binding normalization to propagate a proper napi::Error on invalid input
  • Rebuild rolldown to regenerate binding.d.cts
  • Update CLI snapshot test
Original prompt

This section details on the original issue you should resolve

<issue_title>"use strict" is not injected for CJS builds</issue_title>
<issue_description>I've seen #4607, but I feel it's important to bring this up again.

I think this decision is quite unfortunate.

Is the expectation that developers will add 'use strict' to their ESM/TS source code when dual-bundling as ESM/CJS? Including 'use strict' in ESM code seems counterproductive.

An alternative might be to add 'use strict' to CJS files by default when "type": "module" is used and bundling for CJS.</issue_description>

Comments on the Issue (you are @copilot in this section)

@sapphi-red We are going to follow esbuild's behavior, which is to respect tsconfig.json's `strict` (`alwaysStrict`) option. When that is implemented, you should be able to output `"use strict"` by setting `"alwaysStrict": true` in tsconfig.json. If the tsconfig.json has `include` & `exclude`, the entrypoint should be included in them.

For now, if you add `"use strict"` to all entrypoints manually, Rolldown should emit `"use strict"`. @shulaoda I’d like to clarify a few points: 1. Should this only apply when the output format is CJS? 2. Should this behavior only take effect when `tsconfig` is enabled? If the user does not enable `tsconfig`, should we avoid emitting `"use strict"`? 3. If the user enables `tsconfig: 'auto'` and we resolve the `tsconfig.json` from the current working directory, or if they explicitly specify `tsconfig: '..'`, and the `include` / `exclude` configuration excludes the entry file, should we still respect the `alwaysStrict` setting from that `tsconfig.json`? @sapphi-red > 1. Should this only apply when the output format is CJS?

This should apply to all formats (other than ESM as it already enables strict mode).

  1. Should this behavior only take effect when tsconfig is enabled? If the user does not enable tsconfig, should we avoid emitting "use strict"?

Yes. This behavior should only take effect when tsconfig is enabled.

  1. If the user enables tsconfig: 'auto' and we resolve the tsconfig.json from the current working directory, or if they explicitly specify tsconfig: '..', and the include / exclude configuration excludes the entry file, should we still respect the alwaysStrict setting from that tsconfig.json?

No. It should apply the alwaysStrict setting only when the other settings are respected. In other words, we should respect the include / exclude options.

@sapphi-red I noticed that TS 6.0 will deprecate `"alwaysStrict": false`. https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-beta/#deprecated:---alwaysstrict-false This means we should be treating any TS files as if they have `"use strict"` at the top of the file. @sapphi-red Maybe we should add [`output.strict`](https://rollupjs.org/configuration-options/#output-strict) option with `'auto' | boolean` instead. `'auto'` does not exist in Rollup and is the current behavior.

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@netlify
Copy link

netlify bot commented Feb 27, 2026

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit ea8a2f8
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69a69590bf5f3c00084887c4
😎 Deploy Preview https://deploy-preview-8489--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI changed the title [WIP] Add 'use strict' injection for CJS builds Add output.strict option to control "use strict" directive emission Feb 27, 2026
@hyf0 hyf0 assigned IWANABETHATGUY and unassigned shulaoda and Copilot Feb 27, 2026
Copilot AI and others added 3 commits March 3, 2026 10:56
Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
… source

Co-authored-by: IWANABETHATGUY <17974631+IWANABETHATGUY@users.noreply.github.com>
@shulaoda shulaoda force-pushed the copilot/add-use-strict-to-cjs-builds branch from 54e010d to 092b4a1 Compare March 3, 2026 02:57
@shulaoda shulaoda force-pushed the copilot/add-use-strict-to-cjs-builds branch from ffaa3eb to 828b684 Compare March 3, 2026 03:17
@shulaoda shulaoda changed the title Add output.strict option to control "use strict" directive emission feat: add output.strict option to control "use strict" directive emission Mar 3, 2026
Copy link
Member

@shulaoda shulaoda left a comment

Choose a reason for hiding this comment

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

I think we should move node test cases to rust side, I will do it later

@shulaoda shulaoda requested review from Copilot and sapphi-red March 3, 2026 03:26
@shulaoda shulaoda marked this pull request as ready for review March 3, 2026 03:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new output.strict option to control whether "use strict" directives are emitted in generated output (primarily affecting non-ESM formats), threading the option through TypeScript options, NAPI bindings, Rust options normalization, and code generation, with new integration tests and snapshot updates.

Changes:

  • Introduce StrictMode in rolldown_common and plumb strict through BundlerOptionsNormalizedBundlerOptions → build context preparation.
  • Expose strict in TypeScript OutputOptions, validator schema, and NAPI binding types/normalization.
  • Update code generation to apply strict behavior and reuse a shared is_use_strict_directive helper; add/refresh tests & snapshots.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/rolldown/tests/cli/snapshots/cli-e2e.test.ts.snap Updates CLI help snapshot to include --strict.
packages/rolldown/src/utils/validator.ts Adds strict validation (`boolean
packages/rolldown/src/utils/bindingify-output-options.ts Passes outputOptions.strict into binding output options.
packages/rolldown/src/options/output-options.ts Adds `strict?: boolean
packages/rolldown/src/binding.d.cts Regenerates binding types to include strict.
crates/rolldown_testing_config/src/config_variant.rs Adds strict to test config variants and formatting.
crates/rolldown_testing/_config.schema.json Extends test config JSON schema with StrictMode and strict fields.
crates/rolldown_common/src/lib.rs Re-exports StrictMode from bundler options types.
crates/rolldown_common/src/inner_bundler_options/types/strict_mode.rs Introduces StrictMode enum and conversions.
crates/rolldown_common/src/inner_bundler_options/types/normalized_bundler_options.rs Adds normalized strict: StrictMode field + default.
crates/rolldown_common/src/inner_bundler_options/types/mod.rs Registers the new strict_mode module.
crates/rolldown_common/src/inner_bundler_options/mod.rs Adds strict: Option<StrictMode> to BundlerOptions.
crates/rolldown_binding/src/utils/normalize_binding_options.rs Maps NAPI strict into Rust StrictMode.
crates/rolldown_binding/src/options/binding_output_options/mod.rs Exposes strict in NAPI output options (`boolean
crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap Snapshot updates for new strict-mode test fixtures.
crates/rolldown/tests/rolldown/misc/use_strict/strict_never_cjs/main.js Adds integration test input for strict: never (CJS).
crates/rolldown/tests/rolldown/misc/use_strict/strict_never_cjs/artifacts.snap Adds expected output snapshot for strict: never (CJS).
crates/rolldown/tests/rolldown/misc/use_strict/strict_never_cjs/_config.json Adds test config for strict: never (CJS).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_esm/main.js Adds integration test input for strict: always (ESM).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_esm/artifacts.snap Adds expected output snapshot for strict: always (ESM).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_esm/_config.json Adds test config for strict: always (ESM).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_cjs_dedup/main.js Adds dedup integration test input (entry already has 'use strict').
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_cjs_dedup/artifacts.snap Adds expected output snapshot for dedup behavior.
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_cjs_dedup/_config.json Adds test config for dedup case (strict: always, CJS).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_cjs/main.js Adds integration test input for strict: always (CJS).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_cjs/artifacts.snap Adds expected output snapshot for strict: always (CJS).
crates/rolldown/tests/rolldown/misc/use_strict/strict_always_cjs/_config.json Adds test config for strict: always (CJS).
crates/rolldown/src/utils/prepare_build_context.rs Threads strict into NormalizedBundlerOptions.
crates/rolldown/src/ecmascript/format/utils/mod.rs Adds shared is_use_strict_directive helper.
crates/rolldown/src/ecmascript/format/esm.rs Uses shared helper to filter "use strict" from ESM directives.
crates/rolldown/src/ecmascript/ecma_generator.rs Applies output.strict policy to directives during chunk instantiation.

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Benchmarks Rust

  • target: main(8fc5471)
  • pr: copilot/add-use-strict-to-cjs-builds(ea8a2f8)
group                                                        pr                                     target
-----                                                        --                                     ------
bundle/bundle@multi-duplicated-top-level-symbol              1.01     80.0±3.95ms        ? ?/sec    1.00     79.4±2.57ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-sourcemap    1.00     87.0±2.47ms        ? ?/sec    1.00     87.1±2.76ms        ? ?/sec
bundle/bundle@rome_ts                                        1.00    163.3±4.50ms        ? ?/sec    1.02    165.8±6.74ms        ? ?/sec
bundle/bundle@rome_ts-sourcemap                              1.00    183.8±8.10ms        ? ?/sec    1.01    186.5±3.86ms        ? ?/sec
bundle/bundle@threejs                                        1.00     74.7±2.71ms        ? ?/sec    1.03     76.6±2.28ms        ? ?/sec
bundle/bundle@threejs-sourcemap                              1.00     85.1±2.58ms        ? ?/sec    1.02     86.8±2.40ms        ? ?/sec
bundle/bundle@threejs10x                                     1.00   754.1±12.15ms        ? ?/sec    1.00   752.0±12.51ms        ? ?/sec
bundle/bundle@threejs10x-sourcemap                           1.02   885.9±12.46ms        ? ?/sec    1.00   870.4±13.32ms        ? ?/sec
scan/scan@rome_ts                                            1.03     73.1±1.54ms        ? ?/sec    1.00     71.1±1.46ms        ? ?/sec
scan/scan@threejs                                            1.04     26.6±1.72ms        ? ?/sec    1.00     25.6±0.48ms        ? ?/sec
scan/scan@threejs10x                                         1.01    256.4±4.44ms        ? ?/sec    1.00    254.4±3.38ms        ? ?/sec

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
@shulaoda shulaoda assigned sapphi-red and unassigned IWANABETHATGUY Mar 3, 2026
Copy link
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

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

LGTM. We should update https://rolldown.rs/in-depth/directives as well

Co-authored-by: 翠 <green@sapphi.red>
Signed-off-by: dalaoshu <165626830+shulaoda@users.noreply.github.com>
@shulaoda
Copy link
Member

shulaoda commented Mar 3, 2026

LGTM. We should update https://rolldown.rs/in-depth/directives as well

Do you mean we should update it for output.strict option?

@shulaoda shulaoda merged commit e9ddfbb into main Mar 3, 2026
34 checks passed
@shulaoda shulaoda deleted the copilot/add-use-strict-to-cjs-builds branch March 3, 2026 08:11
@minsoo-web
Copy link
Contributor

As a follow-up to this PR discussion, I opened #8535 to update docs/in-depth/directives for output.strict (#8532).

This was referenced Mar 4, 2026
shulaoda added a commit that referenced this pull request Mar 5, 2026
## [1.0.0-rc.7] - 2026-03-05

⚡ Smarter Code Generation Defaults
- DCE-only minification and smart constant inlining are now enabled by default
- Produces cleaner, smaller output bundles without requiring explicit configuration

💡 LLM-Friendly Bundle Analyzer Reports
- New markdown output format for the bundle analyzer plugin with bundle summaries, module graphs, dependency chains, and optimization suggestions
- Optimization suggestions now also recommend using the entriesAware option when common chunks contain modules only reachable from specific entries


### 💥 BREAKING CHANGES

- enable minify: 'dce-only' by default (#8465) by @IWANABETHATGUY
- settings `inlineConst: { mode: 'smart', pass: 1}`  by default (#8444) by @IWANABETHATGUY

### 🚀 Features

- binding: add original getter to BindingMagicString (#8533) by @IWANABETHATGUY
- native-magic-string: add `offset` property support (#8531) by @IWANABETHATGUY
- add `output.strict` option to control `"use strict"` directive emission (#8489) by @Copilot
- watch: expose `watcher.compareContentsForPolling` (#8526) by @hyf0
- watch: use new watcher to support watch mode (#8475) by @hyf0
- rust/watch: handle bulk-change (#8466) by @hyf0
- add LLM-friendly markdown output format to bundle analyzer plugin (#8242) by @IWANABETHATGUY

### 🐛 Bug Fixes

- expose `plugins` on `NormalizedInputOptions` for `buildStart` hook (#8521) by @Copilot
- only uppercase facade symbols in JSX preserve mode (#8519) by @IWANABETHATGUY
- binding: export BindingResult in generated dts header (#8537) by @minsoo-web
- pre-resolve paths option to avoid `invoke_sync` deadlock (#8518) by @IWANABETHATGUY
- remove debug-only jsx_preset and UntranspiledSyntaxError (#8511) by @IWANABETHATGUY
- apply `topLevelVar` to exported `const`/`let` declarations (#8507) by @IWANABETHATGUY
- rolldown_plugin_vite_web_worker_post: avoid replacing `new.target` (#8488) by @sapphi-red
- update copyright year to 2026 (#8486) by @maciekzygmunt

### 🚜 Refactor

- rust: use Oxc's SymbolFlags::ConstVariable instead of custom IsConst flag (#8543) by @Dunqing
- rust: remove FacadeScoping, use Scoping::create_symbol for facade symbols (#8540) by @Dunqing
- rust/watch: remove hacky `reset_closed_for_watch_mode` (#8530) by @hyf0
- binding: return &str instead of String in filename() getter (#8534) by @IWANABETHATGUY
- rust: remove old watch mode implementation (#8525) by @hyf0
- rust/watch: simply watch logic in the binding layer (#8516) by @hyf0
- rust/watch: tweak struct/function names (#8464) by @hyf0

### 📚 Documentation

- explain how external modules work in rolldown (#8457) by @sapphi-red
- add some diagrams using graphviz (#8499) by @sapphi-red
- use `vitepress-plugin-graphviz` (#8498) by @sapphi-red
- list s390x/ppc64le prebuilt binaries (#8495) by @crusty-voidzero
- fix error type for `RolldownBuild.generate` and others (#8490) by @sapphi-red

### ⚡ Performance

- string_wizard: reduce allocations and add ASCII fast paths (#8541) by @IWANABETHATGUY
- use IndexBitSet to replace IndexVec<XXXIdx, bool> for module/stmt inclusion tracking (#8503) by @IWANABETHATGUY
- plugin: use IndexBitSet to optimize skipped plugins checking (#8497) by @ShroXd
- rust/tla: skip compute_tla if there is no module use TLA (#8487) by @ShroXd

### 🧪 Testing

- node/watch: make watch tests run in concurrent and retry-able (#8512) by @hyf0
- add test case for static flag tree-shaking (#8476) by @IWANABETHATGUY
- migrate post-banner sourcemap-with-shebang to Rust (#8477) by @Copilot

### ⚙️ Miscellaneous Tasks

- vscode: `formatOnSave` for markdown files using oxc formatter (#8536) by @minsoo-web
- deps: update test262 submodule for tests (#8528) by @sapphi-red
- remove `retry` workaround from output paths test fixtures (#8520) by @Copilot
- docs: add Shuyuan Wang (h-a-n-a) and remove from acknowledgements (#8509) by @Copilot
- consolidate top_level_var test cases using configVariants (#8508) by @IWANABETHATGUY
- add s390x and ppc64le linux gnu targets (#8493) by @Brooooooklyn

### ◀️ Revert

- fix(rolldown): increase tokio blocking threads size for watch mode (#8517) by @hyf0

### ❤️ New Contributors

* @minsoo-web made their first contribution in [#8536](#8536)
* @crusty-voidzero made their first contribution in [#8495](#8495)
* @maciekzygmunt made their first contribution in [#8486](#8486)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"use strict" is not injected for CJS builds

6 participants