Skip to content

fix: dynamically resolve Turbopack external module mappings#1139

Merged
james-elicx merged 15 commits intomainfrom
james/turbopack-externals
Apr 14, 2026
Merged

fix: dynamically resolve Turbopack external module mappings#1139
james-elicx merged 15 commits intomainfrom
james/turbopack-externals

Conversation

@james-elicx
Copy link
Copy Markdown
Collaborator

@james-elicx james-elicx commented Feb 21, 2026

Disclaimer that this was 100% debugged and patched by Opus 4.6 agents in a different project that I was trying to get running on Workers. I've moved the patch back into this repo and added a regression test.

Some packages that are externalised by turbopack fail to be loaded/resolved on workerd, and esbuild can't statically analyse them properly when bundling from what I understand.

This PR does the following:

  • Discovers externalised modules through symlinked packages and trace files.
  • Re-maps the imports so that the bundler can statically analyse / handle them.
  • Regression test with shiki (which was the library I had an issue with in my own project).

Without this patch:

image

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 21, 2026

🦋 Changeset detected

Latest commit: a75fac1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@opennextjs/cloudflare Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Feb 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@opennextjs/cloudflare@1139

commit: a75fac1

@james-elicx james-elicx force-pushed the james/turbopack-externals branch from e91953c to f2932bd Compare February 21, 2026 20:07
@james-elicx james-elicx force-pushed the james/turbopack-externals branch from f2932bd to ea1d8ff Compare February 21, 2026 21:36
@james-elicx james-elicx force-pushed the james/turbopack-externals branch from a7f2cec to b98c6e1 Compare February 22, 2026 18:46
@james-elicx james-elicx marked this pull request as ready for review February 22, 2026 19:10
@james-elicx james-elicx requested a review from vicb February 22, 2026 19:10
@vicb
Copy link
Copy Markdown
Contributor

vicb commented Feb 22, 2026

Thanks for the PR James!

When I implemented support for OG, I knew it was brittle and would probably need to be handled in a better way.

I'll take a look later this week.

Copy link
Copy Markdown
Collaborator

@conico974 conico974 left a comment

Choose a reason for hiding this comment

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

LGTM but I think the test is in the wrong place

Comment thread examples/e2e/app-router/package.json Outdated
Comment thread .changeset/five-gifts-hope.md Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
* Discover Turbopack external module mappings by reading symlinks in .next/node_modules/.
*
* Turbopack externalizes packages listed in serverExternalPackages and creates hashed
* identifiers (e.g. "shiki-43d062b67f27bbdc") with symlinks in .next/node_modules/ pointing
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.

Thought while reading this JSDoc: can this be implemented as an ESBuild plugin?
i.e. when we try to resolve shiki-43d062b67f27bbdc, we would replace with the actual path

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I have a feeling it might not solve all the cases where this can happen with dynamic imports

Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
@james-elicx james-elicx changed the title fix: dynamically resolve Turbopack external module mappings for workerd fix: dynamically resolve Turbopack external module mappings Feb 27, 2026
@james-elicx james-elicx requested a review from vicb February 27, 2026 15:09
@gavinying
Copy link
Copy Markdown

is it planned to be merged? thx.

* statically analyze those hashed names. This function discovers the mappings so we can
* generate explicit switch cases for the bundler.
*
* @param filePath Absolute path to the Turbopack runtime file (e.g. `.next/server/chunks/ssr/[turbopack]_runtime.js`),
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.

.next/server/chunks/ssr/[turbopack]_runtime.js does not look like an absolute path. Could we pass the build options instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
Comment thread packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts Outdated
// E.g. for hashedName "shiki-43d062b67f27bbdc", this matches strings like
// "shiki-43d062b67f27bbdc/wasm" or "shiki-43d062b67f27bbdc/engine/javascript".
// The hashedName is escaped to safely use it as a literal in the regex pattern.
const escaped = hashedName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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.

nit: don't we have an util for that? (IIRC it's coming to latest v8)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I believe we still support Node v22, and it looks like RegExp.escape(...) is only available from v24 onwards according to mdn

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.

Iirc we have a util we use for i.e. path escaping (in aws)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh sorry I thought you meant a runtime util when I read V8. My bad. There's the cross platform path one that handles escaping.

@vicb
Copy link
Copy Markdown
Contributor

vicb commented Apr 13, 2026

Thanks for the updates James, I'll review tomorrow

Copy link
Copy Markdown
Contributor

@vicb vicb left a comment

Choose a reason for hiding this comment

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

LGTM, thanks James!

@james-elicx james-elicx merged commit 79b01b8 into main Apr 14, 2026
7 checks passed
@james-elicx james-elicx deleted the james/turbopack-externals branch April 14, 2026 18:07
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.

4 participants