Skip to content

Commit f5c454b

Browse files
JPeer264claude
andcommitted
feat(cloudflare): Add @sentry/cloudflare/vite orchestrion plugin
Exposes a Sentry Vite plugin for Cloudflare Workers that wraps the orchestrion plugin from @sentry/server-utils. It injects diagnostics channels into bundled npm packages (e.g. `mysql`) at build time, so the SDK can trace them without monkey-patching - which wouldn't work in workerd anyway, since there is no require hook to intercept module loading. It also injects a generated registration module into the bundle (`registerIntegrations: true`), which registers the matching channel-subscriber integrations on the global orchestrion marker for `Sentry.init` to pick up. The subpath is published ESM-only because `@sentry/server-utils/orchestrion/vite` exposes no `require` condition; Vite configs are ESM anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent f713b57 commit f5c454b

4 files changed

Lines changed: 88 additions & 1 deletion

File tree

packages/cloudflare/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
"types": "./build/types/nodejs_compat/index.d.ts",
4747
"default": "./build/cjs/nodejs_compat/index.js"
4848
}
49+
},
50+
"./vite": {
51+
"types": "./build/types/vite/index.d.ts",
52+
"import": "./build/esm/vite/index.js"
4953
}
5054
},
5155
"typesVersions": {

packages/cloudflare/rollup.npm.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollu
22

33
export default makeNPMConfigVariants(
44
makeBaseNPMConfig({
5-
entrypoints: ['src/index.ts', 'src/nodejs_compat/index.ts'],
5+
entrypoints: ['src/index.ts', 'src/nodejs_compat/index.ts', 'src/vite/index.ts'],
66
}),
77
);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Published ESM-only via the `@sentry/cloudflare/vite` subpath export:
2+
// `@sentry/server-utils/orchestrion/vite` exposes no `require` condition, so a
3+
// CJS entry here would fail at resolution time (ERR_PACKAGE_PATH_NOT_EXPORTED).
4+
// The CJS rollup variant still emits this file, but `package.json` doesn't
5+
// expose it — same setup as `@sentry/server-utils/orchestrion/vite` itself.
6+
import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/vite';
7+
8+
/**
9+
* Sentry Vite plugin for Cloudflare Workers.
10+
*
11+
* Injects `diagnostics_channel.tracingChannel` calls into bundled npm packages
12+
* (e.g. `mysql`) at build time via orchestrion, so the SDK can trace them
13+
* without monkey-patching, which wouldn't work in workerd anyway.
14+
*
15+
* It also injects a generated registration module into the bundle, which
16+
* registers the matching channel-subscriber integrations for `Sentry.init` to
17+
* pick up. The SDK itself doesn't import them, so workers built without this
18+
* plugin don't ship that code; the worker only needs the usual
19+
* `Sentry.withSentry` wrapping.
20+
*
21+
* @example
22+
* ```ts
23+
* // vite.config.ts
24+
* import { cloudflare } from '@cloudflare/vite-plugin';
25+
* import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite';
26+
*
27+
* export default {
28+
* plugins: [
29+
* sentryCloudflareVitePlugin(),
30+
* cloudflare(),
31+
* ],
32+
* };
33+
*
34+
* // src/index.ts (worker entry)
35+
* import * as Sentry from '@sentry/cloudflare';
36+
*
37+
* export default Sentry.withSentry(
38+
* env => ({
39+
* dsn: env.SENTRY_DSN,
40+
* tracesSampleRate: 1.0,
41+
* }),
42+
* {
43+
* async fetch(request, env, ctx) {
44+
* // ...
45+
* },
46+
* } satisfies ExportedHandler,
47+
* );
48+
* ```
49+
*/
50+
export function sentryCloudflareVitePlugin() {
51+
return sentryOrchestrionPlugin({ registerIntegrations: true });
52+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { sentryOrchestrionPlugin } from '../../src/orchestrion/bundler/vite';
3+
import { INSTRUMENTED_MODULE_NAMES } from '../../src/orchestrion/config';
4+
5+
function getMarkerPlugin() {
6+
const plugins = sentryOrchestrionPlugin();
7+
const marker = plugins.find(p => p.name === 'sentry-orchestrion-marker');
8+
expect(marker).toBeDefined();
9+
return marker;
10+
}
11+
12+
describe('sentryOrchestrionPlugin', () => {
13+
it('returns the marker plugin and the code transformer', () => {
14+
const plugins = sentryOrchestrionPlugin();
15+
expect(plugins.map(p => p.name)).toContain('sentry-orchestrion-marker');
16+
expect(plugins.map(p => p.name)).toContain('code-transformer');
17+
});
18+
19+
it('force-bundles instrumented packages via ssr.noExternal', () => {
20+
const marker = getMarkerPlugin();
21+
expect(marker.config()).toEqual({ ssr: { noExternal: INSTRUMENTED_MODULE_NAMES } });
22+
});
23+
24+
it('prepends the bundler marker banner to entry chunks', () => {
25+
const marker = getMarkerPlugin();
26+
const result = marker.renderChunk('console.log("app");', { isEntry: true });
27+
expect(result.code).toContain('globalThis.__SENTRY_ORCHESTRION__.bundler = true;');
28+
expect(result.map).toBeDefined();
29+
expect(marker.renderChunk('console.log("chunk");', { isEntry: false })).toBeNull();
30+
});
31+
});

0 commit comments

Comments
 (0)