You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Starting with @sveltejs/adapter-node@5.5.6, a production build can contain a top-level static import of a package that is not installed anywhere, in build/instrumentation.server.js — the very first thing build/index.js imports. The server never starts:
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@babel/preset-typescript' imported from /app/build/instrumentation.server.js
at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9)
at packageResolve (node:internal/modules/esm/resolve:768:81)
The offending line in the emitted bundle is a bare side-effect import:
Nothing in the dependency tree installs @babel/preset-typescript. The import comes from a require that CommonJS code guards behind a catch block and never reaches at runtime — @rollup/plugin-commonjs hoists it to a static ESM import, tree-shaking then removes the surrounding code but keeps the now-dangling side-effect import, and nodeResolve leaves it external because it cannot resolve it. Because adapter 5.5.5 moved this file to the build root and put it on the boot path, an import that was previously inert now kills the process before it listens.
This took down one of our deployments: the image builds fine, the container starts, runs its migrations, and then exits on the trace above with nothing else logged.
Reproduction
Minimal app, verified from scratch. Four files plus a package.json:
The @sentry/vite-plugin override is only there to hold the transitive dependency at the version that still pulls @babel/core (5.3.0, via @sentry/bundler-plugin-core); 5.4.0 swapped that out, which incidentally hides the bug. Sentry is not special here — any dependency reachable from instrumentation.server.js that contains a guarded require of an uninstalled package will do.
Bisect
Same app, same lockfile, only the adapter version varying:
adapter-node
instrumentation output path
files with the dangling import
node build/index.js
5.5.4
build/server/instrumentation.server.js
0
boots
5.5.5
build/instrumentation.server.js
0
boots
5.5.6
build/instrumentation.server.js
2
ERR_MODULE_NOT_FOUND
5.5.7
build/instrumentation.server.js
2
ERR_MODULE_NOT_FOUND
So it is 5.5.6, not the 5.5.5 restructure, that introduces the dangling import. 5.5.5 (#16069) moved the file to the build root and onto the boot path; 5.5.6 added the manualChunks option in #16115 — the fix for the #16092 top-level-await deadlock:
Forcing every module under the Vite server directory into its own chunk changes where rollup places external imports, and the dangling side-effect import ends up hoisted into the entry chunks — including the one the server boots from.
This is the whole of the functional 5.5.5 → 5.5.6 diff: the only other change in index.js is extracting ${tmp}/entries into an entries local. So manualChunks is not merely correlated with the regression, it is the sole candidate.
Why the import exists at all
The chain in the repro is instrumentation.server.js → @sentry/sveltekit (whose server entry re-exports the sentrySvelteKit Vite plugin) → @sentry/vite-plugin → @sentry/bundler-plugin-core → @babel/core. Inside Babel:
@babel/core declares @babel/preset-typescript neither as a dependency nor as a peer — it is a soft runtime lookup that is legitimately absent, which is why the require is guarded. The adapter marks only pkg.dependencies as external, so everything else is bundled, and this guarded require becomes a hard static dependency of the boot entry.
Worth noting the emitted import targets a .json file with no import attribute, so even installing@babel/preset-typescript would not make it loadable — under Node ESM it then fails with ERR_IMPORT_ATTRIBUTE_MISSING: Module ... needs an import attribute of "type: json" (verified separately). There is no user-side dependency fix available; the emitted import is invalid regardless of what is installed.
Suggested directions
Don't let unresolved externals reach the instrumentation entry. Since it is --imported before anything else, an unresolvable specifier there is always fatal. Failing the build (or warning loudly) when the instrumentation chunk has an external that nodeResolve could not resolve would turn a silent production crash into a build error.
Revisit the manualChunks shape from fix: avoid circular dependency #16115. The deadlock fix is doing more than retaining file structure — it is changing external-import placement. Narrowing it, or excluding the instrumentation entry from it, may address both.
Silent and total: the build succeeds, the image builds, and the container exits at boot. Nothing in the build output flags an external that resolves nowhere.
Describe the bug
Starting with
@sveltejs/adapter-node@5.5.6, a production build can contain a top-level static import of a package that is not installed anywhere, inbuild/instrumentation.server.js— the very first thingbuild/index.jsimports. The server never starts:The offending line in the emitted bundle is a bare side-effect import:
Nothing in the dependency tree installs
@babel/preset-typescript. The import comes from arequirethat CommonJS code guards behind acatchblock and never reaches at runtime —@rollup/plugin-commonjshoists it to a static ESM import, tree-shaking then removes the surrounding code but keeps the now-dangling side-effect import, andnodeResolveleaves it external because it cannot resolve it. Because adapter 5.5.5 moved this file to the build root and put it on the boot path, an import that was previously inert now kills the process before it listens.This took down one of our deployments: the image builds fine, the container starts, runs its migrations, and then exits on the trace above with nothing else logged.
Reproduction
Minimal app, verified from scratch. Four files plus a
package.json:package.json{ "name": "repro-adapter-node-babel", "private": true, "type": "module", "scripts": { "build": "vite build" }, "overrides": { "@sentry/vite-plugin": "5.3.0" }, "devDependencies": { "@sentry/sveltekit": "10.63.0", "@sveltejs/adapter-node": "5.5.6", "@sveltejs/kit": "2.70.2", "@sveltejs/vite-plugin-svelte": "^7.2.0", "svelte": "^5.56.4", "vite": "^8.1.3" } }svelte.config.jsvite.config.jssrc/instrumentation.server.jsPlus a trivial
src/app.htmlandsrc/routes/+page.svelte. Then:The
@sentry/vite-pluginoverride is only there to hold the transitive dependency at the version that still pulls@babel/core(5.3.0, via@sentry/bundler-plugin-core); 5.4.0 swapped that out, which incidentally hides the bug. Sentry is not special here — any dependency reachable frominstrumentation.server.jsthat contains a guardedrequireof an uninstalled package will do.Bisect
Same app, same lockfile, only the adapter version varying:
node build/index.jsbuild/server/instrumentation.server.jsbuild/instrumentation.server.jsbuild/instrumentation.server.jsbuild/instrumentation.server.jsSo it is 5.5.6, not the 5.5.5 restructure, that introduces the dangling import. 5.5.5 (#16069) moved the file to the build root and onto the boot path; 5.5.6 added the
manualChunksoption in #16115 — the fix for the #16092 top-level-await deadlock:Forcing every module under the Vite server directory into its own chunk changes where rollup places external imports, and the dangling side-effect import ends up hoisted into the entry chunks — including the one the server boots from.
This is the whole of the functional 5.5.5 → 5.5.6 diff: the only other change in
index.jsis extracting${tmp}/entriesinto anentrieslocal. SomanualChunksis not merely correlated with the regression, it is the sole candidate.Why the import exists at all
The chain in the repro is
instrumentation.server.js→@sentry/sveltekit(whose server entry re-exports thesentrySvelteKitVite plugin) →@sentry/vite-plugin→@sentry/bundler-plugin-core→@babel/core. Inside Babel:@babel/coredeclares@babel/preset-typescriptneither as a dependency nor as a peer — it is a soft runtime lookup that is legitimately absent, which is why therequireis guarded. The adapter marks onlypkg.dependenciesas external, so everything else is bundled, and this guardedrequirebecomes a hard static dependency of the boot entry.Worth noting the emitted import targets a
.jsonfile with no import attribute, so even installing@babel/preset-typescriptwould not make it loadable — under Node ESM it then fails withERR_IMPORT_ATTRIBUTE_MISSING: Module ... needs an import attribute of "type: json"(verified separately). There is no user-side dependency fix available; the emitted import is invalid regardless of what is installed.Suggested directions
--imported before anything else, an unresolvable specifier there is always fatal. Failing the build (or warning loudly) when the instrumentation chunk has an external thatnodeResolvecould not resolve would turn a silent production crash into a build error.manualChunksshape from fix: avoid circular dependency #16115. The deadlock fix is doing more than retaining file structure — it is changing external-import placement. Narrowing it, or excluding the instrumentation entry from it, may address both.build/index.jswith an instrumentation file present; that gap is exactly why a fatal boot regression shipped in a patch release. Landing a boot test looks more valuable than any single fix here.Related
$env/dynamic/private(regression from #16069) #16092 / fix: avoid circular dependency #16115 — the top-level-await deadlock and itsmanualChunksfix (5.5.6), which is what this regression rides in on@opentelemetry/apito prevent chunk colocation #16302 — externalizing@opentelemetry/api; same "instrumentation entry gets contaminated by shared chunking" familySeverity
Silent and total: the build succeeds, the image builds, and the container exits at boot. Nothing in the build output flags an external that resolves nowhere.
System info