Skip to content

fix(server-runtime-injection): Bundle the ESM loader hook into one self-contained file - #26271

Closed
sentry-junior[bot] wants to merge 7 commits into
developfrom
fix/server-runtime-injection-self-contained-hook
Closed

sentry-junior[bot] wants to merge 7 commits into
developfrom
fix/server-runtime-injection-self-contained-hook

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Why: On Node < 24.13, register.ts passes build/esm/hook.js to Module.register() using a path computed at runtime. Turbopack includes that file in the Next.js server trace but treats it as an opaque asset and doesn't follow its imports. So the loader thread can't find build/esm/vendored/**, and also can't find @sentry/core's ESM build, which src/debug.ts imports. Registration then warns on every cold start. I reproduced this locally with the nextjs-16-standalone app (Next 16.4.3, Turbopack, pnpm, Node 22) and got the exact error from the issue. The standalone output contained build/esm/hook.js and nothing else from build/esm.

Instead of trying to make every tracer follow the hook's imports, the hook is now a single file that imports only node: builtins. Non-obvious decisions:

  • The hook bundles the real debug package instead of src/debug.ts, which imports @sentry/core. debugShim only exists because debug reads process.env at import time, which throws on Deno, and Module.register() never runs there. The Sentry logger couldn't print on the loader thread anyway, since that thread has its own globalThis. I checked this on Node 22: after debug.enable() on the main thread, debug.isEnabled() in a Module.register() loader still returns false. Now DEBUG=@apm-js-collab/* shows the hook's messages there. Its license notices (debug, ms, supports-color, has-flag) are added to THIRD-PARTY-LICENSES.txt.
  • The vendored transformer code is now duplicated: once inside hook.js and once in the preserved-modules chunks that register.js uses for the CJS _compile patch. The published package grows by about 1.6 MB, mostly the new hook.js.map. This package is server-only. At runtime nothing extra loads: the loader thread always parsed its own copy of the transformer.
  • The @sentry/node/import size-limit entry now measures only import-hook.mjs. hook.js runs on the separate loader thread, so bundling the two together made webpack count the transformer twice. Locally the entry measures 81.85 kB, vs 82.32 kB on develop.
  • The vendored code (e.g. meriyah's class fields) has always been ES2022. It used to live in vendored/**, which the es-check glob doesn't cover. Now it sits in top-level build/esm/hook.js, so lint:es-compatibility checks that file at es2022 and everything else at es2020 as before.

How to review: Start with rollup.npm.config.mjs. The new test/hook.test.ts checks that the built hook imports only builtins, and that registration succeeds when a deployment ships nothing from the ESM build except hook.js. Both tests fail on develop with the error from the issue. The new nextjs-16-standalone build-output test does the same check against the real Turbopack and webpack standalone output. It forces the Module.register() path because the runner's Node uses registerHooks. Without the fix, it fails with the issue's error. With the fix, it passes for both bundlers.

Unsure about: Whether the extra package size is acceptable. The quieter warning for missing hook files comes in a separate PR.

Closes #26121

via Francesco Gringl-Novy.

--

View Session [Sentry]

…lf-contained file

`register.ts` hands `build/esm/hook.js` to `Module.register()` by runtime path. Turbopack ships
that file into the Next.js server trace but does not follow its imports, so on Node < 24.13 the
loader thread failed to find the vendored chunks (and `@sentry/core`'s ESM build, imported by the
`debug` shim) and registration warned on every cold start.

The hook is now built as a single file that imports only Node builtins. Its `debug` shim is a
no-op: the loader thread has its own `globalThis`, so the Sentry logger there is never enabled.

Fixes #26121

Co-Authored-By: Francesco Gringl-Novy <francesco.novy@sentry.io>
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Compared: cc1fc15 (PR test merge using develop at d6d803c)

Against: d6d803c (develop) · base run #65636

✅ Baseline matches: this diff contains only this PR's changes.

Path Size % Change Change
@sentry/browser 29.92 kB - -
@sentry/browser - with treeshaking flags 28.08 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.97 kB - -
@sentry/browser (incl. Tracing) 52.29 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 52.3 kB - -
@sentry/browser (incl. Tracing, Profiling) 55.27 kB - -
@sentry/browser (incl. Tracing, Replay) 92.7 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 81.37 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 97.43 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 110.42 kB - -
@sentry/browser (incl. Feedback) 47.52 kB - -
@sentry/browser (incl. sendFeedback) 35.02 kB - -
@sentry/browser (incl. FeedbackAsync) 40.13 kB - -
@sentry/browser (incl. Metrics) 31.48 kB - -
@sentry/browser (incl. Logs) 31.77 kB - -
@sentry/browser (incl. Metrics & Logs) 32.45 kB - -
@sentry/react 31.78 kB - -
@sentry/react (incl. Tracing) 54.76 kB - -
@sentry/vue 38.33 kB - -
@sentry/vue (incl. Tracing) 55.33 kB - -
@sentry/svelte 29.95 kB - -
@sentry/remix (Remix 3 client bundle) 57.92 kB - -
CDN Bundle 31.72 kB - -
CDN Bundle (incl. Tracing) 52.89 kB - -
CDN Bundle (incl. Logs, Metrics) 34.52 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 55.39 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 76 kB - -
CDN Bundle (incl. Tracing, Replay) 91.17 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 93.72 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 97.3 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 99.89 kB - -
CDN Bundle - uncompressed 92.76 kB - -
CDN Bundle (incl. Tracing) - uncompressed 156.02 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 101.29 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 163.79 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 233.18 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 278.04 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 285.8 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 291.74 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 299.5 kB - -
@sentry/nextjs (client) 57.73 kB - -
@sentry/sveltekit (client) 52.82 kB - -
@sentry/core/server 44.51 kB - -
@sentry/core/browser 14.02 kB - -
@sentry/node 157.79 kB +0.02% +16 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.85 kB -0.57% -461 B 🔽
@sentry/node - without tracing 97.23 kB +0.02% +17 B 🔺
@sentry/node - without channel injection 138.11 kB +0.02% +19 B 🔺
@sentry/aws-serverless 106.08 kB +0.03% +24 B 🔺
@sentry/cloudflare (withSentry) - minified 203.81 kB - -
@sentry/cloudflare (withSentry) 506.45 kB - -
@sentry/nextjs/cloudflare (withSentry) - minified 225.49 kB - -

View base workflow run

@github-actions

Copy link
Copy Markdown
Contributor

One or more gzipped bundles increased by more than 500 bytes. If this increase is intentional, add the Accept Bundlesize Increase label to this PR to rerun and accept the check.

The no-op shim only existed to avoid `src/debug.ts`. The hook never runs on
Deno, so the original reason for `debugShim` doesn't apply, and bundling
`debug` keeps `DEBUG=@apm-js-collab/*` working on the loader thread. The hook
build now feeds the shared license plugin so debug, ms, supports-color and
has-flag are listed in THIRD-PARTY-LICENSES.txt.
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Optional E2E tests

Some optional checks were not successful. They do not block the merge. 12 not successful, 45 successful. See the run.

Check Status
❌ CI: Optional E2E Tests / E2E solidstart-2 Test (optional) Failing after 54s Details
❌ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (client source maps kept) Test (optional) Failing after 26s Details
❌ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps Test (optional) Failing after 32s Details
❌ CI: Optional E2E Tests / E2E nuxt-5 (tunnel-dev) Test (optional) Failing after 32s Details
❌ CI: Optional E2E Tests / E2E node-flue (latest) Test (optional) Failing after 34s Details
❌ CI: Optional E2E Tests / E2E nuxt-5-cloudflare Test (optional) Failing after 24s Details
❌ CI: Optional E2E Tests / E2E node-eve (latest) Test (optional) Failing after 57s Details
❌ CI: Optional E2E Tests / E2E nuxt-5 Test (optional) Failing after 25s Details
❌ CI: Optional E2E Tests / E2E nextjs-16-cacheComponents (cloudflare) Test (optional) Failing after 10m Details
❌ CI: Optional E2E Tests / E2E node-eve Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E node-eve (orchestrion) Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (static / nuxt generate) Test (optional) Failing after 27s Details
Successful checks
Check Status
✅ CI: Optional E2E Tests / E2E cloudflare-flue-send-to-sentry Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-agent Test (optional) Successful in 46s Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (latest) Test (optional) Successful in 4m Details
✅ CI: Optional E2E Tests / E2E cloudflare-think (ai v6) Test (optional) Successful in 55s Details
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable Test (optional) Successful in 44s Details
✅ CI: Optional E2E Tests / E2E cloudflare-workers-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 Test (optional) Successful in 34s Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions Test (optional) Successful in 28s Details
✅ CI: Optional E2E Tests / E2E node-pi-durable Test (optional) Successful in 37s Details
✅ CI: Optional E2E Tests / E2E astro-7-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16-tunnel (cloudflare) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable (latest) Test (optional) Successful in 50s Details
✅ CI: Optional E2E Tests / E2E debug-id-sourcemaps Test (optional) Successful in 41s Details
✅ CI: Optional E2E Tests / E2E nextjs-14 (latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable (latest) Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (cloudflare, latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E react-router-7-framework-instrumentation Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions (latest) Test (optional) Successful in 26s Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (latest) Test (optional) Successful in 46s Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-cloudflare Test (optional) Successful in 45s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (deno) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E vue-3 (no Options API) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-sourcemaps Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E astro-7 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E supabase-nextjs Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (latest) Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E cloudflare-think Test (optional) Successful in 54s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext, bun) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (cloudflare) Test (optional) Successful in 41s Details
✅ CI: Optional E2E Tests / E2E nextjs-orpc Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (turbo) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E deno (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-flue Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-mastra (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 (tracer provider) Test (optional) Successful in 36s Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v2 Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E react-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-mastra Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries Test (optional) Successful in 44s Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v1 Test (optional) Successful in 36s Details

…/import

Co-Authored-By: Francesco Gringl-Novy <francesco.novy@sentry.io>
Comment thread .size-limit.js Resolved
The registration test fails whenever the hook or anything it loads is missing,
so the file-exists check adds nothing. Assert on the registration marker
instead of the warning text, which #26272 rewords.
@mydea
mydea marked this pull request as ready for review November 11, 2026 09:37
@mydea
mydea requested review from a team, JPeer264 and stephanie-anderson November 11, 2026 09:37
@mydea
mydea requested a review from a team as a code owner November 11, 2026 09:37
@mydea
mydea requested review from andreiborza, chargome, isaacs and timfish and removed request for a team November 11, 2026 09:37
 into fix/server-runtime-injection-self-contained-hook

Co-Authored-By: Francesco Gringl-Novy <francesco.novy@sentry.io>

@timfish timfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This doesn't add 1.6MB to all server SDKs dependencies, it actually adds 3.4MB with the additional sourcemap file!

Hold off on this for a bit, I think I have an alternative!

@timfish

timfish commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

I think this is a better solution with no downsides?

makeBaseNPMConfig({
entrypoints: ['src/hook.mjs'],
packageSpecificConfig: {
plugins: [guardTracingDump, commonjs(commonJSOptions), thirdPartyLicensePlugin],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The hook bundle runs debug's browser build at load and touches localStorage.

debug/src/index.js picks ./browser.js or ./node.js with a runtime if. With strictRequires: false, @rollup/plugin-commonjs hoists both requires, so the bundled hook.js evaluates browser.js too (see browser.exports near line 466 of the built build/esm/hook.js). browser.js runs exports.storage = localstorage(), which reads the global localStorage. On Node versions where Web Storage is on by default, that read emits a process warning:

(node:94064) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.

Reproduced on Node 26.5 with the forced Module.register() path. When I replaced return localStorage; with return undefined; in the built hook, the warning went away. In real use, Node 26 takes the registerHooks path, so it is not affected. But hasStableSyncModuleHooks() (packages/server-runtime-injection/src/register.ts line 34) sends Node 25.0.x down the Module.register() path, and Node 25 turns on Web Storage by default. I did not have a Node 25.0 binary to confirm. Node 22 with --experimental-webstorage may also hit it (not verified). This is the kind of "warning on every cold start" the PR sets out to remove, and develop did not have it, because debugShim kept the real debug out of the hook.

Suggestion: in the hook build only, resolve debug straight to its Node entry, for example a resolveId plugin with order: 'pre' that maps debug to require.resolve('debug/src/node.js') (the same pattern as debugShim). That also drops browser.js from the bundle. Add an assertion to test/hook.test.ts that loading the hook writes nothing to stderr, or at least that the built file does not contain localStorage.

plugins: [guardTracingDump, commonjs(commonJSOptions), thirdPartyLicensePlugin],
output: {
exports: 'named',
preserveModules: false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The PR says the package grows "by about 1.6 MB". I packed this branch and compared it with the published 11.12.0:

11.12.0 (npm) this PR (local pack)
unpacked size 6.96 MB 10.43 MB
build/esm/hook.js n/a 0.54 MB
build/esm/hook.js.map n/a 2.94 MB

The new map holds 2.3 MB of mappings and 0.59 MB of sourcesContent. The tarball is about 1.0 MB. Develop has moved since 11.12.0, so the numbers are not exact, but the growth is about +3.5 MB unpacked. This package is a dependency of @sentry/node, so every Node SDK install pays it, and serverless users with size limits (Lambda layers, Vercel functions that trace the whole build/esm dir) can feel it.

Suggestion: fix the number in the PR description. Then decide whether the hook needs a full source map. Node only uses it with --enable-source-maps, and errors from the loader thread are rare. Options: sourcemapExcludeSources: true for the hook output, or sourcemap: false for this config only. Either is a one-line change in the output block.

Comment thread .size-limit.js
{
name: '@sentry/node/import (ESM hook with diagnostics-channel injection)',
path: ['packages/server-runtime-injection/build/esm/hook.js', 'packages/node/build/import-hook.mjs'],
path: 'packages/node/build/import-hook.mjs',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nothing measures the loader-thread code any more

Before, the entry measured hook.js plus import-hook.mjs. Now it measures only import-hook.mjs, and register.ts reaches the hook through join(), which webpack does not follow. So the 540 kB hook, which every Module.register() user loads, has no size check at all. If a future bump of @apm-js-collab/* or a new dependency doubles it, CI will not notice. The PR's "81.85 kB vs 82.32 kB" improvement comes from not counting that code, not from loading less.

Suggestion: add a second entry, for example @sentry/server-runtime-injection hook (loader thread) with path: 'packages/server-runtime-injection/build/esm/hook.js' and the same ignore list, so changes to that file stay visible. (The bot comment "One or more gzipped bundles increased by more than 500 bytes" does not match the current table, so it is probably from an earlier push.)

const hookSpecifier = hookFound ? pathToFileURL(hookPath).href : '@sentry/server-runtime-injection/hook';

if (hookFound) {
// Only `build/esm/package.json` marks the hook and its chunks as ESM. Deployments can drop

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// Only `build/esm/package.json` marks the hook as ESM. Deployments can drop

Comment on lines 298 to 301
// Built from `join()` rather than `new URL('./hook.js', import.meta.url)` because webpack
// reads that second form as an asset reference: it copies the hook next to the app bundle
// without its vendored chunks, which would leave the loader thread importing a file whose
// own imports are missing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still referring to chunks here, looks like it was missed in the other edits.

Suggestion: Either drop the "without its vendored chunks" reasoning or restate why join() is still preferred. If the only reason left is webpack's asset copy, it may now be safe to switch to new URL(), but that changes where the hook comes from in bundled apps, so it is a separate decision and out of scope here.

describe('when a deployment ships only the hook from the ESM build', () => {
// The copy sits inside this package so `register.js` still resolves `@sentry/*` from its
// `node_modules`.
const copyDir = mkdtempSync(join(__dirname, '.hook-only-'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

mkdtempSync(join(__dirname, '.hook-only-')) runs while vitest collects the file, not in a beforeAll. It creates test/.hook-only-XXXX/ with a full copy of build/cjs. If the run is killed or collection fails after that line, the folder stays behind. It is not in .gitignore, so it shows up in git status and can be committed by mistake. It is also created when the test is filtered out.

Suggestion: create it in beforeAll. Better, put it in os.tmpdir() and give the child NODE_PATH set to this package's node_modules. I checked that build/cjs/register.js resolves @sentry/core and @sentry/server-utils that way from /tmp.

Comment on lines +68 to +74
import('data:text/javascript,export default 1').then(() => {
const marker = globalThis.__SENTRY__?.__SENTRY_ORCHESTRION__ ?? globalThis.__SENTRY_ORCHESTRION__;
process.stdout.write(JSON.stringify(marker));
});
`;

const { stdout, stderr } = spawnSync(process.execPath, ['--no-experimental-detect-module', '-e', script], {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  • import('data:text/javascript,export default 1').then(...) waits for an import before it reads the marker. A broken hook fails inside Module.register() on the spot (verified above), and the e2e test reads the marker right away. The wait is not needed, and the two tests now do the same check in two different ways.
  • --no-experimental-detect-module is there, I think, to copy Vercel's setting so that the missing build/esm/package.json matters and the data: format loader gets exercised. That is a good thing to test, but nothing says so.

Suggestion: drop the import() wait so the script matches the e2e one, and add a short comment on the flag. Or state the assumption if the wait is needed for a reason I missed.

Comment on lines 95 to 102
const thirdPartyLicensePlugin = license({
thirdParty: {
includePrivate: false,
output: {
file: 'build/THIRD-PARTY-LICENSES.txt',
},
},
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This shared license plugin instance is now load-bearing.

All three configs (register CJS, register ESM, hook ESM) use the same thirdPartyLicensePlugin object and write the same build/THIRD-PARTY-LICENSES.txt. The file is right only because that one instance collects dependencies across all builds. The final file lists debug, ms, supports-color and has-flag (hook only) and module-details-from-path (register only), so this works today. If someone changes it to one license() call per config, which is what the PR just did for commonjs(), the last build wins and some notices go missing with no error.

Suggestion: add one line to the comment above thirdPartyLicensePlugin saying that the instance must be shared so the file covers every build. A cheap guard is a unit test that reads build/THIRD-PARTY-LICENSES.txt and checks for debug and module-details-from-path.

@mydea

mydea commented Nov 11, 2026

Copy link
Copy Markdown
Member

I think this is a better solution with no downsides?

yeah if that works that seems much better, closing this in favor of that then! thanks for the reviews ❤️

@mydea mydea closed this Nov 11, 2026
timfish added a commit that referenced this pull request Nov 12, 2026
…CJS build (#26278)

On Node < 24.13, the CJS build of `register.ts` passed
`build/esm/hook.js` to `Module.register()`. Turbopack ships that file in
the Next.js server trace but does not follow its imports, so the loader
thread could not find `build/esm/vendored/**` or the ESM build of
`@sentry/core`. Registration then warned on every cold start.

The CJS build now registers `build/cjs/hook.js`, which the package
already built but did not use. That file only requires chunks that
`register.js` requires too, so a tracer that ships a working
`register.js` always ships them. Node accepts a CJS module as a
`Module.register()` target. This is an alternative to #26271: the
package does not grow and no Next.js config is needed.

The `data:` URL loader that forced the ESM format for `build/esm` is
removed. It only helped the CJS build load the ESM hook when a
deployment dropped `build/esm/package.json`, and the CJS build no longer
loads anything from `build/esm`. The ESM build still registers the ESM
hook.

The new `test/hook.test.ts` registers the hook from a copy that ships
only `build/cjs`. The `nextjs-16-standalone` build-output test (taken
from #26271) forces the `Module.register()` path against the real
Turbopack standalone output. Both fail on `develop` with the error from
the issue.

Closes #26121

---------

Co-authored-by: isaacs <i@izs.me>
mydea added a commit that referenced this pull request Nov 13, 2026
…se the deployment is missing a file (#26272)

**Why:** Follow-up to #26271. When `Module.register()` fails with
`ERR_MODULE_NOT_FOUND`, the always-on warning used to just repeat Node's
"Cannot find module …". That reads like an SDK bug, when the real cause
is that a build tracer didn't ship all of
`@sentry/server-runtime-injection`. The warning now says so. I checked
on Node 22 that `error.code` survives the trip back from the loader
thread. The warning still fires once per process: issue #26121 asks for
it to be quiet, but staying silent would hide that channel integrations
aren't recording anything (see #25677).

**How to review:** It's a single branch in the `catch` block of
`registerDiagnosticsChannelInjection()`, plus one unit test.

Refs #26121

<!-- junior-request-attribution:start -->
via **Francesco Gringl-Novy**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AD0CA8FXFXAS%3A1794383205.954799 -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/slack%3AD0CA8FXFXAS%3A1794383205.954799)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0CA8FXFXAS%3A1794383205.954799/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Francesco Gringl-Novy <francesco.novy@sentry.io>
mydea added a commit that referenced this pull request Nov 13, 2026
…se the deployment is missing a file (#26272)

**Why:** Follow-up to #26271. When `Module.register()` fails with
`ERR_MODULE_NOT_FOUND`, the always-on warning used to just repeat Node's
"Cannot find module …". That reads like an SDK bug, when the real cause
is that a build tracer didn't ship all of
`@sentry/server-runtime-injection`. The warning now says so. I checked
on Node 22 that `error.code` survives the trip back from the loader
thread. The warning still fires once per process: issue #26121 asks for
it to be quiet, but staying silent would hide that channel integrations
aren't recording anything (see #25677).

**How to review:** It's a single branch in the `catch` block of
`registerDiagnosticsChannelInjection()`, plus one unit test.

Refs #26121

<!-- junior-request-attribution:start -->
via **Francesco Gringl-Novy**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AD0CA8FXFXAS%3A1794383205.954799 -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/slack%3AD0CA8FXFXAS%3A1794383205.954799)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0CA8FXFXAS%3A1794383205.954799/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Francesco Gringl-Novy <francesco.novy@sentry.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants