@@ -5,26 +5,25 @@ import * as SentryWebpackPlugin from '@sentry/webpack-plugin';
55import {
66 BuildContext ,
77 EntryPointObject ,
8+ EntryPointValue ,
89 EntryPropertyObject ,
910 NextConfigObject ,
1011 SentryWebpackPluginOptions ,
1112 WebpackConfigFunction ,
1213 WebpackConfigObject ,
1314 WebpackEntryProperty ,
1415} from './types' ;
15- import {
16- SENTRY_CLIENT_CONFIG_FILE ,
17- SENTRY_SERVER_CONFIG_FILE ,
18- SERVER_SDK_INIT_PATH ,
19- storeServerConfigFileLocation ,
20- } from './utils' ;
16+ import { SERVER_SDK_INIT_PATH , storeServerConfigFileLocation } from './utils' ;
2117
2218export { SentryWebpackPlugin } ;
2319
2420// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile
2521// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
2622// TODO: drop merged keys from override check? `includeDefaults` option?
2723
24+ const CLIENT_SDK_CONFIG_FILE = './sentry.client.config.js' ;
25+ const SERVER_SDK_CONFIG_FILE = './sentry.server.config.js' ;
26+
2827const defaultSentryWebpackPluginOptions = dropUndefinedKeys ( {
2928 url : process . env . SENTRY_URL ,
3029 org : process . env . SENTRY_ORG ,
@@ -53,9 +52,11 @@ export function constructWebpackConfigFunction(
5352 userNextConfig : NextConfigObject = { } ,
5453 userSentryWebpackPluginOptions : Partial < SentryWebpackPluginOptions > = { } ,
5554) : WebpackConfigFunction {
56- const newWebpackFunction = ( config : WebpackConfigObject , options : BuildContext ) : WebpackConfigObject => {
57- // clone to avoid mutability issues
58- let newConfig = { ...config } ;
55+ // Will be called by nextjs and passed its default webpack configuration and context data about the build (whether
56+ // we're building server or client, whether we're in dev, what version of webpack we're using, etc). Note that
57+ // `incomingConfig` and `buildContext` are referred to as `config` and `options` in the nextjs docs.
58+ const newWebpackFunction = ( incomingConfig : WebpackConfigObject , buildContext : BuildContext ) : WebpackConfigObject => {
59+ let newConfig = { ...incomingConfig } ;
5960
6061 // if we're building server code, store the webpack output path as an env variable, so we know where to look for the
6162 // webpack-processed version of `sentry.server.config.js` when we need it
@@ -66,7 +67,7 @@ export function constructWebpackConfigFunction(
6667 // if user has custom webpack config (which always takes the form of a function), run it so we have actual values to
6768 // work with
6869 if ( 'webpack' in userNextConfig && typeof userNextConfig . webpack === 'function' ) {
69- newConfig = userNextConfig . webpack ( newConfig , options ) ;
70+ newConfig = userNextConfig . webpack ( newConfig , buildContext ) ;
7071 }
7172
7273 // Tell webpack to inject user config files (containing the two `Sentry.init()` calls) into the appropriate output
@@ -78,10 +79,10 @@ export function constructWebpackConfigFunction(
7879 // will call the callback which will call `f` which will call `x.y`... and on and on. Theoretically this could also
7980 // be fixed by using `bind`, but this is way simpler.)
8081 const origEntryProperty = newConfig . entry ;
81- newConfig . entry = async ( ) => addSentryToEntryProperty ( origEntryProperty , options . isServer ) ;
82+ newConfig . entry = async ( ) => addSentryToEntryProperty ( origEntryProperty , buildContext ) ;
8283
8384 // Enable the Sentry plugin (which uploads source maps to Sentry when not in dev) by default
84- const enableWebpackPlugin = options . isServer
85+ const enableWebpackPlugin = buildContext . isServer
8586 ? ! userNextConfig . sentry ?. disableServerWebpackPlugin
8687 : ! userNextConfig . sentry ?. disableClientWebpackPlugin ;
8788
@@ -92,7 +93,7 @@ export function constructWebpackConfigFunction(
9293
9394 // Next doesn't let you change this is dev even if you want to - see
9495 // https://github.com/vercel/next.js/blob/master/errors/improper-devtool.md
95- if ( ! options . dev ) {
96+ if ( ! buildContext . dev ) {
9697 newConfig . devtool = 'source-map' ;
9798 }
9899
@@ -103,8 +104,8 @@ export function constructWebpackConfigFunction(
103104 // @ts -ignore Our types for the plugin are messed up somehow - TS wants this to be `SentryWebpackPlugin.default`,
104105 // but that's not actually a thing
105106 new SentryWebpackPlugin ( {
106- dryRun : options . dev ,
107- release : getSentryRelease ( options . buildId ) ,
107+ dryRun : buildContext . dev ,
108+ release : getSentryRelease ( buildContext . buildId ) ,
108109 ...defaultSentryWebpackPluginOptions ,
109110 ...userSentryWebpackPluginOptions ,
110111 } ) ,
@@ -121,27 +122,23 @@ export function constructWebpackConfigFunction(
121122 * Modify the webpack `entry` property so that the code in `sentry.server.config.js` and `sentry.client.config.js` is
122123 * included in the the necessary bundles.
123124 *
124- * @param origEntryProperty The value of the property before Sentry code has been injected
125- * @param isServer A boolean provided by nextjs indicating whether we're handling the server bundles or the browser
126- * bundles
125+ * @param currentEntryProperty The value of the property before Sentry code has been injected
126+ * @param buildContext Object passed by nextjs containing metadata about the build
127127 * @returns The value which the new `entry` property (which will be a function) will return (TODO: this should return
128128 * the function, rather than the function's return value)
129129 */
130130async function addSentryToEntryProperty (
131- origEntryProperty : WebpackEntryProperty ,
132- isServer : boolean ,
131+ currentEntryProperty : WebpackEntryProperty ,
132+ buildContext : BuildContext ,
133133) : Promise < EntryPropertyObject > {
134134 // The `entry` entry in a webpack config can be a string, array of strings, object, or function. By default, nextjs
135135 // sets it to an async function which returns the promise of an object of string arrays. Because we don't know whether
136136 // someone else has come along before us and changed that, we need to check a few things along the way. The one thing
137137 // we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
138138 // options. See https://webpack.js.org/configuration/entry-context/#entry.
139139
140- let newEntryProperty = origEntryProperty ;
141- if ( typeof origEntryProperty === 'function' ) {
142- newEntryProperty = await origEntryProperty ( ) ;
143- }
144- newEntryProperty = newEntryProperty as EntryPropertyObject ;
140+ const newEntryProperty =
141+ typeof currentEntryProperty === 'function' ? await currentEntryProperty ( ) : { ...currentEntryProperty } ;
145142
146143 // Add a new element to the `entry` array, we force webpack to create a bundle out of the user's
147144 // `sentry.server.config.js` file and output it to `SERVER_INIT_LOCATION`. (See
@@ -152,14 +149,14 @@ async function addSentryToEntryProperty(
152149 // because that then forces the user into a particular TS config.)
153150
154151 // On the server, create a separate bundle, as there's no one entry point depended on by all the others
155- if ( isServer ) {
152+ if ( buildContext . isServer ) {
156153 // slice off the final `.js` since webpack is going to add it back in for us, and we don't want to end up with
157154 // `.js.js` as the extension
158- newEntryProperty [ SERVER_SDK_INIT_PATH . slice ( 0 , - 3 ) ] = SENTRY_SERVER_CONFIG_FILE ;
155+ newEntryProperty [ SERVER_SDK_INIT_PATH . slice ( 0 , - 3 ) ] = SERVER_SDK_CONFIG_FILE ;
159156 }
160157 // On the client, it's sufficient to inject it into the `main` JS code, which is included in every browser page.
161158 else {
162- addFileToExistingEntryPoint ( newEntryProperty , 'main' , SENTRY_CLIENT_CONFIG_FILE ) ;
159+ addFileToExistingEntryPoint ( newEntryProperty , 'main' , CLIENT_SDK_CONFIG_FILE ) ;
163160
164161 // To work around a bug in nextjs, we need to ensure that the `main.js` entry is empty (otherwise it'll choose that
165162 // over `main` and we'll lose the change we just made). In case some other library has put something into it, copy
@@ -195,37 +192,44 @@ function addFileToExistingEntryPoint(
195192 filepath : string ,
196193) : void {
197194 // can be a string, array of strings, or object whose `import` property is one of those two
198- let injectedInto = entryProperty [ entryPointName ] ;
199-
200- // Sometimes especially for older next.js versions it happens we don't have an entry point
201- if ( ! injectedInto ) {
202- // eslint-disable-next-line no-console
203- console . error ( `[Sentry] Can't inject ${ filepath } , no entrypoint is defined.` ) ;
204- return ;
205- }
195+ const currentEntryPoint = entryProperty [ entryPointName ] ;
196+ let newEntryPoint : EntryPointValue ;
206197
207198 // We inject the user's client config file after the existing code so that the config file has access to
208199 // `publicRuntimeConfig`. See https://github.com/getsentry/sentry-javascript/issues/3485
209- if ( typeof injectedInto === 'string' ) {
210- injectedInto = [ injectedInto , filepath ] ;
211- } else if ( Array . isArray ( injectedInto ) ) {
212- injectedInto = [ ...injectedInto , filepath ] ;
213- } else {
214- let importVal : string | string [ ] ;
200+ if ( typeof currentEntryPoint === 'string' ) {
201+ newEntryPoint = [ currentEntryPoint , filepath ] ;
202+ } else if ( Array . isArray ( currentEntryPoint ) ) {
203+ newEntryPoint = [ ...currentEntryPoint , filepath ] ;
204+ }
205+ // descriptor object (webpack 5+)
206+ else if ( typeof currentEntryPoint === 'object' && 'import' in currentEntryPoint ) {
207+ const currentImportValue = currentEntryPoint . import ;
208+ let newImportValue : string | string [ ] ;
215209
216- if ( typeof injectedInto . import === 'string' ) {
217- importVal = [ injectedInto . import , filepath ] ;
210+ if ( typeof currentImportValue === 'string' ) {
211+ newImportValue = [ currentImportValue , filepath ] ;
218212 } else {
219- importVal = [ ...injectedInto . import , filepath ] ;
213+ newImportValue = [ ...currentImportValue , filepath ] ;
220214 }
221215
222- injectedInto = {
223- ...injectedInto ,
224- import : importVal ,
216+ newEntryPoint = {
217+ ...currentEntryPoint ,
218+ import : newImportValue ,
225219 } ;
220+ } else {
221+ // mimic the logger prefix in order to use `console.warn` (which will always be printed, regardless of SDK settings)
222+ // eslint-disable-next-line no-console
223+ console . error (
224+ 'Sentry Logger [Error]:' ,
225+ `Could not inject SDK initialization code into entry point ${ entryPointName } , as it is not a recognized format.\n` ,
226+ `Expected: string | Array<string> | { [key:string]: any, import: string | Array<string> }\n` ,
227+ `Got: ${ currentEntryPoint } ` ,
228+ ) ;
229+ return ;
226230 }
227231
228- entryProperty [ entryPointName ] = injectedInto ;
232+ entryProperty [ entryPointName ] = newEntryPoint ;
229233}
230234
231235/**
0 commit comments