Skip to content
Draft
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
29 changes: 28 additions & 1 deletion packages/cloudflare/src/vite/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,28 @@ function wrapSpecifierExports(node: ExportNamedNode, ctx: TransformContext, stat
state.ms.overwrite(node.start, node.end, statements.join('\n'));
}

/**
* `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 when its class has
* to be wrapped through a fresh binding — the cross-module case. The import/wrapper statements that
Expand All @@ -508,7 +530,12 @@ function wrapCrossModuleSpecifier(
? resolveWrapperKind(exportedName, sourceLiteral ? undefined : localName, state)
: undefined;

if (!exportedName || !localName || !kind) return undefined;
if (!exportedName || !localName) return undefined;
if (!kind) {
return exportedName === DEFAULT_IMPORT && sourceLiteral
? wrapDefaultReexport(localName, sourceLiteral, state, prelude)
: undefined;
}

// Without a `from` clause the specifier points at a module-local binding, which may already be
// (or become) the wrapped class without touching the export statement itself.
Expand Down
50 changes: 50 additions & 0 deletions packages/cloudflare/test/vite/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,46 @@ describe('default export wrapping', () => {
expect(result.code).toContain('__SENTRY__.withSentry(');
});

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(),
Expand Down Expand Up @@ -889,6 +929,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';",
Expand Down
Loading