From 2b116e5c44b066eef51be2f24182459d624c5fa8 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 23 Sep 2026 15:04:02 +0200 Subject: [PATCH] fix(nextjs): Stop Turbopack loaders from loading the Sentry CLI The Turbopack loaders imported helpers from @sentry/bundler-plugins/core, which also loads the sentry CLI package. Next.js canary follows the loaders' imports into that package and fails compiling its index.d.cts type file as code. Add an internal @sentry/bundler-plugins/loader-utils entry point that exports only the two helpers the loaders need, and point the loaders at it. Fixes #24650 Co-Authored-By: Claude Opus 5.5 --- packages/bundler-plugins/package.json | 5 +++ .../bundler-plugins/rollup.npm.config.mjs | 1 + .../src/core/component-annotate-hooks.ts | 43 +++++++++++++++++++ packages/bundler-plugins/src/core/index.ts | 43 +------------------ .../bundler-plugins/src/loader-utils/index.ts | 4 ++ .../test/loader-utils/index.test.ts | 14 ++++++ .../loaders/componentAnnotationLoader.ts | 2 +- .../loaders/moduleMetadataInjectionLoader.ts | 2 +- .../config/loaders/valueInjectionLoader.ts | 2 +- .../loaders/componentAnnotationLoader.test.ts | 2 +- 10 files changed, 72 insertions(+), 46 deletions(-) create mode 100644 packages/bundler-plugins/src/core/component-annotate-hooks.ts create mode 100644 packages/bundler-plugins/src/loader-utils/index.ts create mode 100644 packages/bundler-plugins/test/loader-utils/index.test.ts diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 184d03788b69..0e7bf587e579 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -44,6 +44,11 @@ "import": "./build/esm/core/index.js", "require": "./build/cjs/core/index.js" }, + "./loader-utils": { + "types": "./build/types/loader-utils/index.d.ts", + "import": "./build/esm/loader-utils/index.js", + "require": "./build/cjs/loader-utils/index.js" + }, "./babel-plugin": { "types": "./build/types/babel-plugin/index.d.ts", "import": "./build/esm/babel-plugin/index.js", diff --git a/packages/bundler-plugins/rollup.npm.config.mjs b/packages/bundler-plugins/rollup.npm.config.mjs index fe1cb44cbe76..65f39a6a8d22 100644 --- a/packages/bundler-plugins/rollup.npm.config.mjs +++ b/packages/bundler-plugins/rollup.npm.config.mjs @@ -4,6 +4,7 @@ export default makeNPMConfigVariants( makeBaseNPMConfig({ entrypoints: [ 'src/core/index.ts', + 'src/loader-utils/index.ts', 'src/rollup/index.ts', 'src/vite/index.ts', 'src/esbuild/index.ts', diff --git a/packages/bundler-plugins/src/core/component-annotate-hooks.ts b/packages/bundler-plugins/src/core/component-annotate-hooks.ts new file mode 100644 index 000000000000..00898f9942cc --- /dev/null +++ b/packages/bundler-plugins/src/core/component-annotate-hooks.ts @@ -0,0 +1,43 @@ +import { createOxcComponentNameAnnotateHooks, getOxcParseAstAsync } from './component-annotation-oxc'; +import type { ComponentAnnotationTransformMeta, ParseAstAsync } from './component-annotation-oxc-ast'; +import type { Logger } from './logger'; + +const PARSER_UNAVAILABLE_MESSAGE = + 'Could not load `oxc-parser` for this platform. React components will not be annotated.'; + +// Module level, because the Turbopack loader creates new hooks for +// every file. +let warnedParserUnavailable = false; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function createComponentNameAnnotateHooks( + ignoredComponents: string[], + injectIntoHtml: boolean, + options: { getParseAstAsync?: () => Promise; logger?: Logger } = {}, +) { + const hooks = createOxcComponentNameAnnotateHooks( + ignoredComponents, + async () => { + const parseAstAsync = (await options.getParseAstAsync?.()) ?? (await getOxcParseAstAsync()); + + if (!parseAstAsync && !warnedParserUnavailable) { + warnedParserUnavailable = true; + if (options.logger) { + options.logger.warn(PARSER_UNAVAILABLE_MESSAGE); + } else { + // eslint-disable-next-line no-console + console.warn(`[@sentry/bundler-plugins] ${PARSER_UNAVAILABLE_MESSAGE}`); + } + } + + return parseAstAsync; + }, + injectIntoHtml, + ); + + return { + transform(this: void, code: string, id: string, meta?: ComponentAnnotationTransformMeta) { + return hooks.transform(code, id, meta); + }, + }; +} diff --git a/packages/bundler-plugins/src/core/index.ts b/packages/bundler-plugins/src/core/index.ts index ca5ca854f182..fd645190fb51 100644 --- a/packages/bundler-plugins/src/core/index.ts +++ b/packages/bundler-plugins/src/core/index.ts @@ -1,7 +1,4 @@ import { CodeInjection, containsOnlyImports, stripQueryAndHashFromPath } from './utils'; -import { createOxcComponentNameAnnotateHooks, getOxcParseAstAsync } from './component-annotation-oxc'; -import type { ComponentAnnotationTransformMeta, ParseAstAsync } from './component-annotation-oxc-ast'; -import type { Logger } from './logger'; /** * Checks if a file is a JavaScript file based on its extension. @@ -43,45 +40,7 @@ export function shouldSkipCodeInjection(code: string, facadeModuleId: string | n export { globFiles } from './glob'; export { getCodeInjectionPosition } from './get-code-injection-position'; -const PARSER_UNAVAILABLE_MESSAGE = - 'Could not load `oxc-parser` for this platform. React components will not be annotated.'; - -// Module level, because the Turbopack loader creates new hooks for -// every file. -let warnedParserUnavailable = false; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function createComponentNameAnnotateHooks( - ignoredComponents: string[], - injectIntoHtml: boolean, - options: { getParseAstAsync?: () => Promise; logger?: Logger } = {}, -) { - const hooks = createOxcComponentNameAnnotateHooks( - ignoredComponents, - async () => { - const parseAstAsync = (await options.getParseAstAsync?.()) ?? (await getOxcParseAstAsync()); - - if (!parseAstAsync && !warnedParserUnavailable) { - warnedParserUnavailable = true; - if (options.logger) { - options.logger.warn(PARSER_UNAVAILABLE_MESSAGE); - } else { - // eslint-disable-next-line no-console - console.warn(`[@sentry/bundler-plugins] ${PARSER_UNAVAILABLE_MESSAGE}`); - } - } - - return parseAstAsync; - }, - injectIntoHtml, - ); - - return { - transform(this: void, code: string, id: string, meta?: ComponentAnnotationTransformMeta) { - return hooks.transform(code, id, meta); - }, - }; -} +export { createComponentNameAnnotateHooks } from './component-annotate-hooks'; export function getDebugIdSnippet(debugId: string): CodeInjection { return new CodeInjection( diff --git a/packages/bundler-plugins/src/loader-utils/index.ts b/packages/bundler-plugins/src/loader-utils/index.ts new file mode 100644 index 000000000000..96fe7cd50bf0 --- /dev/null +++ b/packages/bundler-plugins/src/loader-utils/index.ts @@ -0,0 +1,4 @@ +// Internal to Sentry SDKs, not public API. Kept separate from `./core` so bundler loaders +// don't pull in the build plugin manager and the `sentry` CLI. +export { getCodeInjectionPosition } from '../core/get-code-injection-position'; +export { createComponentNameAnnotateHooks } from '../core/component-annotate-hooks'; diff --git a/packages/bundler-plugins/test/loader-utils/index.test.ts b/packages/bundler-plugins/test/loader-utils/index.test.ts new file mode 100644 index 000000000000..bcb61f5b992f --- /dev/null +++ b/packages/bundler-plugins/test/loader-utils/index.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('sentry', () => { + throw new Error('`loader-utils` must not import the `sentry` CLI'); +}); + +describe('loader-utils', () => { + it('exports the loader helpers without loading the sentry CLI', async () => { + const loaderUtils = await import('../../src/loader-utils'); + + expect(loaderUtils.getCodeInjectionPosition).toBeInstanceOf(Function); + expect(loaderUtils.createComponentNameAnnotateHooks).toBeInstanceOf(Function); + }); +}); diff --git a/packages/nextjs/src/config/loaders/componentAnnotationLoader.ts b/packages/nextjs/src/config/loaders/componentAnnotationLoader.ts index 44496ee1e781..49ab694e673d 100644 --- a/packages/nextjs/src/config/loaders/componentAnnotationLoader.ts +++ b/packages/nextjs/src/config/loaders/componentAnnotationLoader.ts @@ -1,4 +1,4 @@ -import { createComponentNameAnnotateHooks } from '@sentry/bundler-plugins/core'; +import { createComponentNameAnnotateHooks } from '@sentry/bundler-plugins/loader-utils'; import type { LoaderThis } from './types'; export type ComponentAnnotationLoaderOptions = { diff --git a/packages/nextjs/src/config/loaders/moduleMetadataInjectionLoader.ts b/packages/nextjs/src/config/loaders/moduleMetadataInjectionLoader.ts index 0c44bac71db5..c199ecd75c8f 100644 --- a/packages/nextjs/src/config/loaders/moduleMetadataInjectionLoader.ts +++ b/packages/nextjs/src/config/loaders/moduleMetadataInjectionLoader.ts @@ -1,4 +1,4 @@ -import { getCodeInjectionPosition } from '@sentry/bundler-plugins/core'; +import { getCodeInjectionPosition } from '@sentry/bundler-plugins/loader-utils'; import type { LoaderThis } from './types'; export type ModuleMetadataInjectionLoaderOptions = { diff --git a/packages/nextjs/src/config/loaders/valueInjectionLoader.ts b/packages/nextjs/src/config/loaders/valueInjectionLoader.ts index 8937b4e435e1..9efd442e8d83 100644 --- a/packages/nextjs/src/config/loaders/valueInjectionLoader.ts +++ b/packages/nextjs/src/config/loaders/valueInjectionLoader.ts @@ -1,4 +1,4 @@ -import { getCodeInjectionPosition } from '@sentry/bundler-plugins/core'; +import { getCodeInjectionPosition } from '@sentry/bundler-plugins/loader-utils'; import type { LoaderThis } from './types'; export type ValueInjectionLoaderOptions = { diff --git a/packages/nextjs/test/config/loaders/componentAnnotationLoader.test.ts b/packages/nextjs/test/config/loaders/componentAnnotationLoader.test.ts index f086d89f3ae2..a97b07291326 100644 --- a/packages/nextjs/test/config/loaders/componentAnnotationLoader.test.ts +++ b/packages/nextjs/test/config/loaders/componentAnnotationLoader.test.ts @@ -9,7 +9,7 @@ const { mockTransform, mockCreateHooks } = vi.hoisted(() => { return { mockTransform, mockCreateHooks }; }); -vi.mock('@sentry/bundler-plugins/core', () => ({ +vi.mock('@sentry/bundler-plugins/loader-utils', () => ({ createComponentNameAnnotateHooks: mockCreateHooks, }));