Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/cloudflare/src/vite/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,11 @@ function wrapDefaultSpecifier(localName: string, state: TransformState): string
/**
* `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.
*
* The original re-export is a live binding to the handler module's export, but the wrapper is not
* set until this module's body runs. A circular import that reads the default export before then
* gets `undefined` instead of the handler. The binding is a `var` so that the read does not throw,
* as a `const` would, and stop the Worker from starting. See `wrapDefaultSpecifier`.
*/
function wrapDefaultReexport(
localName: string,
Expand All @@ -616,7 +621,7 @@ function wrapDefaultReexport(
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});`,
`var __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, ${state.optionsFn}, ${target});`,
);
state.needsImport = true;
state.autoWrapped.add(DEFAULT_EXPORT);
Expand Down
33 changes: 30 additions & 3 deletions packages/cloudflare/test/vite/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('default export wrapping', () => {
[
"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__);',
'var __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);',
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
].join('\n'),
);
Expand All @@ -254,20 +254,47 @@ describe('default export wrapping', () => {
[
"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__);',
'var __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);',
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
].join('\n'),
);
});

it('does not throw when a circular import reads a default re-export during module evaluation', () => {
// `b` reads the entry's default export after `./handler.mjs` has run but before the entry has.
const entry = [
"export { default } from './handler.mjs';",
"import { readDefault } from './b.mjs';",
'console.log(readDefault());',
].join('\n');
const transformed = transform(entry, ctx)!.code.replace(
"import * as __SENTRY__ from '@sentry/cloudflare';",
'const __SENTRY__ = { withSentry: (_options, handler) => handler, _INTERNAL_wrapUnlessInstrumented: (wrap, options, handler) => wrap(options, handler) };',
);

const dir = mkdtempSync(join(tmpdir(), 'sentry-cf-cycle-'));
try {
writeFileSync(join(dir, 'a.mjs'), transformed);
writeFileSync(join(dir, 'handler.mjs'), 'export default { fetch() {} };\n');
writeFileSync(
join(dir, 'b.mjs'),
"import handler from './a.mjs';\nconst kind = typeof handler;\nexport function readDefault() { return kind; }\n",
);
// Plain Node: Vitest's module runner does not evaluate import cycles like native ESM does.
expect(execFileSync(process.execPath, [join(dir, 'a.mjs')], { encoding: 'utf8' }).trim()).toBe('undefined');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

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__);',
'var __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);',
'export { __SENTRY_DEFAULT_EXPORT__ as default };',
"export { helper } from './handler';",
].join('\n'),
Expand Down
Loading