diff --git a/packages/cloudflare/src/vite/transform.ts b/packages/cloudflare/src/vite/transform.ts index 49b623d6c431..5307ad48336b 100644 --- a/packages/cloudflare/src/vite/transform.ts +++ b/packages/cloudflare/src/vite/transform.ts @@ -566,6 +566,28 @@ function wrapDefaultSpecifier(localName: string, state: TransformState): string return '__SENTRY_DEFAULT_EXPORT__ as default'; } +/** + * `export { default } from './handler'`. The handler may already be wrapped in its own module, + * which this transform cannot see, so the emitted guard returns such a handler as-is. + */ +function wrapDefaultReexport( + localName: string, + sourceLiteral: string, + state: TransformState, + prelude: string[], +): string { + const target = '__SENTRY_REEXPORT_default__'; + prelude.push( + localName === DEFAULT_IMPORT + ? `import ${target} from ${sourceLiteral};` + : `import { ${localName} as ${target} } from ${sourceLiteral};`, + `const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, ${state.optionsFn}, ${target});`, + ); + state.needsImport = true; + state.autoWrapped.add(DEFAULT_EXPORT); + return '__SENTRY_DEFAULT_EXPORT__ as default'; +} + /** * Handle one export specifier, returning the `Wrapped as Exported` pair to emit for a class that * gets wrapped. The import/wrapper statements a cross-module pair depends on are pushed onto @@ -593,7 +615,10 @@ function wrapSpecifier( if (!exportedName || !localName) return undefined; if (!kind) { - return exportedName === DEFAULT_IMPORT && !sourceLiteral ? wrapDefaultSpecifier(localName, state) : undefined; + if (exportedName !== DEFAULT_IMPORT) return undefined; + return sourceLiteral + ? wrapDefaultReexport(localName, sourceLiteral, state, prelude) + : wrapDefaultSpecifier(localName, state); } // Without a `from` clause the specifier points at a module-local binding. diff --git a/packages/cloudflare/test/vite/transform.test.ts b/packages/cloudflare/test/vite/transform.test.ts index 92396bf21bae..8b3cd2ec207f 100644 --- a/packages/cloudflare/test/vite/transform.test.ts +++ b/packages/cloudflare/test/vite/transform.test.ts @@ -234,6 +234,46 @@ describe('default export wrapping', () => { expect(transform(code, ctx)).toBeUndefined(); }); + it('wraps a default re-export from another module', () => { + const code = "export { default } from './handler';"; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + "import __SENTRY_REEXPORT_default__ from './handler';", + 'const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + ].join('\n'), + ); + }); + + it('wraps a named export of another module re-exported as default', () => { + const code = "export { handler as default } from './handler';"; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + "import { handler as __SENTRY_REEXPORT_default__ } from './handler';", + 'const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + ].join('\n'), + ); + }); + + it('leaves sibling specifiers of a default re-export untouched', () => { + const code = "export { default, helper } from './handler';"; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + "import __SENTRY_REEXPORT_default__ from './handler';", + 'const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + "export { helper } from './handler';", + ].join('\n'), + ); + }); + it('uses custom options callback', () => { const custom: TransformContext = { classWrappers: doWrappers(), @@ -1118,6 +1158,16 @@ describe('same-worker RPC binding floor', () => { expect(result.code).toContain('rpcTracePropagationBindings: ["SELF",'); }); + it('enables a self service binding when it wrapped a default re-export', () => { + const result = transform("export { default } from './handler';", { + classWrappers: doWrappers(), + optionsFn: '() => undefined', + sameWorkerBindings: [{ bindingName: 'SELF', className: DEFAULT_EXPORT }], + })!; + + expect(result.code).toContain('rpcTracePropagationBindings: ["SELF",'); + }); + it('enables a self service binding when the default export re-exports an already wrapped class', () => { const code = [ "import { WorkerEntrypoint } from 'cloudflare:workers';",