Skip to content

fix(bundle): resolution fixes - #1091

Open
aryasaatvik wants to merge 3 commits into
alchemy-run:mainfrom
aryasaatvik:fix/bundle-resolution-fixes
Open

fix(bundle): resolution fixes#1091
aryasaatvik wants to merge 3 commits into
alchemy-run:mainfrom
aryasaatvik:fix/bundle-resolution-fixes

Conversation

@aryasaatvik

@aryasaatvik aryasaatvik commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Two fixes to what the bundler resolves.

cloudflare:workers is supplied by workerd and has no filesystem existence, but rolldown resolves imports before tree-shaking — so any bundled graph referencing it fails with [UNRESOLVED_IMPORT] even where a runtime guard makes the import dead code. The CLI's dev executable reaches cloudflare_workers.ts (whose guarded import("cloudflare:workers") falls back to a stub off workerd), and a cross-provider Lambda can reach it too — a Lambda can never resolve the module, so keeping it external lets the runtime fallback handle it. Worker bundles already get this from the cloudflare rolldown plugin.

  defineConfig({
    entry: ["bin/exec.ts"],
+   external: ["cloudflare:workers"],

Alchemy's SDK dependencies (@distilled.cloud/*) resolve from alchemy's own installation instead of the consumer's package graph, which only worked when the package manager happened to hoist them:

const selfResolvePlugin = (): rolldown.Plugin => ({
  name: "alchemy:self-resolve",
  resolveId: {
    filter: { id: /^@distilled\.cloud\// },
    handler(source) {
      try {
        return fileURLToPath(import.meta.resolve(source));
      } catch {
        return null;
      }
    },
  },
});

Peers like Effect are deliberately not handled — they must resolve from the consumer so the runtime has a single Effect identity. An id alchemy's own install cannot resolve falls through to normal resolution.

workerd supplies `cloudflare:workers` at runtime; it has no filesystem
existence. Rolldown resolves imports before tree-shaking, so any bundled
graph referencing it trips [UNRESOLVED_IMPORT] even where a runtime guard
makes the import dead code.

The CLI's dev executable reaches `cloudflare_workers.ts`, whose guarded
`import("cloudflare:workers")` falls back to a stub off workerd. A
cross-provider Lambda can reach it too, and a Lambda can never resolve the
module — keeping it external lets the runtime fallback handle it.
A virtual entry has no filesystem location, and Alchemy's bundled runtime
entries reference private `@distilled.cloud/*` packages the consumer never
depends on. Resolving either through the consumer's package graph only
worked when the package manager happened to hoist those dependencies.

Peer dependencies such as Effect are deliberately left alone: they must
keep resolving from the consumer's graph so the runtime has a single
Effect identity.
@aryasaatvik
aryasaatvik force-pushed the fix/bundle-resolution-fixes branch 2 times, most recently from 9047a91 to 9d298bd Compare August 4, 2026 21:11
@aryasaatvik aryasaatvik changed the title fix(bundle): resolution and rebuild-correctness fixes fix(bundle): resolution fixes Aug 4, 2026
// dynamic import of workerd's virtual module. A Lambda can never
// resolve it; keep the import external so the runtime fallback
// handles it instead of tripping [UNRESOLVED_IMPORT] before DCE.
if (moduleId === "cloudflare:workers") return true;

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.

This makes sense

Comment thread packages/alchemy/src/Bundle/Bundle.ts Outdated
const ENTRY_PREFIX = "\0virtual:alchemy-entry:";
// oxlint-disable-next-line no-control-regex
const ENTRY_REGEX = /^\0virtual:alchemy-entry:/;
const ALCHEMY_PRIVATE_DEPENDENCY_REGEX = /^@distilled\.cloud\//;

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.

These are not all private. All the distilled SDKs are in there. What motivated this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair — "private" is the wrong word; they're published. The intent is provenance, not privacy: resolve @distilled.cloud/* from alchemy's own install rather than the consumer's package graph.

What motivated it: I consume alchemy in another monorepo from a packed tarball built off a worktree carrying my open PRs plus distilled patches that aren't on npm yet. (bun link isn't workable there — linking pulls alchemy's whole source tree into the dev server's watch graph, >10k files, and the process hits the macOS open-file limit.) The patched SDK builds ride inside alchemy's install, so bundled user code has to resolve @distilled.cloud/* to those copies — the consumer graph either doesn't have them or hoists the unpatched registry versions.

It's not specific to that workflow though: imports reached from the virtual entry have no importer path, so without the plugin resolution falls back to the consumer graph and only works when the package manager happens to hoist — and the versions it hoists aren't necessarily the ones alchemy's install was built against.

The @distilled.cloud packages are published SDKs, not private. The plugin
is about provenance — bundled user code gets exactly the SDK versions
this installation of alchemy carries — so name and comment now say that.
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.

2 participants