-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
ref(nextjs): Pre-disable-plugin-option config cleanup #3770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
08d9a62
f1de2aa
e55c2d1
b942dfd
ede7200
4846ede
55f4c51
5e8a390
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,17 +12,18 @@ export function withSentryConfig( | |
| userNextConfig: ExportedNextConfig = {}, | ||
| userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {}, | ||
| ): NextConfigFunction | NextConfigObject { | ||
| const newWebpackConfig = constructWebpackConfigFunction(userNextConfig, userSentryWebpackPluginOptions); | ||
|
|
||
| // If the user has passed us a function, we need to return a function, so that we have access to `phase` and | ||
| // `defaults` in order to pass them along to the user's function | ||
| if (typeof userNextConfig === 'function') { | ||
| return (phase: string, defaults: { defaultConfig: { [key: string]: unknown } }): NextConfigObject => ({ | ||
| ...userNextConfig(phase, defaults), | ||
| webpack: newWebpackConfig, | ||
| }); | ||
| return function(phase: string, defaults: { defaultConfig: { [key: string]: unknown } }): NextConfigObject { | ||
| const materializedUserNextConfig = userNextConfig(phase, defaults); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a test case showing that we properly pass through phase?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍🏻 |
||
| return { | ||
| ...materializedUserNextConfig, | ||
| webpack: constructWebpackConfigFunction(materializedUserNextConfig, userSentryWebpackPluginOptions), | ||
| }; | ||
| }; | ||
| } | ||
|
|
||
| // Otherwise, we can just merge their config with ours and return an object. | ||
| return { ...userNextConfig, webpack: newWebpackConfig }; | ||
| return { ...userNextConfig, webpack: constructWebpackConfigFunction(userNextConfig, userSentryWebpackPluginOptions) }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -6,7 +6,7 @@ import { | |||
| BuildContext, | ||||
| EntryPointObject, | ||||
| EntryPropertyObject, | ||||
| ExportedNextConfig, | ||||
| NextConfigObject, | ||||
| SentryWebpackPluginOptions, | ||||
| WebpackConfigFunction, | ||||
| WebpackConfigObject, | ||||
|
|
@@ -50,22 +50,23 @@ const defaultSentryWebpackPluginOptions = dropUndefinedKeys({ | |||
| * @returns The function to set as the nextjs config's `webpack` value | ||||
| */ | ||||
| export function constructWebpackConfigFunction( | ||||
| userNextConfig: ExportedNextConfig = {}, | ||||
| userNextConfig: NextConfigObject = {}, | ||||
| userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {}, | ||||
| ): WebpackConfigFunction { | ||||
| const newWebpackFunction = (config: WebpackConfigObject, options: BuildContext): WebpackConfigObject => { | ||||
| // clone to avoid mutability issues | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm of the opinion that we can remove this comment, I think most readers should be aware of why we spread into a new object.
Suggested change
|
||||
| let newConfig = { ...config }; | ||||
|
|
||||
| // if we're building server code, store the webpack output path as an env variable, so we know where to look for the | ||||
| // webpack-processed version of `sentry.server.config.js` when we need it | ||||
| if (config.target === 'node') { | ||||
| storeServerConfigFileLocation(config); | ||||
| if (newConfig.target === 'node') { | ||||
| storeServerConfigFileLocation(newConfig); | ||||
| } | ||||
|
|
||||
| let newConfig = config; | ||||
|
|
||||
| // if user has custom webpack config (which always takes the form of a function), run it so we have actual values to | ||||
| // work with | ||||
| if ('webpack' in userNextConfig && typeof userNextConfig.webpack === 'function') { | ||||
| newConfig = userNextConfig.webpack(config, options); | ||||
| newConfig = userNextConfig.webpack(newConfig, options); | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be running
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just wanna avoid doing
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Either way works. I was thinking of cloning as a first step to immediately get out of the way.
Given that we don't know if we're going to reassign it or not, the only way around using 'let' would be to move the Curiosity: Is it just out of a general principle that immutability > mutability that you want to avoid
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's more specific to this instance. I made the comment because we are passing around config objects everywhere in this webpack logic, and I don't want someone to accidentally introduce a bug in future refactors or changes. |
||||
| } | ||||
|
|
||||
| // Ensure quality source maps in production. (Source maps aren't uploaded in dev, and besides, Next doesn't let you | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,15 +7,16 @@ import { | |
| SentryWebpackPluginOptions, | ||
| WebpackConfigObject, | ||
| } from '../src/config/types'; | ||
| import { SENTRY_SERVER_CONFIG_FILE, SERVER_SDK_INIT_PATH, storeServerConfigFileLocation } from '../src/config/utils'; | ||
| import { SENTRY_SERVER_CONFIG_FILE, SERVER_SDK_INIT_PATH } from '../src/config/utils'; | ||
| import { constructWebpackConfigFunction, SentryWebpackPlugin } from '../src/config/webpack'; | ||
|
|
||
| // mock `storeServerConfigFileLocation` in order to make it a no-op when necessary | ||
| jest.mock('../src/config/utils', () => { | ||
| const original = jest.requireActual('../src/config/utils'); | ||
| return { | ||
| ...original, | ||
| storeServerConfigFileLocation: jest.fn().mockImplementation(original.setRuntimeEnvVars), | ||
| // nuke this so it won't try to look for our dummy paths | ||
| storeServerConfigFileLocation: jest.fn(), | ||
| }; | ||
| }); | ||
|
|
||
|
|
@@ -34,6 +35,10 @@ const userNextConfig = { | |
| }; | ||
| const userSentryWebpackPluginConfig = { org: 'squirrelChasers', project: 'simulator', include: './thirdPartyMaps' }; | ||
|
|
||
| /** mocks of the arguments passed to the result of `withSentryConfig` (when it's a function) */ | ||
| const runtimePhase = 'puppy-phase-chew-everything-in-sight'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Can we use a phase from
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ¯\(ツ)/¯ We can, if you think it's important. Since they're values which are merely passed from our function to the user's function, they can really be anything. I find writing (and reading) tests to be kinda uninteresting, so I try to have some fun with it. Regardless, I'm going to merge this now, in the interest of moving on. |
||
| const defaultNextConfig = { nappingHoursPerDay: 20, oversizeFeet: true, shouldChaseTail: true }; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Can we make this a more realistic config? |
||
|
|
||
| /** mocks of the arguments passed to `nextConfig.webpack` */ | ||
| const serverWebpackConfig = { | ||
| entry: () => Promise.resolve({ 'pages/api/dogs/[name]': 'private-next-pages/api/dogs/[name].js' }), | ||
|
|
@@ -47,7 +52,8 @@ const clientWebpackConfig = { | |
| target: 'web', | ||
| context: '/Users/Maisey/projects/squirrelChasingSimulator', | ||
| }; | ||
| const buildContext = { isServer: true, dev: false, buildId: 'doGsaREgReaT' }; | ||
| const serverBuildContext = { isServer: true, dev: false, buildId: 'doGsaREgReaT' }; | ||
| const clientBuildContext = { isServer: false, dev: false, buildId: 'doGsaREgReaT' }; | ||
|
|
||
| /** | ||
| * 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' }; | |
| */ | ||
| function materializeFinalNextConfig( | ||
| userNextConfig: ExportedNextConfig, | ||
| userSentryWebpackPluginConfig: SentryWebpackPluginOptions, | ||
| userSentryWebpackPluginConfig?: SentryWebpackPluginOptions, | ||
| ): NextConfigObject { | ||
| const sentrifiedConfig = withSentryConfig(userNextConfig, userSentryWebpackPluginConfig); | ||
| let finalConfigValues = sentrifiedConfig; | ||
|
|
||
| if (typeof sentrifiedConfig === 'function') { | ||
| // for some reason TS won't recognize that `finalConfigValues` is now a NextConfigObject, which is why the cast | ||
| // below is necessary | ||
| finalConfigValues = sentrifiedConfig('phase-production-build', { | ||
| defaultConfig: {}, | ||
| finalConfigValues = sentrifiedConfig(runtimePhase, { | ||
| defaultConfig: defaultNextConfig, | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -92,14 +98,25 @@ function materializeFinalNextConfig( | |
| */ | ||
| async function materializeFinalWebpackConfig(options: { | ||
| userNextConfig: ExportedNextConfig; | ||
| userSentryWebpackPluginConfig: SentryWebpackPluginOptions; | ||
| userSentryWebpackPluginConfig?: SentryWebpackPluginOptions; | ||
| incomingWebpackConfig: WebpackConfigObject; | ||
| incomingWebpackBuildContext: BuildContext; | ||
| }): Promise<WebpackConfigObject> { | ||
| const { userNextConfig, userSentryWebpackPluginConfig, incomingWebpackConfig, incomingWebpackBuildContext } = options; | ||
|
|
||
| // if the user's next config is a function, run it so we have access to the values | ||
| const materializedUserNextConfig = | ||
| typeof userNextConfig === 'function' | ||
| ? userNextConfig('phase-production-build', { | ||
| defaultConfig: {}, | ||
| }) | ||
| : userNextConfig; | ||
|
|
||
| // get the webpack config function we'd normally pass back to next | ||
| const webpackConfigFunction = constructWebpackConfigFunction(userNextConfig, userSentryWebpackPluginConfig); | ||
| const webpackConfigFunction = constructWebpackConfigFunction( | ||
| materializedUserNextConfig, | ||
| userSentryWebpackPluginConfig, | ||
| ); | ||
|
|
||
| // call it to get concrete values for comparison | ||
| const finalWebpackConfigValue = webpackConfigFunction(incomingWebpackConfig, incomingWebpackBuildContext); | ||
|
|
@@ -111,7 +128,7 @@ async function materializeFinalWebpackConfig(options: { | |
|
|
||
| describe('withSentryConfig', () => { | ||
| it('includes expected properties', () => { | ||
| const finalConfig = materializeFinalNextConfig(userNextConfig, userSentryWebpackPluginConfig); | ||
| const finalConfig = materializeFinalNextConfig(userNextConfig); | ||
|
|
||
| expect(finalConfig).toEqual( | ||
| expect.objectContaining({ | ||
|
|
@@ -121,13 +138,13 @@ describe('withSentryConfig', () => { | |
| }); | ||
|
|
||
| it('preserves unrelated next config options', () => { | ||
| const finalConfig = materializeFinalNextConfig(userNextConfig, userSentryWebpackPluginConfig); | ||
| const finalConfig = materializeFinalNextConfig(userNextConfig); | ||
|
|
||
| expect(finalConfig.publicRuntimeConfig).toEqual(userNextConfig.publicRuntimeConfig); | ||
| }); | ||
|
|
||
| it("works when user's overall config is an object", () => { | ||
| const finalConfig = materializeFinalNextConfig(userNextConfig, userSentryWebpackPluginConfig); | ||
| const finalConfig = materializeFinalNextConfig(userNextConfig); | ||
|
|
||
| expect(finalConfig).toEqual( | ||
| expect.objectContaining({ | ||
|
|
@@ -140,7 +157,7 @@ describe('withSentryConfig', () => { | |
| it("works when user's overall config is a function", () => { | ||
| const userNextConfigFunction = () => userNextConfig; | ||
|
|
||
| const finalConfig = materializeFinalNextConfig(userNextConfigFunction, userSentryWebpackPluginConfig); | ||
| const finalConfig = materializeFinalNextConfig(userNextConfigFunction); | ||
|
|
||
| expect(finalConfig).toEqual( | ||
| expect.objectContaining({ | ||
|
|
@@ -149,20 +166,24 @@ describe('withSentryConfig', () => { | |
| }), | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('webpack config', () => { | ||
| beforeEach(() => { | ||
| // nuke this so it won't try to look for our dummy paths | ||
| (storeServerConfigFileLocation as jest.Mock).mockImplementationOnce(() => undefined); | ||
| it('correctly passes `phase` and `defaultConfig` through to functional `userNextConfig`', () => { | ||
| const userNextConfigFunction = jest.fn().mockReturnValue(userNextConfig); | ||
|
|
||
| materializeFinalNextConfig(userNextConfigFunction); | ||
|
|
||
| expect(userNextConfigFunction).toHaveBeenCalledWith(runtimePhase, { | ||
| defaultConfig: defaultNextConfig, | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('webpack config', () => { | ||
| it('includes expected properties', async () => { | ||
| const finalWebpackConfig = await materializeFinalWebpackConfig({ | ||
| userNextConfig, | ||
| userSentryWebpackPluginConfig, | ||
| incomingWebpackConfig: serverWebpackConfig, | ||
| incomingWebpackBuildContext: buildContext, | ||
| incomingWebpackBuildContext: serverBuildContext, | ||
| }); | ||
|
|
||
| expect(finalWebpackConfig).toEqual( | ||
|
|
@@ -177,14 +198,13 @@ describe('webpack config', () => { | |
| it('preserves unrelated webpack config options', async () => { | ||
| const finalWebpackConfig = await materializeFinalWebpackConfig({ | ||
| userNextConfig, | ||
| userSentryWebpackPluginConfig, | ||
| incomingWebpackConfig: serverWebpackConfig, | ||
| incomingWebpackBuildContext: buildContext, | ||
| incomingWebpackBuildContext: serverBuildContext, | ||
| }); | ||
|
|
||
| // Run the user's webpack config function, so we can check the results against ours. Delete `entry` because we'll | ||
| // test it separately, and besides, it's one that we *should* be overwriting. | ||
| const materializedUserWebpackConfig = userNextConfig.webpack(serverWebpackConfig, buildContext); | ||
| const materializedUserWebpackConfig = userNextConfig.webpack(serverWebpackConfig, serverBuildContext); | ||
| // @ts-ignore `entry` may be required in real life, but we don't need it for our tests | ||
| delete materializedUserWebpackConfig.entry; | ||
|
|
||
|
|
@@ -195,9 +215,8 @@ describe('webpack config', () => { | |
| it('injects correct code when building server bundle', async () => { | ||
| const finalWebpackConfig = await materializeFinalWebpackConfig({ | ||
| userNextConfig, | ||
| userSentryWebpackPluginConfig, | ||
| incomingWebpackConfig: serverWebpackConfig, | ||
| incomingWebpackBuildContext: buildContext, | ||
| incomingWebpackBuildContext: serverBuildContext, | ||
| }); | ||
|
|
||
| expect(finalWebpackConfig.entry).toEqual( | ||
|
|
@@ -210,9 +229,8 @@ describe('webpack config', () => { | |
| it('injects correct code when building client bundle', async () => { | ||
| const finalWebpackConfig = await materializeFinalWebpackConfig({ | ||
| userNextConfig, | ||
| userSentryWebpackPluginConfig, | ||
| incomingWebpackConfig: clientWebpackConfig, | ||
| incomingWebpackBuildContext: { ...buildContext, isServer: false }, | ||
| incomingWebpackBuildContext: clientBuildContext, | ||
| }); | ||
|
|
||
| expect(finalWebpackConfig.entry).toEqual( | ||
|
|
@@ -224,12 +242,11 @@ describe('webpack config', () => { | |
| it('handles non-empty `main.js` entry point', async () => { | ||
| const finalWebpackConfig = await materializeFinalWebpackConfig({ | ||
| userNextConfig, | ||
| userSentryWebpackPluginConfig, | ||
| incomingWebpackConfig: { | ||
| ...clientWebpackConfig, | ||
| entry: () => Promise.resolve({ main: './src/index.ts', 'main.js': ['sitLieDownRollOver.config.js'] }), | ||
| }, | ||
| incomingWebpackBuildContext: { ...buildContext, isServer: false }, | ||
| incomingWebpackBuildContext: clientBuildContext, | ||
| }); | ||
|
|
||
| expect(finalWebpackConfig.entry).toEqual( | ||
|
|
@@ -244,15 +261,15 @@ describe('webpack config', () => { | |
|
|
||
| describe('Sentry webpack plugin config', () => { | ||
| it('includes expected properties', () => { | ||
| // pass | ||
| // TODO | ||
| }); | ||
|
|
||
| it('preserves unrelated plugin config options', () => { | ||
| // pass | ||
| // TODO | ||
| }); | ||
|
|
||
| it('warns when overriding certain default values', () => { | ||
| // pass | ||
| // TODO | ||
| }); | ||
|
|
||
| it("merges default include and ignore/ignoreFile options with user's values", () => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: prefer an arrow function if we are just returning an anonymous func
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had one, then this
returnwas getting visually muddied with thereturninside the returned function, so I changed it to this, to make it clearer/easier to differentiate.