Skip to content

Commit f1e59f9

Browse files
authored
fix(nextjs): Apply Webpack configuration in dev mode (#6291)
1 parent 690f74f commit f1e59f9

File tree

6 files changed

+37
-28
lines changed

6 files changed

+37
-28
lines changed

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isBuild } from '../utils/isBuild';
1+
import { NEXT_PHASE_DEVELOPMENT_SERVER, NEXT_PHASE_PRODUCTION_BUILD } from '../utils/phases';
22
import type {
33
ExportedNextConfig,
44
NextConfigFunction,
@@ -18,23 +18,20 @@ export function withSentryConfig(
1818
exportedUserNextConfig: ExportedNextConfig = {},
1919
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {},
2020
): NextConfigFunction | NextConfigObject {
21-
// If the user has passed us a function, we need to return a function, so that we have access to `phase` and
22-
// `defaults` in order to pass them along to the user's function
23-
if (typeof exportedUserNextConfig === 'function') {
24-
return function (phase: string, defaults: { defaultConfig: NextConfigObject }): NextConfigObject {
21+
return function (phase: string, defaults: { defaultConfig: NextConfigObject }): NextConfigObject {
22+
if (typeof exportedUserNextConfig === 'function') {
2523
const userNextConfigObject = exportedUserNextConfig(phase, defaults);
26-
27-
return getFinalConfigObject(userNextConfigObject, userSentryWebpackPluginOptions);
28-
};
29-
}
30-
31-
// Otherwise, we can just merge their config with ours and return an object.
32-
return getFinalConfigObject(exportedUserNextConfig, userSentryWebpackPluginOptions);
24+
return getFinalConfigObject(phase, userNextConfigObject, userSentryWebpackPluginOptions);
25+
} else {
26+
return getFinalConfigObject(phase, exportedUserNextConfig, userSentryWebpackPluginOptions);
27+
}
28+
};
3329
}
3430

3531
// Modify the materialized object form of the user's next config by deleting the `sentry` property and wrapping the
3632
// `webpack` property
3733
function getFinalConfigObject(
34+
phase: string,
3835
incomingUserNextConfigObject: NextConfigObjectWithSentry,
3936
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions>,
4037
): NextConfigObject {
@@ -48,8 +45,8 @@ function getFinalConfigObject(
4845

4946
// In order to prevent all of our build-time code from being bundled in people's route-handling serverless functions,
5047
// we exclude `webpack.ts` and all of its dependencies from nextjs's `@vercel/nft` filetracing. We therefore need to
51-
// make sure that we only require it at build time.
52-
if (isBuild()) {
48+
// make sure that we only require it at build time or in development mode.
49+
if (phase === NEXT_PHASE_PRODUCTION_BUILD || phase === NEXT_PHASE_DEVELOPMENT_SERVER) {
5350
// eslint-disable-next-line @typescript-eslint/no-var-requires
5451
const { constructWebpackConfigFunction } = require('./webpack');
5552
return {

packages/nextjs/src/utils/isBuild.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { NEXT_PHASE_PRODUCTION_BUILD } from './phases';
2+
13
/**
24
* Decide if the currently running process is part of the build phase or happening at runtime.
35
*/
@@ -18,7 +20,7 @@ export function isBuild(): boolean {
1820
process.env.SENTRY_BUILD_PHASE ||
1921
// This is set by next, but not until partway through the build process, which is why we need the above checks. That
2022
// said, in case this function isn't called until we're in a child process, it can serve as a good backup.
21-
process.env.NEXT_PHASE === 'phase-production-build'
23+
process.env.NEXT_PHASE === NEXT_PHASE_PRODUCTION_BUILD
2224
) {
2325
process.env.SENTRY_BUILD_PHASE = 'true';
2426
return true;

packages/nextjs/src/utils/phases.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const NEXT_PHASE_PRODUCTION_BUILD = 'phase-production-build';
2+
export const NEXT_PHASE_PRODUCTION_SERVER = 'phase-production-server';
3+
export const NEXT_PHASE_DEVELOPMENT_SERVER = 'phase-development-server';

packages/nextjs/test/config/fixtures.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ process.env.SENTRY_AUTH_TOKEN = 'dogsarebadatkeepingsecrets';
3131
process.env.SENTRY_RELEASE = 'doGsaREgReaT';
3232

3333
/** Mocks of the arguments passed to the result of `withSentryConfig` (when it's a function). */
34-
export const runtimePhase = 'ball-fetching';
34+
export const defaultRuntimePhase = 'ball-fetching';
3535
// `defaultConfig` is the defaults for all nextjs options (we don't use these at all in the tests, so for our purposes
3636
// here the values don't matter)
3737
export const defaultsObject = { defaultConfig: {} as NextConfigObject };

packages/nextjs/test/config/testUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '../../src/config/types';
1111
import { constructWebpackConfigFunction, SentryWebpackPlugin } from '../../src/config/webpack';
1212
import { withSentryConfig } from '../../src/config/withSentryConfig';
13-
import { defaultsObject, runtimePhase } from './fixtures';
13+
import { defaultRuntimePhase, defaultsObject } from './fixtures';
1414

1515
/**
1616
* Derive the final values of all next config options, by first applying `withSentryConfig` and then, if it returns a
@@ -25,14 +25,15 @@ import { defaultsObject, runtimePhase } from './fixtures';
2525
export function materializeFinalNextConfig(
2626
exportedNextConfig: ExportedNextConfig,
2727
userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>,
28+
runtimePhase?: string,
2829
): NextConfigObject {
2930
const sentrifiedConfig = withSentryConfig(exportedNextConfig, userSentryWebpackPluginConfig);
3031
let finalConfigValues = sentrifiedConfig;
3132

3233
if (typeof sentrifiedConfig === 'function') {
3334
// for some reason TS won't recognize that `finalConfigValues` is now a NextConfigObject, which is why the cast
3435
// below is necessary
35-
finalConfigValues = sentrifiedConfig(runtimePhase, defaultsObject);
36+
finalConfigValues = sentrifiedConfig(runtimePhase ?? defaultRuntimePhase, defaultsObject);
3637
}
3738

3839
return finalConfigValues as NextConfigObject;

packages/nextjs/test/config/withSentryConfig.test.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import * as isBuildModule from '../../src/utils/isBuild';
2-
import { defaultsObject, exportedNextConfig, runtimePhase, userNextConfig } from './fixtures';
1+
import { defaultRuntimePhase, defaultsObject, exportedNextConfig, userNextConfig } from './fixtures';
32
import { materializeFinalNextConfig } from './testUtils';
43

5-
const isBuildSpy = jest.spyOn(isBuildModule, 'isBuild').mockReturnValue(true);
6-
74
describe('withSentryConfig', () => {
85
it('includes expected properties', () => {
96
const finalConfig = materializeFinalNextConfig(exportedNextConfig);
@@ -50,7 +47,7 @@ describe('withSentryConfig', () => {
5047

5148
materializeFinalNextConfig(exportedNextConfigFunction);
5249

53-
expect(exportedNextConfigFunction).toHaveBeenCalledWith(runtimePhase, defaultsObject);
50+
expect(exportedNextConfigFunction).toHaveBeenCalledWith(defaultRuntimePhase, defaultsObject);
5451
});
5552

5653
it('removes `sentry` property', () => {
@@ -71,25 +68,34 @@ describe('withSentryConfig', () => {
7168
// time, but the spy belongs to the first instance of the module and therefore never registers a call. Thus we have
7269
// to test whether or not the file is required instead.
7370

74-
it('imports from `webpack.ts` if `isBuild` returns true', () => {
71+
it('imports from `webpack.ts` if build phase is "phase-production-build"', () => {
7572
jest.isolateModules(() => {
7673
// In case this is still set from elsewhere, reset it
7774
delete (global as any)._sentryWebpackModuleLoaded;
7875

79-
materializeFinalNextConfig(exportedNextConfig);
76+
materializeFinalNextConfig(exportedNextConfig, undefined, 'phase-production-build');
8077

8178
expect((global as any)._sentryWebpackModuleLoaded).toBe(true);
8279
});
8380
});
8481

85-
it("doesn't import from `webpack.ts` if `isBuild` returns false", () => {
82+
it('imports from `webpack.ts` if build phase is "phase-development-server"', () => {
8683
jest.isolateModules(() => {
87-
isBuildSpy.mockReturnValueOnce(false);
84+
// In case this is still set from elsewhere, reset it
85+
delete (global as any)._sentryWebpackModuleLoaded;
8886

87+
materializeFinalNextConfig(exportedNextConfig, undefined, 'phase-production-build');
88+
89+
expect((global as any)._sentryWebpackModuleLoaded).toBe(true);
90+
});
91+
});
92+
93+
it('Doesn\'t import from `webpack.ts` if build phase is "phase-production-server"', () => {
94+
jest.isolateModules(() => {
8995
// In case this is still set from elsewhere, reset it
9096
delete (global as any)._sentryWebpackModuleLoaded;
9197

92-
materializeFinalNextConfig(exportedNextConfig);
98+
materializeFinalNextConfig(exportedNextConfig, undefined, 'phase-production-server');
9399

94100
expect((global as any)._sentryWebpackModuleLoaded).toBeUndefined();
95101
});

0 commit comments

Comments
 (0)