fix: align module external remapping with webpack#13802
fix: align module external remapping with webpack#13802JSerFeng wants to merge 5 commits intoweb-infra-dev:mainfrom
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d3eddc5975
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Aligns Rspack’s module externals behavior with webpack when optimization.mangleExports is enabled by generating a remapped namespace object for external property access, preventing mangled bundle-local names from being used against real external exports. Adds targeted Rust unit coverage and new rspack-test fixtures to prevent regressions (incl. #13733).
Changes:
- Implement module-external namespace remapping logic in
rspack_coreexternal module codegen. - Add Rust unit tests validating remapping behavior (including nested exports).
- Add regression fixtures for
output-module/issue-13733and migratednode/output-module-externalcases.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/rspack_core/src/external_module.rs | Implements module-external remapping logic and adds focused Rust unit tests. |
| tests/rspack-test/configCases/output-module/issue-13733/test.config.js | Adds bundle selection for the new regression case. |
| tests/rspack-test/configCases/output-module/issue-13733/rspack.config.js | Reproducer config for #13733 (module output + mangleExports + chunk merge). |
| tests/rspack-test/configCases/output-module/issue-13733/index.js | Asserts emitted bundle output and runtime behavior stay correct. |
| tests/rspack-test/configCases/output-module/issue-13733/a.js | Minimal ESM import that exercises named module externals. |
| tests/rspack-test/configCases/output-module/issue-13733/b.js | Async import side module to force chunk merge scenario. |
| tests/rspack-test/configCases/node/output-module-external/test.filter.js | Filters test execution based on Node support for createRequire. |
| tests/rspack-test/configCases/node/output-module-external/test.config.js | Adds bundle selection for migrated node builtin externals case. |
| tests/rspack-test/configCases/node/output-module-external/stream.js | Helper module for stream assertion in the require-based test. |
| tests/rspack-test/configCases/node/output-module-external/rspack.config.js | Configures outputModule build and injects NODE_VERSION. |
| tests/rspack-test/configCases/node/output-module-external/require.js | Migrated require-based node builtin external coverage. |
| tests/rspack-test/configCases/node/output-module-external/import.js | Migrated dynamic-import-based node builtin external coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let return_x = runtime_template.returning_function("x", ""); | ||
| format!( | ||
| "var x = {create_namespace_object};\nvar y = {};", | ||
| runtime_template.returning_function(&return_x, "x") | ||
| ) |
There was a problem hiding this comment.
The remapping init code introduces very generic top-level identifiers (var x, var y). When this ExternalModule is concatenated (i.e. concatenation_scope is active), multiple concatenated modules share one scope, so these names can collide with other concatenated modules or other remapped externals and cause incorrect runtime behavior. Consider generating helper identifiers that are unique per external (e.g. derived from ident), or avoid introducing named variables by inlining the helper functions / using an IIFE, and update render_module_external_remapping to reference those unique helper names.
| let exports_info = compilation | ||
| .exports_info_artifact | ||
| .get_prefetched_exports_info(&self.identifier(), PrefetchExportsInfoMode::Full); |
There was a problem hiding this comment.
PrefetchExportsInfoMode::Full is documented as “should only be used in hash calculation” and it recursively prefetches nested exports info. Using it here in the module external codegen path can add noticeable overhead for builds with many externals. If nested exports data is only needed for the subset of exports that require remapping, consider switching to Default and lazily prefetching nested exports_info only when export_info.exports_info() is present (or otherwise limiting the prefetch scope), or add a justification comment if Full is required here.
| let exports_info = compilation | |
| .exports_info_artifact | |
| .get_prefetched_exports_info(&self.identifier(), PrefetchExportsInfoMode::Full); | |
| // Avoid recursively prefetching nested exports info in the external module | |
| // codegen path; `Full` is intended for hash calculation and can add | |
| // noticeable overhead for builds with many externals. | |
| let exports_info = compilation | |
| .exports_info_artifact | |
| .get_prefetched_exports_info(&self.identifier(), PrefetchExportsInfoMode::Default); |
| for (const [request, imported] of Object.entries(builtinImports)) { | ||
| expect(imported).toBeDefined(); | ||
| expect(content).toMatch( | ||
| new RegExp(`import\\(\\s*['']${escapeRegExp(request)}['']\\s*\\)`) |
There was a problem hiding this comment.
The regexp used to assert presence of import(...) only matches single quotes (['']). Since the bundle output may use double quotes (especially after minification / formatting), this can make the test flaky. Consider using a quote class like ['"] (or otherwise matching both quote styles) so the assertion is robust across output formatting changes.
| new RegExp(`import\\(\\s*['']${escapeRegExp(request)}['']\\s*\\)`) | |
| new RegExp(`import\\(\\s*['"]${escapeRegExp(request)}['"]\\s*\\)`) |
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
moduleexternal remapping with webpack by generating a remapped namespace object instead of using mangled bundle-local names as external property accessrspack_coreoutput-module/issue-13733reproducer and a migrated webpacknode/output-module-externalcaseRoot cause
When a
moduleexternal fell back to namespace access in module output, Rspack mixed two different name domains:used_nameproduced by export manglingThat made namespace property access use mangled names like
ns.ainstead of the real external export name such asns.EventEmitter.Impact
This keeps
moduleexternals compatible withoptimization.mangleExports, concatenation, and namespace fallback behavior, while matching webpack's remapping strategy more closely.Fixes #13733.
Validation
cargo test -p rspack_core module_external_remapping --lib -- --nocapturecargo test -p rspack_plugin_javascript --lib -- --nocapturecargo test -p rspack_plugin_esm_library --lib -- --nocapturecargo fmt --all --checkgit diff --checknode --check tests/rspack-test/configCases/node/output-module-external/import.jsnode --check tests/rspack-test/configCases/node/output-module-external/require.jsnode --check tests/rspack-test/configCases/node/output-module-external/rspack.config.jsnode --check tests/rspack-test/configCases/node/output-module-external/test.config.jsnode --check tests/rspack-test/configCases/node/output-module-external/test.filter.jsnode --check tests/rspack-test/configCases/node/output-module-external/stream.jsnode -e "console.log(require('./tests/rspack-test/configCases/node/output-module-external/test.filter.js')())"Notes
rspack-testconfig cases locally because this environment does not havepnpmorcorepack. The migrated JS fixture was syntax-checked instead.