Skip to content

Commit fa0e226

Browse files
chargomemsonnb
authored andcommitted
feat(nextjs): Inject manifest into client for turbopack builds (#16902)
1 parent 32b29e9 commit fa0e226

File tree

5 files changed

+574
-7
lines changed

5 files changed

+574
-7
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { logger } from '@sentry/core';
2+
import * as chalk from 'chalk';
3+
import * as path from 'path';
4+
import type { RouteManifest } from '../manifest/types';
5+
import type { NextConfigObject, TurbopackOptions, TurbopackRuleConfigItemOrShortcut } from '../types';
6+
7+
/**
8+
* Construct a Turbopack config object from a Next.js config object and a Turbopack options object.
9+
*
10+
* @param userNextConfig - The Next.js config object.
11+
* @param turbopackOptions - The Turbopack options object.
12+
* @returns The Turbopack config object.
13+
*/
14+
export function constructTurbopackConfig({
15+
userNextConfig,
16+
routeManifest,
17+
}: {
18+
userNextConfig: NextConfigObject;
19+
routeManifest?: RouteManifest;
20+
}): TurbopackOptions {
21+
const newConfig: TurbopackOptions = {
22+
...userNextConfig.turbopack,
23+
};
24+
25+
if (routeManifest) {
26+
newConfig.rules = safelyAddTurbopackRule(newConfig.rules, {
27+
matcher: '**/instrumentation-client.*',
28+
rule: {
29+
loaders: [
30+
{
31+
loader: path.resolve(__dirname, '..', 'loaders', 'valueInjectionLoader.js'),
32+
options: {
33+
values: {
34+
_sentryRouteManifest: JSON.stringify(routeManifest),
35+
},
36+
},
37+
},
38+
],
39+
},
40+
});
41+
}
42+
43+
return newConfig;
44+
}
45+
46+
/**
47+
* Safely add a Turbopack rule to the existing rules.
48+
*
49+
* @param existingRules - The existing rules.
50+
* @param matcher - The matcher for the rule.
51+
* @param rule - The rule to add.
52+
* @returns The updated rules object.
53+
*/
54+
export function safelyAddTurbopackRule(
55+
existingRules: TurbopackOptions['rules'],
56+
{ matcher, rule }: { matcher: string; rule: TurbopackRuleConfigItemOrShortcut },
57+
): TurbopackOptions['rules'] {
58+
if (!existingRules) {
59+
return {
60+
[matcher]: rule,
61+
};
62+
}
63+
64+
// If the rule already exists, we don't want to mess with it.
65+
if (existingRules[matcher]) {
66+
logger.info(
67+
`${chalk.cyan(
68+
'info',
69+
)} - Turbopack rule already exists for ${matcher}. Please remove it from your Next.js config in order for Sentry to work properly.`,
70+
);
71+
return existingRules;
72+
}
73+
74+
return {
75+
...existingRules,
76+
[matcher]: rule,
77+
};
78+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './constructTurbopackConfig';

packages/nextjs/src/config/types.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type NextConfigObject = {
5151
// https://nextjs.org/docs/pages/api-reference/next-config-js/env
5252
env?: Record<string, string>;
5353
serverExternalPackages?: string[]; // next >= v15.0.0
54+
turbopack?: TurbopackOptions;
5455
};
5556

5657
export type SentryBuildOptions = {
@@ -607,3 +608,38 @@ export type EnhancedGlobal = typeof GLOBAL_OBJ & {
607608
SENTRY_RELEASE?: { id: string };
608609
SENTRY_RELEASES?: { [key: string]: { id: string } };
609610
};
611+
612+
type JSONValue = string | number | boolean | JSONValue[] | { [k: string]: JSONValue };
613+
614+
type TurbopackLoaderItem =
615+
| string
616+
| {
617+
loader: string;
618+
// At the moment, Turbopack options must be JSON-serializable, so restrict values.
619+
options: Record<string, JSONValue>;
620+
};
621+
622+
type TurbopackRuleCondition = {
623+
path: string | RegExp;
624+
};
625+
626+
export type TurbopackRuleConfigItemOrShortcut = TurbopackLoaderItem[] | TurbopackRuleConfigItem;
627+
628+
type TurbopackRuleConfigItemOptions = {
629+
loaders: TurbopackLoaderItem[];
630+
as?: string;
631+
};
632+
633+
type TurbopackRuleConfigItem =
634+
| TurbopackRuleConfigItemOptions
635+
| { [condition: string]: TurbopackRuleConfigItem }
636+
| false;
637+
638+
export interface TurbopackOptions {
639+
resolveAlias?: Record<string, string | string[] | Record<string, string | string[]>>;
640+
resolveExtensions?: string[];
641+
rules?: Record<string, TurbopackRuleConfigItemOrShortcut>;
642+
conditions?: Record<string, TurbopackRuleCondition>;
643+
moduleIds?: 'named' | 'deterministic';
644+
root?: string;
645+
}

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as fs from 'fs';
77
import * as path from 'path';
88
import { createRouteManifest } from './manifest/createRouteManifest';
99
import type { RouteManifest } from './manifest/types';
10+
import { constructTurbopackConfig } from './turbopack';
1011
import type {
1112
ExportedNextConfig as NextConfig,
1213
NextConfigFunction,
@@ -251,6 +252,8 @@ function getFinalConfigObject(
251252
}
252253

253254
let nextMajor: number | undefined;
255+
const isTurbopack = process.env.TURBOPACK;
256+
let isTurbopackSupported = false;
254257
if (nextJsVersion) {
255258
const { major, minor, patch, prerelease } = parseSemver(nextJsVersion);
256259
nextMajor = major;
@@ -262,6 +265,7 @@ function getFinalConfigObject(
262265
(major === 15 && minor > 3) ||
263266
(major === 15 && minor === 3 && patch === 0 && prerelease === undefined) ||
264267
(major === 15 && minor === 3 && patch > 0));
268+
isTurbopackSupported = isSupportedVersion;
265269
const isSupportedCanary =
266270
major !== undefined &&
267271
minor !== undefined &&
@@ -274,7 +278,7 @@ function getFinalConfigObject(
274278
parseInt(prerelease.split('.')[1] || '', 10) >= 28;
275279
const supportsClientInstrumentation = isSupportedCanary || isSupportedVersion;
276280

277-
if (!supportsClientInstrumentation && process.env.TURBOPACK) {
281+
if (!supportsClientInstrumentation && isTurbopack) {
278282
if (process.env.NODE_ENV === 'development') {
279283
// eslint-disable-next-line no-console
280284
console.warn(
@@ -307,12 +311,17 @@ function getFinalConfigObject(
307311
],
308312
},
309313
}),
310-
webpack: constructWebpackConfigFunction(
311-
incomingUserNextConfigObject,
312-
userSentryOptions,
313-
releaseName,
314-
routeManifest,
315-
),
314+
webpack: !isTurbopack
315+
? constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName, routeManifest)
316+
: undefined,
317+
...(isTurbopackSupported && isTurbopack
318+
? {
319+
turbopack: constructTurbopackConfig({
320+
userNextConfig: incomingUserNextConfigObject,
321+
routeManifest,
322+
}),
323+
}
324+
: {}),
316325
};
317326
}
318327

0 commit comments

Comments
 (0)