Skip to content

Commit 2ee6fa8

Browse files
authored
ref(nextjs): Pre-disable-plugin-option config cleanup (#3770)
This is a bunch of prep work pulled out of an upcoming PR, which will add the option to disable the `SentryWebpackPlugin` for both server and client builds. The changes in this PR are mostly cosmetic, with the aim of making the code more readable. The only substantive changes are: - When modifying the webpack config options, we now clone the config object we're given. - We now ensure that the `userNextConfig` passed to `constructWebpackConfigFunction` is an object. (We've been treating it like one, but in fact it's also possible for it to be given as a function. This is a change which should have been included in #3698.)
1 parent 5289d40 commit 2ee6fa8

File tree

4 files changed

+64
-48
lines changed

4 files changed

+64
-48
lines changed

packages/nextjs/src/config/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ export function withSentryConfig(
1212
userNextConfig: ExportedNextConfig = {},
1313
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {},
1414
): NextConfigFunction | NextConfigObject {
15-
const newWebpackConfig = constructWebpackConfigFunction(userNextConfig, userSentryWebpackPluginOptions);
16-
1715
// If the user has passed us a function, we need to return a function, so that we have access to `phase` and
1816
// `defaults` in order to pass them along to the user's function
1917
if (typeof userNextConfig === 'function') {
20-
return (phase: string, defaults: { defaultConfig: { [key: string]: unknown } }): NextConfigObject => ({
21-
...userNextConfig(phase, defaults),
22-
webpack: newWebpackConfig,
23-
});
18+
return function(phase: string, defaults: { defaultConfig: { [key: string]: unknown } }): NextConfigObject {
19+
const materializedUserNextConfig = userNextConfig(phase, defaults);
20+
return {
21+
...materializedUserNextConfig,
22+
webpack: constructWebpackConfigFunction(materializedUserNextConfig, userSentryWebpackPluginOptions),
23+
};
24+
};
2425
}
2526

2627
// Otherwise, we can just merge their config with ours and return an object.
27-
return { ...userNextConfig, webpack: newWebpackConfig };
28+
return { ...userNextConfig, webpack: constructWebpackConfigFunction(userNextConfig, userSentryWebpackPluginOptions) };
2829
}

packages/nextjs/src/config/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ export { SentryCliPluginOptions as SentryWebpackPluginOptions } from '@sentry/we
77
export type ExportedNextConfig = NextConfigObject | NextConfigFunction;
88

99
export type NextConfigObject = {
10-
// whether or not next should create source maps for browser code
11-
// see: https://nextjs.org/docs/advanced-features/source-maps
12-
productionBrowserSourceMaps?: boolean;
1310
// custom webpack options
1411
webpack?: WebpackConfigFunction;
1512
} & {

packages/nextjs/src/config/webpack.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
BuildContext,
77
EntryPointObject,
88
EntryPropertyObject,
9-
ExportedNextConfig,
9+
NextConfigObject,
1010
SentryWebpackPluginOptions,
1111
WebpackConfigFunction,
1212
WebpackConfigObject,
@@ -50,22 +50,23 @@ const defaultSentryWebpackPluginOptions = dropUndefinedKeys({
5050
* @returns The function to set as the nextjs config's `webpack` value
5151
*/
5252
export function constructWebpackConfigFunction(
53-
userNextConfig: ExportedNextConfig = {},
53+
userNextConfig: NextConfigObject = {},
5454
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {},
5555
): WebpackConfigFunction {
5656
const newWebpackFunction = (config: WebpackConfigObject, options: BuildContext): WebpackConfigObject => {
57+
// clone to avoid mutability issues
58+
let newConfig = { ...config };
59+
5760
// if we're building server code, store the webpack output path as an env variable, so we know where to look for the
5861
// webpack-processed version of `sentry.server.config.js` when we need it
59-
if (config.target === 'node') {
60-
storeServerConfigFileLocation(config);
62+
if (newConfig.target === 'node') {
63+
storeServerConfigFileLocation(newConfig);
6164
}
6265

63-
let newConfig = config;
64-
6566
// if user has custom webpack config (which always takes the form of a function), run it so we have actual values to
6667
// work with
6768
if ('webpack' in userNextConfig && typeof userNextConfig.webpack === 'function') {
68-
newConfig = userNextConfig.webpack(config, options);
69+
newConfig = userNextConfig.webpack(newConfig, options);
6970
}
7071

7172
// Ensure quality source maps in production. (Source maps aren't uploaded in dev, and besides, Next doesn't let you

packages/nextjs/test/config.test.ts

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import {
77
SentryWebpackPluginOptions,
88
WebpackConfigObject,
99
} from '../src/config/types';
10-
import { SENTRY_SERVER_CONFIG_FILE, SERVER_SDK_INIT_PATH, storeServerConfigFileLocation } from '../src/config/utils';
10+
import { SENTRY_SERVER_CONFIG_FILE, SERVER_SDK_INIT_PATH } from '../src/config/utils';
1111
import { constructWebpackConfigFunction, SentryWebpackPlugin } from '../src/config/webpack';
1212

1313
// mock `storeServerConfigFileLocation` in order to make it a no-op when necessary
1414
jest.mock('../src/config/utils', () => {
1515
const original = jest.requireActual('../src/config/utils');
1616
return {
1717
...original,
18-
storeServerConfigFileLocation: jest.fn().mockImplementation(original.setRuntimeEnvVars),
18+
// nuke this so it won't try to look for our dummy paths
19+
storeServerConfigFileLocation: jest.fn(),
1920
};
2021
});
2122

@@ -34,6 +35,10 @@ const userNextConfig = {
3435
};
3536
const userSentryWebpackPluginConfig = { org: 'squirrelChasers', project: 'simulator', include: './thirdPartyMaps' };
3637

38+
/** mocks of the arguments passed to the result of `withSentryConfig` (when it's a function) */
39+
const runtimePhase = 'puppy-phase-chew-everything-in-sight';
40+
const defaultNextConfig = { nappingHoursPerDay: 20, oversizeFeet: true, shouldChaseTail: true };
41+
3742
/** mocks of the arguments passed to `nextConfig.webpack` */
3843
const serverWebpackConfig = {
3944
entry: () => Promise.resolve({ 'pages/api/dogs/[name]': 'private-next-pages/api/dogs/[name].js' }),
@@ -47,7 +52,8 @@ const clientWebpackConfig = {
4752
target: 'web',
4853
context: '/Users/Maisey/projects/squirrelChasingSimulator',
4954
};
50-
const buildContext = { isServer: true, dev: false, buildId: 'doGsaREgReaT' };
55+
const serverBuildContext = { isServer: true, dev: false, buildId: 'doGsaREgReaT' };
56+
const clientBuildContext = { isServer: false, dev: false, buildId: 'doGsaREgReaT' };
5157

5258
/**
5359
* Derive the final values of all next config options, by first applying `withSentryConfig` and then, if it returns a
@@ -61,16 +67,16 @@ const buildContext = { isServer: true, dev: false, buildId: 'doGsaREgReaT' };
6167
*/
6268
function materializeFinalNextConfig(
6369
userNextConfig: ExportedNextConfig,
64-
userSentryWebpackPluginConfig: SentryWebpackPluginOptions,
70+
userSentryWebpackPluginConfig?: SentryWebpackPluginOptions,
6571
): NextConfigObject {
6672
const sentrifiedConfig = withSentryConfig(userNextConfig, userSentryWebpackPluginConfig);
6773
let finalConfigValues = sentrifiedConfig;
6874

6975
if (typeof sentrifiedConfig === 'function') {
7076
// for some reason TS won't recognize that `finalConfigValues` is now a NextConfigObject, which is why the cast
7177
// below is necessary
72-
finalConfigValues = sentrifiedConfig('phase-production-build', {
73-
defaultConfig: {},
78+
finalConfigValues = sentrifiedConfig(runtimePhase, {
79+
defaultConfig: defaultNextConfig,
7480
});
7581
}
7682

@@ -92,14 +98,25 @@ function materializeFinalNextConfig(
9298
*/
9399
async function materializeFinalWebpackConfig(options: {
94100
userNextConfig: ExportedNextConfig;
95-
userSentryWebpackPluginConfig: SentryWebpackPluginOptions;
101+
userSentryWebpackPluginConfig?: SentryWebpackPluginOptions;
96102
incomingWebpackConfig: WebpackConfigObject;
97103
incomingWebpackBuildContext: BuildContext;
98104
}): Promise<WebpackConfigObject> {
99105
const { userNextConfig, userSentryWebpackPluginConfig, incomingWebpackConfig, incomingWebpackBuildContext } = options;
100106

107+
// if the user's next config is a function, run it so we have access to the values
108+
const materializedUserNextConfig =
109+
typeof userNextConfig === 'function'
110+
? userNextConfig('phase-production-build', {
111+
defaultConfig: {},
112+
})
113+
: userNextConfig;
114+
101115
// get the webpack config function we'd normally pass back to next
102-
const webpackConfigFunction = constructWebpackConfigFunction(userNextConfig, userSentryWebpackPluginConfig);
116+
const webpackConfigFunction = constructWebpackConfigFunction(
117+
materializedUserNextConfig,
118+
userSentryWebpackPluginConfig,
119+
);
103120

104121
// call it to get concrete values for comparison
105122
const finalWebpackConfigValue = webpackConfigFunction(incomingWebpackConfig, incomingWebpackBuildContext);
@@ -111,7 +128,7 @@ async function materializeFinalWebpackConfig(options: {
111128

112129
describe('withSentryConfig', () => {
113130
it('includes expected properties', () => {
114-
const finalConfig = materializeFinalNextConfig(userNextConfig, userSentryWebpackPluginConfig);
131+
const finalConfig = materializeFinalNextConfig(userNextConfig);
115132

116133
expect(finalConfig).toEqual(
117134
expect.objectContaining({
@@ -121,13 +138,13 @@ describe('withSentryConfig', () => {
121138
});
122139

123140
it('preserves unrelated next config options', () => {
124-
const finalConfig = materializeFinalNextConfig(userNextConfig, userSentryWebpackPluginConfig);
141+
const finalConfig = materializeFinalNextConfig(userNextConfig);
125142

126143
expect(finalConfig.publicRuntimeConfig).toEqual(userNextConfig.publicRuntimeConfig);
127144
});
128145

129146
it("works when user's overall config is an object", () => {
130-
const finalConfig = materializeFinalNextConfig(userNextConfig, userSentryWebpackPluginConfig);
147+
const finalConfig = materializeFinalNextConfig(userNextConfig);
131148

132149
expect(finalConfig).toEqual(
133150
expect.objectContaining({
@@ -140,7 +157,7 @@ describe('withSentryConfig', () => {
140157
it("works when user's overall config is a function", () => {
141158
const userNextConfigFunction = () => userNextConfig;
142159

143-
const finalConfig = materializeFinalNextConfig(userNextConfigFunction, userSentryWebpackPluginConfig);
160+
const finalConfig = materializeFinalNextConfig(userNextConfigFunction);
144161

145162
expect(finalConfig).toEqual(
146163
expect.objectContaining({
@@ -149,20 +166,24 @@ describe('withSentryConfig', () => {
149166
}),
150167
);
151168
});
152-
});
153169

154-
describe('webpack config', () => {
155-
beforeEach(() => {
156-
// nuke this so it won't try to look for our dummy paths
157-
(storeServerConfigFileLocation as jest.Mock).mockImplementationOnce(() => undefined);
170+
it('correctly passes `phase` and `defaultConfig` through to functional `userNextConfig`', () => {
171+
const userNextConfigFunction = jest.fn().mockReturnValue(userNextConfig);
172+
173+
materializeFinalNextConfig(userNextConfigFunction);
174+
175+
expect(userNextConfigFunction).toHaveBeenCalledWith(runtimePhase, {
176+
defaultConfig: defaultNextConfig,
177+
});
158178
});
179+
});
159180

181+
describe('webpack config', () => {
160182
it('includes expected properties', async () => {
161183
const finalWebpackConfig = await materializeFinalWebpackConfig({
162184
userNextConfig,
163-
userSentryWebpackPluginConfig,
164185
incomingWebpackConfig: serverWebpackConfig,
165-
incomingWebpackBuildContext: buildContext,
186+
incomingWebpackBuildContext: serverBuildContext,
166187
});
167188

168189
expect(finalWebpackConfig).toEqual(
@@ -177,14 +198,13 @@ describe('webpack config', () => {
177198
it('preserves unrelated webpack config options', async () => {
178199
const finalWebpackConfig = await materializeFinalWebpackConfig({
179200
userNextConfig,
180-
userSentryWebpackPluginConfig,
181201
incomingWebpackConfig: serverWebpackConfig,
182-
incomingWebpackBuildContext: buildContext,
202+
incomingWebpackBuildContext: serverBuildContext,
183203
});
184204

185205
// Run the user's webpack config function, so we can check the results against ours. Delete `entry` because we'll
186206
// test it separately, and besides, it's one that we *should* be overwriting.
187-
const materializedUserWebpackConfig = userNextConfig.webpack(serverWebpackConfig, buildContext);
207+
const materializedUserWebpackConfig = userNextConfig.webpack(serverWebpackConfig, serverBuildContext);
188208
// @ts-ignore `entry` may be required in real life, but we don't need it for our tests
189209
delete materializedUserWebpackConfig.entry;
190210

@@ -195,9 +215,8 @@ describe('webpack config', () => {
195215
it('injects correct code when building server bundle', async () => {
196216
const finalWebpackConfig = await materializeFinalWebpackConfig({
197217
userNextConfig,
198-
userSentryWebpackPluginConfig,
199218
incomingWebpackConfig: serverWebpackConfig,
200-
incomingWebpackBuildContext: buildContext,
219+
incomingWebpackBuildContext: serverBuildContext,
201220
});
202221

203222
expect(finalWebpackConfig.entry).toEqual(
@@ -210,9 +229,8 @@ describe('webpack config', () => {
210229
it('injects correct code when building client bundle', async () => {
211230
const finalWebpackConfig = await materializeFinalWebpackConfig({
212231
userNextConfig,
213-
userSentryWebpackPluginConfig,
214232
incomingWebpackConfig: clientWebpackConfig,
215-
incomingWebpackBuildContext: { ...buildContext, isServer: false },
233+
incomingWebpackBuildContext: clientBuildContext,
216234
});
217235

218236
expect(finalWebpackConfig.entry).toEqual(
@@ -224,12 +242,11 @@ describe('webpack config', () => {
224242
it('handles non-empty `main.js` entry point', async () => {
225243
const finalWebpackConfig = await materializeFinalWebpackConfig({
226244
userNextConfig,
227-
userSentryWebpackPluginConfig,
228245
incomingWebpackConfig: {
229246
...clientWebpackConfig,
230247
entry: () => Promise.resolve({ main: './src/index.ts', 'main.js': ['sitLieDownRollOver.config.js'] }),
231248
},
232-
incomingWebpackBuildContext: { ...buildContext, isServer: false },
249+
incomingWebpackBuildContext: clientBuildContext,
233250
});
234251

235252
expect(finalWebpackConfig.entry).toEqual(
@@ -244,15 +261,15 @@ describe('webpack config', () => {
244261

245262
describe('Sentry webpack plugin config', () => {
246263
it('includes expected properties', () => {
247-
// pass
264+
// TODO
248265
});
249266

250267
it('preserves unrelated plugin config options', () => {
251-
// pass
268+
// TODO
252269
});
253270

254271
it('warns when overriding certain default values', () => {
255-
// pass
272+
// TODO
256273
});
257274

258275
it("merges default include and ignore/ignoreFile options with user's values", () => {

0 commit comments

Comments
 (0)