Skip to content

Commit 56a1391

Browse files
author
Luca Forstner
committed
ref(nextjs): Use generic loader to inject global values
1 parent 65245f4 commit 56a1391

10 files changed

+124
-135
lines changed

packages/nextjs/rollup.npm.config.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ export default [
2020
...makeNPMConfigVariants(
2121
makeBaseNPMConfig({
2222
entrypoints: [
23-
'src/config/templates/serverRewriteFramesPrefixLoaderTemplate.ts',
24-
'src/config/templates/clientRewriteFramesPrefixLoaderTemplate.ts',
25-
'src/config/templates/releasePrefixLoaderTemplate.ts',
2623
'src/config/templates/pageProxyLoaderTemplate.ts',
2724
'src/config/templates/apiProxyLoaderTemplate.ts',
2825
],
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export { default as valueInjectionLoader } from './valueInjectionLoader';
12
export { default as prefixLoader } from './prefixLoader';
23
export { default as proxyLoader } from './proxyLoader';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { LoaderThis } from './types';
2+
3+
type LoaderOptions = {
4+
values: Record<string, unknown>;
5+
};
6+
7+
/**
8+
* Set values on the global/window object at the start of a module.
9+
*
10+
* Options:
11+
* - `values`: A record where the keys correspond to the keys of the global values to set and the values
12+
* correspond to the values of the values on the global object. Values must be JSON serializable.
13+
*/
14+
export default function valueInjectionLoader(this: LoaderThis<LoaderOptions>, userCode: string): string {
15+
// We know one or the other will be defined, depending on the version of webpack being used
16+
const { values } = 'getOptions' in this ? this.getOptions() : this.query;
17+
18+
// Define some global proxy that works on server and on the browser.
19+
let injectedCode = 'var _sentryCollisionFreeGlobalObject = typeof window === "undefined" ? global : window;\n';
20+
21+
Object.entries(values).forEach(([key, value]) => {
22+
injectedCode += `_sentryCollisionFreeGlobalObject.${key} = ${JSON.stringify(value)};\n`;
23+
});
24+
25+
return `${injectedCode}\n${userCode}`;
26+
}

packages/nextjs/src/config/templates/clientRewriteFramesPrefixLoaderTemplate.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/nextjs/src/config/templates/releasePrefixLoaderTemplate.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/nextjs/src/config/templates/serverRewriteFramesPrefixLoaderTemplate.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/nextjs/src/config/webpack.ts

Lines changed: 75 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -87,26 +87,8 @@ export function constructWebpackConfigFunction(
8787
// `newConfig.module.rules` is required, so we don't have to keep asserting its existence
8888
const newConfig = setUpModuleRules(rawNewConfig);
8989

90-
// Add a loader which will inject code that sets global values for use by `RewriteFrames`
91-
addRewriteFramesLoader(newConfig, isServer ? 'server' : 'client', userNextConfig);
92-
93-
newConfig.module.rules.push({
94-
test: /sentry\.(server|client)\.config\.(jsx?|tsx?)/,
95-
use: [
96-
{
97-
// Inject the release value the same way the webpack plugin does.
98-
loader: path.resolve(__dirname, 'loaders/prefixLoader.js'),
99-
options: {
100-
templatePrefix: 'release',
101-
replacements: [
102-
['__RELEASE__', webpackPluginOptions.release || process.env.SENTRY_RELEASE],
103-
['__ORG__', webpackPluginOptions.org || process.env.SENTRY_ORG],
104-
['__PROJECT__', webpackPluginOptions.project || process.env.SENTRY_PROJECT || ''],
105-
],
106-
},
107-
},
108-
],
109-
});
90+
// Add a loader which will inject code that sets global values
91+
addValueInjectionLoader(newConfig, userNextConfig, webpackPluginOptions);
11092

11193
if (isServer) {
11294
if (userSentryOptions.autoInstrumentServerFunctions !== false) {
@@ -667,49 +649,85 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi
667649
}
668650

669651
/**
670-
* Support the `distDir` and `assetPrefix` options by making their values (easy to get here at build-time) available at
671-
* runtime (for use by `RewriteFrames`), by injecting code to attach their values to `global` or `window`.
672-
*
673-
* @param newConfig The webpack config object being constructed
674-
* @param target Either 'server' or 'client'
675-
* @param userNextConfig The user's nextjs config options
652+
* Adds loaders to inject values on the global object based on user configuration.
676653
*/
677-
function addRewriteFramesLoader(
654+
function addValueInjectionLoader(
678655
newConfig: WebpackConfigObjectWithModuleRules,
679-
target: 'server' | 'client',
680656
userNextConfig: NextConfigObject,
657+
webpackPluginOptions: SentryWebpackPlugin.SentryCliPluginOptions,
681658
): void {
682-
// Nextjs will use `basePath` in place of `assetPrefix` if it's defined but `assetPrefix` is not
683659
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
684-
const replacements = {
685-
server: [
686-
[
687-
'__DIST_DIR__',
688-
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
689-
// characters)
690-
userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
691-
],
692-
],
693-
client: [
694-
[
695-
'__ASSET_PREFIX_PATH__',
696-
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
697-
// `assetPreix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
698-
assetPrefix ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') : '',
699-
],
700-
],
660+
const releaseValue = webpackPluginOptions.release || process.env.SENTRY_RELEASE;
661+
const orgValue = webpackPluginOptions.org || process.env.SENTRY_ORG;
662+
const projectValue = webpackPluginOptions.project || process.env.SENTRY_PROJECT;
663+
664+
const serverValues = {
665+
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
666+
// characters)
667+
__rewriteFramesDistDir__: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
668+
};
669+
670+
const clientValues = {
671+
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
672+
// `assetPreix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
673+
__rewriteFramesAssetPrefixPath__: assetPrefix
674+
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
675+
: '',
676+
};
677+
678+
const isomorphicValues = {
679+
// Inject release into SDK
680+
...(releaseValue
681+
? {
682+
SENTRY_RELEASE: {
683+
id: releaseValue,
684+
},
685+
}
686+
: undefined),
687+
688+
// Enable module federation support (see https://github.com/getsentry/sentry-webpack-plugin/pull/307)
689+
...(projectValue && releaseValue
690+
? {
691+
SENTRY_RELEASES: {
692+
[orgValue ? `${projectValue}@${orgValue}` : projectValue]: { id: releaseValue },
693+
},
694+
}
695+
: undefined),
701696
};
702697

703-
newConfig.module.rules.push({
704-
test: new RegExp(`sentry\\.${target}\\.config\\.(jsx?|tsx?)`),
705-
use: [
706-
{
707-
loader: path.resolve(__dirname, 'loaders/prefixLoader.js'),
708-
options: {
709-
templatePrefix: `${target}RewriteFrames`,
710-
replacements: replacements[target],
698+
newConfig.module.rules.push(
699+
{
700+
test: /sentry\.server\.config\.(jsx?|tsx?)/,
701+
use: [
702+
{
703+
loader: path.resolve(__dirname, 'loaders/valueInjectionLoader.js'),
704+
options: {
705+
values: serverValues,
706+
},
711707
},
712-
},
713-
],
714-
});
708+
],
709+
},
710+
{
711+
test: /sentry\.client\.config\.(jsx?|tsx?)/,
712+
use: [
713+
{
714+
loader: path.resolve(__dirname, 'loaders/valueInjectionLoader.js'),
715+
options: {
716+
values: clientValues,
717+
},
718+
},
719+
],
720+
},
721+
{
722+
test: /sentry\.(server|client)\.config\.(jsx?|tsx?)/,
723+
use: [
724+
{
725+
loader: path.resolve(__dirname, 'loaders/valueInjectionLoader.js'),
726+
options: {
727+
values: isomorphicValues,
728+
},
729+
},
730+
],
731+
},
732+
);
715733
}

packages/nextjs/src/index.client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ declare const __SENTRY_TRACING__: boolean;
3535
// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206
3636
declare const EdgeRuntime: string | undefined;
3737

38-
type GlobalWithAssetPrefixPath = typeof global & { __rewriteFramesAssetPrefixPath__: string };
38+
const globalWithInjectedValues = global as typeof global & {
39+
__rewriteFramesAssetPrefixPath__: string;
40+
__sentryRewritesTunnelPath__?: string;
41+
};
3942

4043
/** Inits the Sentry NextJS SDK on the browser with the React SDK. */
4144
export function init(options: NextjsOptions): void {
@@ -67,7 +70,7 @@ function addClientIntegrations(options: NextjsOptions): void {
6770

6871
// This value is injected at build time, based on the output directory specified in the build config. Though a default
6972
// is set there, we set it here as well, just in case something has gone wrong with the injection.
70-
const assetPrefixPath = (global as GlobalWithAssetPrefixPath).__rewriteFramesAssetPrefixPath__ || '';
73+
const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || '';
7174

7275
const defaultRewriteFramesIntegration = new RewriteFrames({
7376
// Turn `<origin>/<path>/_next/static/...` into `app:///_next/static/...`

packages/nextjs/src/index.server.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export { captureUnderscoreErrorException } from './utils/_error';
1919
// because or SSR of next.js we can only use this.
2020
export { ErrorBoundary, showReportDialog, withErrorBoundary } from '@sentry/react';
2121

22-
type GlobalWithDistDir = typeof global & { __rewriteFramesDistDir__: string };
22+
const globalWithInjectedValues = global as typeof global & {
23+
__rewriteFramesDistDir__: string;
24+
};
25+
2326
const domain = domainModule as typeof domainModule & { active: (domainModule.Domain & Carrier) | null };
2427

2528
// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js
@@ -114,7 +117,7 @@ function addServerIntegrations(options: NextjsOptions): void {
114117

115118
// This value is injected at build time, based on the output directory specified in the build config. Though a default
116119
// is set there, we set it here as well, just in case something has gone wrong with the injection.
117-
const distDirName = (global as GlobalWithDistDir).__rewriteFramesDistDir__ || '.next';
120+
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__ || '.next';
118121
// nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so
119122
// we can read in the project directory from the currently running process
120123
const distDirAbsPath = path.resolve(process.cwd(), distDirName);

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

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ declare global {
4141

4242
describe('webpack loaders', () => {
4343
describe('server loaders', () => {
44-
it('adds server `RewriteFrames` loader to server config', async () => {
44+
it('adds server `valueInjection` loader to server config', async () => {
4545
const finalWebpackConfig = await materializeFinalWebpackConfig({
4646
exportedNextConfig,
4747
incomingWebpackConfig: serverWebpackConfig,
@@ -52,42 +52,16 @@ describe('webpack loaders', () => {
5252
test: /sentry\.server\.config\.(jsx?|tsx?)/,
5353
use: [
5454
{
55-
loader: expect.stringEndingWith('prefixLoader.js'),
56-
options: expect.objectContaining({ templatePrefix: 'serverRewriteFrames' }),
57-
},
58-
],
59-
});
60-
});
61-
62-
it('adds release prefix loader to server config', async () => {
63-
const finalWebpackConfig = await materializeFinalWebpackConfig({
64-
exportedNextConfig,
65-
incomingWebpackConfig: serverWebpackConfig,
66-
incomingWebpackBuildContext: serverBuildContext,
67-
userSentryWebpackPluginConfig: userSentryWebpackPluginConfig,
68-
});
69-
70-
expect(finalWebpackConfig.module.rules).toContainEqual({
71-
test: /sentry\.(server|client)\.config\.(jsx?|tsx?)/,
72-
use: [
73-
{
74-
loader: expect.stringEndingWith('prefixLoader.js'),
75-
options: {
76-
templatePrefix: 'release',
77-
replacements: [
78-
['__RELEASE__', 'doGsaREgReaT'],
79-
['__ORG__', 'squirrelChasers'],
80-
['__PROJECT__', 'simulator'],
81-
],
82-
},
55+
loader: expect.stringEndingWith('valueInjectionLoader.js'),
56+
options: expect.objectContaining({ values: expect.objectContaining({}) }),
8357
},
8458
],
8559
});
8660
});
8761
});
8862

8963
describe('client loaders', () => {
90-
it('adds `RewriteFrames` loader to client config', async () => {
64+
it('adds `valueInjection` loader to client config', async () => {
9165
const finalWebpackConfig = await materializeFinalWebpackConfig({
9266
exportedNextConfig,
9367
incomingWebpackConfig: clientWebpackConfig,
@@ -98,14 +72,14 @@ describe('webpack loaders', () => {
9872
test: /sentry\.client\.config\.(jsx?|tsx?)/,
9973
use: [
10074
{
101-
loader: expect.stringEndingWith('prefixLoader.js'),
102-
options: expect.objectContaining({ templatePrefix: 'clientRewriteFrames' }),
75+
loader: expect.stringEndingWith('valueInjectionLoader.js'),
76+
options: expect.objectContaining({ values: expect.objectContaining({}) }),
10377
},
10478
],
10579
});
10680
});
10781

108-
it('adds release prefix loader to client config', async () => {
82+
it('adds `valueInjection` loader with release values to client and server configs', async () => {
10983
const finalWebpackConfig = await materializeFinalWebpackConfig({
11084
exportedNextConfig,
11185
incomingWebpackConfig: clientWebpackConfig,
@@ -117,14 +91,12 @@ describe('webpack loaders', () => {
11791
test: /sentry\.(server|client)\.config\.(jsx?|tsx?)/,
11892
use: [
11993
{
120-
loader: expect.stringEndingWith('prefixLoader.js'),
94+
loader: expect.stringEndingWith('valueInjectionLoader.js'),
12195
options: {
122-
templatePrefix: 'release',
123-
replacements: [
124-
['__RELEASE__', 'doGsaREgReaT'],
125-
['__ORG__', 'squirrelChasers'],
126-
['__PROJECT__', 'simulator'],
127-
],
96+
values: {
97+
SENTRY_RELEASE: { id: 'doGsaREgReaT' },
98+
SENTRY_RELEASES: { 'simulator@squirrelChasers': { id: 'doGsaREgReaT' } },
99+
},
128100
},
129101
},
130102
],

0 commit comments

Comments
 (0)