Skip to content

fix(adapter-node): fail the build on imports that resolve to no installed package - #16655

Closed
Stadly wants to merge 1 commit into
sveltejs:version-3from
Stadly:fix/adapter-node-unresolvable-imports
Closed

fix(adapter-node): fail the build on imports that resolve to no installed package#16655
Stadly wants to merge 1 commit into
sveltejs:version-3from
Stadly:fix/adapter-node-unresolvable-imports

Conversation

@Stadly

@Stadly Stadly commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Related to #16653 (does not close it — see "Scope" below)

adapter-node marks only pkg.dependencies as external, so everything else is bundled and any bare import left in the output has to resolve against a deployment that will not contain it. When it cannot, the failure is fatal and silent: the build succeeds, the image builds, and the server exits during module evaluation with ERR_MODULE_NOT_FOUND. For instrumentation.server.js that happens before the server can log anything at all — you get a container that starts and immediately dies.

This PR checks what was actually emitted rather than trusting the graph, and fails the build with the offending specifier and the chunk it came from.

Reproduction on version-3

A dependency that statically imports an unmet optional peer is enough. With a package in node_modules containing:

// esm-optional-dep/index.js
import 'not-installed-esm-package';

export function hello() {
  return 'hello';
}

imported from src/instrumentation.server.js, the build succeeds and produces:

// build/instrumentation.server.js
import "not-installed-esm-package";
$ node build/index.js
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'not-installed-esm-package'
  imported from .../build/instrumentation.server.js

Rolldown does warn — Module not found, treating it as an external dependency — but it is one line among the rest of the build output, and it doesn't stop a bundle that cannot start from being written. With this PR the build fails instead:

Error: The build contains imports that resolve to no installed package, so the server would
fail to start with ERR_MODULE_NOT_FOUND:
  not-installed-esm-package (imported by instrumentation.server.js)

If these are optional dependencies, add them to "dependencies" so they are installed
alongside the build, or stop importing them.

Scope, and what this does not fix

This came out of #16653, where adapter-node 5.5.6/5.5.7 emitted import '@babel/preset-typescript/package.json' into build/instrumentation.server.js and took down a production deployment. The root cause there is @rollup/plugin-commonjs hoisting a require from inside a catch clause into a top-level import — fix proposed in rollup/plugins#2018 — so this PR is not a fix for #16653. It is the guard that would have turned that incident into a build error.

Worth noting the two lines behave differently here. On main (adapter-node 5.5.7, rollup + @rollup/plugin-commonjs) that catch-clause require becomes a static import and the server cannot start. On version-3 (rolldown) the same input compiles to a lazy __require(...) and boots fine — which is why the reproduction above uses an ESM import instead, a path that is still live here.

The guard itself applies equally to both lines. I have it implemented and verified against main/5.5.7 as well, where it catches the original @babel/preset-typescript case directly; happy to open that as a companion PR if you'd want the fix on the released line too.

Notes on the implementation

  • Resolution is done with createRequire from the project root, which is where the deployment's node_modules will be. Anything unresolvable there is unresolvable at runtime.
  • Skips: emitted chunks (rolldown reports sibling chunks without a leading ./, so they must be filtered by fileName, not by prefix — this caused false positives on env.js and manifest.js-*.js in a first attempt), relative specifiers, and Node builtins via isBuiltin.
  • Covers dynamicImports as well as static imports.
  • bundle.write's result is bound to bundled rather than written, since written is already taken further up.

Verification

  • pnpm test in packages/adapter-node: 16/16 pass
  • pnpm lint: clean
  • pnpm check: 7 errors, identical with and without this change (all pre-existing, from unbuilt @sveltejs/kit types in my sparse checkout)
  • False positives: the equivalent guard was run against a large real application — ~130 devDependencies bundled, 2 production dependencies — and stays silent on a healthy build. Also verified silent on the reproduction above once the bad import is removed, with the server still serving normally.

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

The existing adapter-node tests don't build fixture apps, so there's no harness a test for this could hang off. #16305 proposes exactly that — a fixture app built with the real adapter and booted — and this guard would slot into it naturally. Happy to add one here if you'd rather not wait for that, or to rebase on it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

…lled package

Only `dependencies` are external, so everything else is bundled and any bare import left in the output has to resolve against a deployment that will not contain it. When it cannot, the failure is both fatal and silent: the build succeeds, the image builds, and the server exits during module evaluation with ERR_MODULE_NOT_FOUND. For `instrumentation.server.js` that happens before the server can log anything at all.

A dependency that statically imports an unmet optional peer is enough to trigger it. Rolldown reports "Module not found, treating it as an external dependency" and carries on, which is easy to lose among the other build output and does not stop a broken bundle from shipping.

Check what was actually emitted rather than trusting the graph: walk the written chunks' imports, skip our own chunks, relative specifiers and builtins, and resolve the rest from the project root. Anything that fails to resolve there cannot resolve at runtime either, so fail the build and name the specifier and the chunk it came from.
@pkg-svelte-dev

pkg-svelte-dev Bot commented Aug 5, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 36117ba:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/36117ba8b79499a23c074325b017e47715b17df4

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/16655

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 36117ba

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

This PR includes changesets to release 6 packages
Name Type
@sveltejs/adapter-node Patch
@sveltejs/adapter-static Patch
@sveltejs/adapter-cloudflare Patch
@sveltejs/adapter-netlify Patch
@sveltejs/adapter-vercel Patch
@sveltejs/kit 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

@vercel vercel Bot left a comment

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.

Additional Suggestion:

Build-time import validation uses CommonJS require.resolve, which falsely flags ESM-only dependencies that expose only an import export condition, breaking otherwise-valid builds.

Fix on Vercel

@Stadly
Stadly changed the base branch from main to version-3 August 5, 2026 08:02
@Stadly

Stadly commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Closing per the discussion in #16653: the premise doesn't hold up on this branch, since rolldown already warns when it can't resolve an import, and resolving every emitted import isn't something the adapter should take on. The root cause is rollup/plugins#2018.

@Stadly Stadly closed this Aug 5, 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