Skip to content

fix(rolldown): always externalize node:* regardless of CONFIG.external - #163

Merged
vitbokisch merged 2 commits into
mainfrom
fix/rolldown-node-external-always
Jun 25, 2026
Merged

fix(rolldown): always externalize node:* regardless of CONFIG.external#163
vitbokisch merged 2 commits into
mainfrom
fix/rolldown-node-external-always

Conversation

@vitbokisch

Copy link
Copy Markdown
Member

The robustness gap

`node:*` externalization was added to `baseConfig.external` — a user-overridable default. But `deepMerge` replaces arrays wholesale, so any package that sets its own `external` in `vl-tools.config.mjs` (e.g. to add `echarts` subpaths) silently dropped the `/^node:/` default and started emitting `UNRESOLVED_IMPORT` warnings on every `node:fs`/`node:path`/etc. import again.

Reproduced: a probe package with `external: [/^echarts/]` → 1 `node:` UNRESOLVED_IMPORT warning (was 0 with the default). It's not charts-specific — any package overriding `external` for any reason re-opens the whole class.

The fix

A `node:` import is never a real module a library could bundle — it's a hard invariant, not a "default you might override." Moved it out of the overridable `baseConfig.external` into an `ALWAYS_EXTERNAL` constant that `resolveExternals` spreads in unconditionally:

```ts
const ALWAYS_EXTERNAL: (string | RegExp)[] = [/^node:/]

const resolveExternals = () =>
CONFIG.bundleAll ? []
: [...PKG.externalDependencies, ...ALWAYS_EXTERNAL, ...CONFIG.external].map(expandExternal)
```

A package's `external` override is still honored (their entries are added); it just can't drop node-builtin externalization anymore.

Regression lock (proven)

Two unit tests — package overrides `CONFIG.external`; `CONFIG.external` empty. Verified by reverting the fix:

node:fs external?
Fix in place ✅ both tests pass
Fix reverted ❌ both tests fail

Plus end-to-end: full monorepo build emits 0 `node:*` UNRESOLVED_IMPORT warnings, probe package overriding `external` keeps `node:fs` external.

On the SOURCEMAP_BROKEN concern (investigated, no change)

The "isolated DTS path didn't get the sourcemap-off treatment" claim does not reproduce. Both the isolated and grouped DTS paths consume configs from `createDtsConfig`, which already sets `dts({ sourcemap: false })`. A single-entry (isolated-path) build emits 0 SOURCEMAP_BROKEN warnings. The 8 warnings the reporter saw were likely on a pre-#159 build. No change needed.

e2e

  • ✅ 587 tests pass (+2 new)
  • ✅ Lint + typecheck clean across all 10 packages
  • ✅ All 10 packages build, 0 node:* warnings
  • ✅ Zero leaked `__dts_tmp*` dirs

Versioning

`@vitus-labs/tools-rolldown`: patch → 2.6.3.

🤖 Generated with Claude Code

node:* was in baseConfig.external (user-overridable). deepMerge replaces
arrays wholesale, so any package setting its own `external` (e.g. echarts
subpaths) silently dropped /^node:/ and re-emitted UNRESOLVED_IMPORT
warnings on every node:fs/node:path import.

A node: import is never a real module a library could bundle — it's a
hard invariant, not a default. Moved it out of baseConfig.external into
an ALWAYS_EXTERNAL constant that resolveExternals spreads in
unconditionally. Package `external` overrides are still honored; they
just can't drop node-builtin externalization anymore.

Regression-locked: 2 unit tests (override external; empty external) —
both fail without the fix, pass with. Full monorepo build emits 0
node:* UNRESOLVED_IMPORT warnings.

Also investigated the SOURCEMAP_BROKEN-on-isolated-path concern: does
NOT reproduce. Both DTS paths share createDtsConfig which already has
dts({ sourcemap: false }); single-entry build emits 0 SOURCEMAP_BROKEN.
No change needed.

e2e: 587 tests (+2), lint+typecheck+build clean, no leaks.
@vitbokisch
vitbokisch enabled auto-merge (squash) June 25, 2026 14:23
@vitbokisch
vitbokisch merged commit 507f10d into main Jun 25, 2026
8 checks passed
@vitbokisch
vitbokisch deleted the fix/rolldown-node-external-always branch June 25, 2026 14:26
@github-actions github-actions Bot mentioned this pull request Jun 25, 2026
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.

1 participant