Skip to content

Commit 9f32c61

Browse files
authored
feat(nextjs): Add automatic sourcemapping for edge part of the SDK (#9454)
1 parent 2c1b288 commit 9f32c61

File tree

10 files changed

+69
-43
lines changed

10 files changed

+69
-43
lines changed

packages/bun/src/client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class BunClient extends ServerRuntimeClient<BunClientOptions> {
3030

3131
const clientOptions: ServerRuntimeClientOptions = {
3232
...options,
33-
platform: 'bun',
33+
platform: 'javascript',
3434
runtime: { name: 'bun', version: Bun.version },
3535
serverName: options.serverName || global.process.env.SENTRY_NAME || os.hostname(),
3636
};

packages/core/test/lib/serverruntimeclient.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ describe('ServerRuntimeClient', () => {
2222
describe('_prepareEvent', () => {
2323
test('adds platform to event', () => {
2424
const options = getDefaultClientOptions({ dsn: PUBLIC_DSN });
25-
const client = new ServerRuntimeClient({ ...options, platform: 'edge' });
25+
const client = new ServerRuntimeClient({ ...options, platform: 'blargh' });
2626

2727
const event: Event = {};
2828
const hint: EventHint = {};
2929
(client as any)._prepareEvent(event, hint);
3030

31-
expect(event.platform).toEqual('edge');
31+
expect(event.platform).toEqual('blargh');
3232
});
3333

3434
test('adds server_name to event', () => {

packages/deno/src/client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class DenoClient extends ServerRuntimeClient<DenoClientOptions> {
3434

3535
const clientOptions: ServerRuntimeClientOptions = {
3636
...options,
37-
platform: 'deno',
37+
platform: 'javascript',
3838
runtime: { name: 'deno', version: Deno.version.deno },
3939
serverName: options.serverName || getHostName(),
4040
};

packages/deno/test/__snapshots__/mod.test.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ snapshot[`captureException 1`] = `
135135
},
136136
],
137137
},
138-
platform: "deno",
138+
platform: "javascript",
139139
sdk: {
140140
integrations: [
141141
"InboundFilters",
@@ -195,7 +195,7 @@ snapshot[`captureMessage 1`] = `
195195
event_id: "{{id}}",
196196
level: "info",
197197
message: "Some error message",
198-
platform: "deno",
198+
platform: "javascript",
199199
sdk: {
200200
integrations: [
201201
"InboundFilters",

packages/nextjs/src/config/webpack.ts

+13-16
Original file line numberDiff line numberDiff line change
@@ -686,24 +686,20 @@ export function getWebpackPluginOptions(
686686
userPluginOptions: Partial<SentryWebpackPluginOptions>,
687687
userSentryOptions: UserSentryOptions,
688688
): SentryWebpackPluginOptions {
689-
const { buildId, isServer, webpack, config, dir: projectDir } = buildContext;
689+
const { buildId, isServer, config, dir: projectDir } = buildContext;
690690
const userNextConfig = config as NextConfigObject;
691691

692692
const distDirAbsPath = path.resolve(projectDir, userNextConfig.distDir || '.next'); // `.next` is the default directory
693693

694-
const isWebpack5 = webpack.version.startsWith('5');
695694
const isServerless = userNextConfig.target === 'experimental-serverless-trace';
696695
const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties'));
697696
const urlPrefix = '~/_next';
698697

699698
const serverInclude = isServerless
700699
? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }]
701-
: [
702-
{ paths: [`${distDirAbsPath}/server/pages/`], urlPrefix: `${urlPrefix}/server/pages` },
703-
{ paths: [`${distDirAbsPath}/server/app/`], urlPrefix: `${urlPrefix}/server/app` },
704-
].concat(
705-
isWebpack5 ? [{ paths: [`${distDirAbsPath}/server/chunks/`], urlPrefix: `${urlPrefix}/server/chunks` }] : [],
706-
);
700+
: [{ paths: [`${distDirAbsPath}/server/`], urlPrefix: `${urlPrefix}/server` }];
701+
702+
const serverIgnore: string[] = [];
707703

708704
const clientInclude = userSentryOptions.widenClientFileUpload
709705
? [{ paths: [`${distDirAbsPath}/static/chunks`], urlPrefix: `${urlPrefix}/static/chunks` }]
@@ -712,15 +708,16 @@ export function getWebpackPluginOptions(
712708
{ paths: [`${distDirAbsPath}/static/chunks/app`], urlPrefix: `${urlPrefix}/static/chunks/app` },
713709
];
714710

711+
// Widening the upload scope is necessarily going to lead to us uploading files we don't need to (ones which
712+
// don't include any user code). In order to lessen that where we can, exclude the internal nextjs files we know
713+
// will be there.
714+
const clientIgnore = userSentryOptions.widenClientFileUpload
715+
? ['framework-*', 'framework.*', 'main-*', 'polyfills-*', 'webpack-*']
716+
: [];
717+
715718
const defaultPluginOptions = dropUndefinedKeys({
716719
include: isServer ? serverInclude : clientInclude,
717-
ignore:
718-
isServer || !userSentryOptions.widenClientFileUpload
719-
? []
720-
: // Widening the upload scope is necessarily going to lead to us uploading files we don't need to (ones which
721-
// don't include any user code). In order to lessen that where we can, exclude the internal nextjs files we know
722-
// will be there.
723-
['framework-*', 'framework.*', 'main-*', 'polyfills-*', 'webpack-*'],
720+
ignore: isServer ? serverIgnore : clientIgnore,
724721
url: process.env.SENTRY_URL,
725722
org: process.env.SENTRY_ORG,
726723
project: process.env.SENTRY_PROJECT,
@@ -976,7 +973,7 @@ function addValueInjectionLoader(
976973

977974
newConfig.module.rules.push(
978975
{
979-
test: /sentry\.server\.config\.(jsx?|tsx?)/,
976+
test: /sentry\.(server|edge)\.config\.(jsx?|tsx?)/,
980977
use: [
981978
{
982979
loader: path.resolve(__dirname, 'loaders/valueInjectionLoader.js'),

packages/nextjs/src/edge/index.ts

+30
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { SDK_VERSION } from '@sentry/core';
2+
import { RewriteFrames } from '@sentry/integrations';
23
import type { SdkMetadata } from '@sentry/types';
4+
import { addOrUpdateIntegration, escapeStringForRegex, GLOBAL_OBJ } from '@sentry/utils';
35
import type { VercelEdgeOptions } from '@sentry/vercel-edge';
46
import { init as vercelEdgeInit } from '@sentry/vercel-edge';
57

68
export type EdgeOptions = VercelEdgeOptions;
79

10+
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
11+
__rewriteFramesDistDir__?: string;
12+
};
13+
814
/** Inits the Sentry NextJS SDK on the Edge Runtime. */
915
export function init(options: VercelEdgeOptions = {}): void {
1016
const opts = {
@@ -23,6 +29,30 @@ export function init(options: VercelEdgeOptions = {}): void {
2329
version: SDK_VERSION,
2430
};
2531

32+
let integrations = opts.integrations || [];
33+
34+
// This value is injected at build time, based on the output directory specified in the build config. Though a default
35+
// is set there, we set it here as well, just in case something has gone wrong with the injection.
36+
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__;
37+
if (distDirName) {
38+
const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one
39+
40+
// Normally we would use `path.resolve` to obtain the absolute path we will strip from the stack frame to align with
41+
// the uploaded artifacts, however we don't have access to that API in edge so we need to be a bit more lax.
42+
const SOURCEMAP_FILENAME_REGEX = new RegExp(`.*${escapeStringForRegex(distDirAbsPath)}`);
43+
44+
const defaultRewriteFramesIntegration = new RewriteFrames({
45+
iteratee: frame => {
46+
frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next');
47+
return frame;
48+
},
49+
});
50+
51+
integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, integrations);
52+
}
53+
54+
opts.integrations = integrations;
55+
2656
vercelEdgeInit(opts);
2757
}
2858

packages/nextjs/src/server/index.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function showReportDialog(): void {
5050
}
5151

5252
const globalWithInjectedValues = global as typeof global & {
53-
__rewriteFramesDistDir__: string;
53+
__rewriteFramesDistDir__?: string;
5454
};
5555

5656
// TODO (v8): Remove this
@@ -119,19 +119,21 @@ function addServerIntegrations(options: NodeOptions): void {
119119

120120
// This value is injected at build time, based on the output directory specified in the build config. Though a default
121121
// is set there, we set it here as well, just in case something has gone wrong with the injection.
122-
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__ || '.next';
123-
// nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so
124-
// we can read in the project directory from the currently running process
125-
const distDirAbsPath = path.resolve(process.cwd(), distDirName);
126-
const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath));
127-
128-
const defaultRewriteFramesIntegration = new RewriteFrames({
129-
iteratee: frame => {
130-
frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next');
131-
return frame;
132-
},
133-
});
134-
integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, integrations);
122+
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__;
123+
if (distDirName) {
124+
// nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so
125+
// we can read in the project directory from the currently running process
126+
const distDirAbsPath = path.resolve(distDirName).replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one
127+
const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath));
128+
129+
const defaultRewriteFramesIntegration = new RewriteFrames({
130+
iteratee: frame => {
131+
frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next');
132+
return frame;
133+
},
134+
});
135+
integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, integrations);
136+
}
135137

136138
const defaultOnUncaughtExceptionIntegration: IntegrationWithExclusionOption = new Integrations.OnUncaughtException({
137139
exitEvenIfOtherHandlersAreRegistered: false,

packages/nextjs/test/config/loaders.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('webpack loaders', () => {
7272
});
7373

7474
expect(finalWebpackConfig.module.rules).toContainEqual({
75-
test: /sentry\.server\.config\.(jsx?|tsx?)/,
75+
test: /sentry\.(server|edge)\.config\.(jsx?|tsx?)/,
7676
use: [
7777
{
7878
loader: expect.stringEndingWith('valueInjectionLoader.js'),

packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,7 @@ describe('Sentry webpack plugin config', () => {
141141
) as SentryWebpackPlugin;
142142

143143
expect(sentryWebpackPluginInstance.options.include).toEqual([
144-
{ paths: [`${serverBuildContextWebpack4.dir}/.next/server/pages/`], urlPrefix: '~/_next/server/pages' },
145-
{ paths: [`${serverBuildContextWebpack4.dir}/.next/server/app/`], urlPrefix: '~/_next/server/app' },
144+
{ paths: [`${serverBuildContextWebpack4.dir}/.next/server/`], urlPrefix: '~/_next/server' },
146145
]);
147146
});
148147

@@ -159,9 +158,7 @@ describe('Sentry webpack plugin config', () => {
159158
) as SentryWebpackPlugin;
160159

161160
expect(sentryWebpackPluginInstance.options.include).toEqual([
162-
{ paths: [`${serverBuildContext.dir}/.next/server/pages/`], urlPrefix: '~/_next/server/pages' },
163-
{ paths: [`${serverBuildContext.dir}/.next/server/app/`], urlPrefix: '~/_next/server/app' },
164-
{ paths: [`${serverBuildContext.dir}/.next/server/chunks/`], urlPrefix: '~/_next/server/chunks' },
161+
{ paths: [`${serverBuildContext.dir}/.next/server/`], urlPrefix: '~/_next/server' },
165162
]);
166163
});
167164
});

packages/vercel-edge/src/client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class VercelEdgeClient extends ServerRuntimeClient<VercelEdgeClientOption
3333

3434
const clientOptions: ServerRuntimeClientOptions = {
3535
...options,
36-
platform: 'vercel-edge',
36+
platform: 'javascript',
3737
// TODO: Grab version information
3838
runtime: { name: 'vercel-edge' },
3939
serverName: options.serverName || process.env.SENTRY_NAME,

0 commit comments

Comments
 (0)