Skip to content

fix(externals): correct external type for aliased node builtin externals#13627

Merged
JSerFeng merged 8 commits intomainfrom
fix/esm-node-target-alias-external
Apr 8, 2026
Merged

fix(externals): correct external type for aliased node builtin externals#13627
JSerFeng merged 8 commits intomainfrom
fix/esm-node-target-alias-external

Conversation

@JSerFeng
Copy link
Copy Markdown
Contributor

@JSerFeng JSerFeng commented Apr 7, 2026

Summary

  • Refactors EsmNodeTargetPlugin from a factorize-phase ExternalsPlugin to an after_factorize hook
  • Fixes aliased externals like externals: { 'node:fs': 'node:path' } where the old factorize hook would override the user's alias by matching first (via is_node_builtin) and using the original request
  • Adds set_external_type() to ExternalModule which updates the type and recomputes the identifier

The new after_factorize (stage=-10) inspects already-created external modules: if the request is a node builtin, it sets the correct type based on the dependency category (ESM → module-import, CJS → node-commonjs). This runs before EsmLibraryPlugin's after_factorize (stage=0) which depends on the correct type for set_id.

Test plan

  • Added esm-node-target-alias test case: verifies externals: { 'node:fs': 'node:path' } generates import { resolve } from "node:path" (not "node:fs")
  • Existing ESM externals tests pass (89 tests)

Refactor EsmNodeTargetPlugin from a factorize-phase ExternalsPlugin to
an after_factorize hook that corrects the external type for node builtin
externals. This fixes aliased externals like `{ 'node:fs': 'node:path' }`
where the previous factorize hook would override the user's alias by
matching first and using the original request.

The new approach:
- Uses after_factorize (stage=-10) to inspect already-created external
  modules and fix their type based on dependency category
- ESM dependencies get "module-import", non-ESM get "node-commonjs"
- Runs before EsmLibraryPlugin's after_factorize (stage=0) which
  depends on the correct type for set_id
Copilot AI review requested due to automatic review settings April 7, 2026 07:25
@JSerFeng JSerFeng requested a review from LingyuCoder as a code owner April 7, 2026 07:25
@github-actions github-actions Bot added release: bug fix release: bug related release(mr only) team The issue/pr is created by the member of Rspack. labels Apr 7, 2026
Copy link
Copy Markdown
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

Note

Copilot was unable to run its full agentic suite in this review.

Fixes incorrect external type handling for aliased Node builtin externals (e.g. externals: { 'node:fs': 'node:path' }) by moving the Node-builtin adjustment to an after_factorize hook and adding a way to update an ExternalModule’s external type while keeping its identifier consistent.

Changes:

  • Refactor EsmNodeTargetPlugin from an ExternalsPlugin factorize-phase approach to a NormalModuleFactory.after_factorize hook (stage -10).
  • Add ExternalModule::set_external_type() to update the type and recompute the module identifier.
  • Add esm-node-target-alias test case + snapshot to verify aliased builtin requests produce correct ESM imports.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/rspack-test/esmOutputCases/externals/esm-node-target-alias/rspack.config.js Adds a repro config that aliases a Node builtin external (node:fsnode:path).
tests/rspack-test/esmOutputCases/externals/esm-node-target-alias/index.js Adds assertions ensuring the aliased request is honored and the external is emitted as ESM import.
tests/rspack-test/esmOutputCases/externals/esm-node-target-alias/snapshots/esm.snap.txt Snapshot verifying output imports from node:path (aliased request), not node:fs.
crates/rspack_plugin_rslib/src/plugin.rs Switches to applying the new EsmNodeTargetPlugin implementation.
crates/rspack_plugin_externals/src/lib.rs Re-exports EsmNodeTargetPlugin instead of the previous factory function.
crates/rspack_plugin_externals/src/esm_node_target_plugin.rs Implements new after_factorize hook to correct external type on already-created external modules.
crates/rspack_core/src/external_module.rs Adds set_external_type() that recomputes the module identifier after changing external type.
crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs Updates builtin plugin wiring to construct/box EsmNodeTargetPlugin.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rspack_plugin_externals/src/esm_node_target_plugin.rs
Comment thread crates/rspack_plugin_externals/src/lib.rs
Comment thread crates/rspack_core/src/external_module.rs
Comment thread crates/rspack_core/src/external_module.rs Outdated
Comment thread crates/rspack_core/src/external_module.rs Outdated
Comment thread crates/rspack_plugin_externals/src/esm_node_target_plugin.rs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 7, 2026

Rsdoctor Bundle Diff Analysis

Found 6 projects in monorepo, 6 projects with changes.

📊 Quick Summary
Project Total Size Change
popular-libs 1.7 MB -
react-10k 5.7 MB -
react-1k 826.9 KB -
react-5k 2.7 MB -
rome 984.1 KB -
ui-components 5.0 MB -
📋 Detailed Reports (Click to expand)

📁 popular-libs

Path: ../build-tools-performance/cases/popular-libs/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 1.7 MB - -
📄 JavaScript 1.7 MB - -
🎨 CSS 0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 react-10k

Path: ../build-tools-performance/cases/react-10k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 5.7 MB - -
📄 JavaScript 5.7 MB - -
🎨 CSS 21.0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 react-1k

Path: ../build-tools-performance/cases/react-1k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 826.9 KB - -
📄 JavaScript 826.9 KB - -
🎨 CSS 0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 react-5k

Path: ../build-tools-performance/cases/react-5k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 2.7 MB - -
📄 JavaScript 2.7 MB - -
🎨 CSS 21.0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 rome

Path: ../build-tools-performance/cases/rome/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 984.1 KB - -
📄 JavaScript 984.1 KB - -
🎨 CSS 0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 ui-components

Path: ../build-tools-performance/cases/ui-components/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 5.0 MB - -
📄 JavaScript 4.7 MB - -
🎨 CSS 291.6 KB - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

Generated by Rsdoctor GitHub Action

Narrow the after_factorize condition to only fire when
dep.category != "esm" && external_type == "module", matching
the original intent. Add CJS require test case to verify the
downgrade path.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 7, 2026

Merging this PR will degrade performance by 2.63%

⚡ 2 improved benchmarks
❌ 2 regressed benchmarks
✅ 14 untouched benchmarks
⏩ 19 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation rust@create_chunk_ids 10.9 ms 10.5 ms +3.18%
Simulation rust@concatenate_module_code_generation 149.6 ms 147.2 ms +1.63%
Simulation rust@persistent_cache_restore_after_single_file_change@basic-react-development 27.4 ms 27.8 ms -1.43%
Simulation rust@persistent_cache_restore@basic-react-development 26.1 ms 26.8 ms -2.63%

Comparing fix/esm-node-target-alias-external (43e8341) with main (f15f7f4)

Open in CodSpeed

Footnotes

  1. 19 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 7, 2026

📦 Binary Size-limit

Comparing 43e8341 to docs: update import for React Refresh plugin (#13632) by neverland

🎉 Size decreased by 6.22KB from 49.35MB to 49.34MB (⬇️0.01%)

Comment thread crates/rspack_plugin_externals/src/esm_node_target_plugin.rs Outdated
JSerFeng added 5 commits April 7, 2026 16:49
…ndencyType

- Rename option from externalEsmNodeBuiltin to autoCjsNodeBuiltin
- Match both "module" and "module-import" external types via starts_with
- Use DependencyType::CjsRequire/CjsFullRequire instead of category check
- Add module-import CJS require test case
ExternalModule::lib_ident was returning user_request (the original
import specifier), which caused non-deterministic moduleIds when
multiple imports alias to the same external target. Now returns
request.primary() (the resolved target), making the output stable.
…ts in test

- set_external_type only sets the type field without recomputing id
- Revert lib_ident to use user_request (id stability to be addressed separately)
- Use different external targets in test to avoid non-deterministic dedup
@JSerFeng JSerFeng merged commit 013df8d into main Apr 8, 2026
37 checks passed
@JSerFeng JSerFeng deleted the fix/esm-node-target-alias-external branch April 8, 2026 03:50
hardfist pushed a commit that referenced this pull request Apr 9, 2026
…als (#13627)

* fix(externals): correct external type for aliased node builtin externals

Refactor EsmNodeTargetPlugin from a factorize-phase ExternalsPlugin to
an after_factorize hook that corrects the external type for node builtin
externals. This fixes aliased externals like `{ 'node:fs': 'node:path' }`
where the previous factorize hook would override the user's alias by
matching first and using the original request.

The new approach:
- Uses after_factorize (stage=-10) to inspect already-created external
  modules and fix their type based on dependency category
- ESM dependencies get "module-import", non-ESM get "node-commonjs"
- Runs before EsmLibraryPlugin's after_factorize (stage=0) which
  depends on the correct type for set_id

* fix: only downgrade module→node-commonjs for non-ESM deps

Narrow the after_factorize condition to only fire when
dep.category != "esm" && external_type == "module", matching
the original intent. Add CJS require test case to verify the
downgrade path.

* fix: add Default impl for EsmNodeTargetPlugin to satisfy clippy

* refactor: rename to autoCjsNodeBuiltin, match module-import, use DependencyType

- Rename option from externalEsmNodeBuiltin to autoCjsNodeBuiltin
- Match both "module" and "module-import" external types via starts_with
- Use DependencyType::CjsRequire/CjsFullRequire instead of category check
- Add module-import CJS require test case

* fix: use different external targets to avoid non-deterministic dedup

* fix: use request instead of user_request for ExternalModule lib_ident

ExternalModule::lib_ident was returning user_request (the original
import specifier), which caused non-deterministic moduleIds when
multiple imports alias to the same external target. Now returns
request.primary() (the resolved target), making the output stable.

* fix: simplify set_external_type, revert lib_ident, use distinct targets in test

- set_external_type only sets the type field without recomputing id
- Revert lib_ident to use user_request (id stability to be addressed separately)
- Use different external targets in test to avoid non-deterministic dedup

* test: update snapshots for existing external comment changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release: bug fix release: bug related release(mr only) team The issue/pr is created by the member of Rspack.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants