diff --git a/packages/cloudflare/src/vite/transform.ts b/packages/cloudflare/src/vite/transform.ts index e640de3a45d1..43add52af3cd 100644 --- a/packages/cloudflare/src/vite/transform.ts +++ b/packages/cloudflare/src/vite/transform.ts @@ -396,7 +396,7 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state // MagicString positions are always relative to the original source. Only the `export default` // keywords are replaced, so parentheses around the expression stay. const source = state.ms.original; - const keywordsEnd = node.start + source.slice(node.start, decl.start).lastIndexOf('default') + 'default'.length; + const keywordsEnd = exportDefaultKeywordsEnd(source, node.start); const declaredName = decl.type === 'ClassDeclaration' || decl.type === 'FunctionDeclaration' ? (decl as ClassDeclarationNode).id?.name @@ -425,6 +425,26 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state state.autoWrapped.add(DEFAULT_EXPORT); } +/** + * Offset just past the `default` keyword of the `export default` statement at `start`. Comments can + * sit between `export`, `default` and the expression, and may themselves contain the word `default`. + */ +function exportDefaultKeywordsEnd(source: string, start: number): number { + let index = start + 'export'.length; + while (index < source.length && !source.startsWith('default', index)) { + if (source.startsWith('//', index)) { + const lineEnd = source.indexOf('\n', index); + index = lineEnd === -1 ? source.length : lineEnd; + } else if (source.startsWith('/*', index)) { + const commentEnd = source.indexOf('*/', index + 2); + index = commentEnd === -1 ? source.length : commentEnd + 2; + } else { + index++; + } + } + return index + 'default'.length; +} + function isAnonymousFunctionOrClass(node: BaseNode): boolean { if (node.type === 'ArrowFunctionExpression') return true; const isFunctionOrClass = [ diff --git a/packages/cloudflare/test/vite/transform.test.ts b/packages/cloudflare/test/vite/transform.test.ts index 76129b034923..bc99d7133f5b 100644 --- a/packages/cloudflare/test/vite/transform.test.ts +++ b/packages/cloudflare/test/vite/transform.test.ts @@ -362,6 +362,33 @@ describe('default export wrapping', () => { ); }); + it('wraps a default export with a comment that mentions `default`', () => { + const code = 'export /* the default */ default /* default handler */ { fetch() {} };'; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'var __SENTRY_DEFAULT_EXPORT__ = /* default handler */ { fetch() {} };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);', + ].join('\n'), + ); + }); + + it('wraps a default export with a line comment that mentions `default`', () => { + const code = ['export // the default', 'default // default handler', '{ fetch() {} };'].join('\n'); + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'var __SENTRY_DEFAULT_EXPORT__ = // default handler', + '{ fetch() {} };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);', + ].join('\n'), + ); + }); + it('keeps the module binding of a named default class', () => { const code = ['export default class Handler { fetch() {} }', 'Handler.ready = true;'].join('\n');