@@ -5,26 +5,25 @@ import * as SentryWebpackPlugin from '@sentry/webpack-plugin';
5
5
import {
6
6
BuildContext ,
7
7
EntryPointObject ,
8
+ EntryPointValue ,
8
9
EntryPropertyObject ,
9
10
NextConfigObject ,
10
11
SentryWebpackPluginOptions ,
11
12
WebpackConfigFunction ,
12
13
WebpackConfigObject ,
13
14
WebpackEntryProperty ,
14
15
} 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' ;
21
17
22
18
export { SentryWebpackPlugin } ;
23
19
24
20
// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile
25
21
// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
26
22
// TODO: drop merged keys from override check? `includeDefaults` option?
27
23
24
+ const CLIENT_SDK_CONFIG_FILE = './sentry.client.config.js' ;
25
+ const SERVER_SDK_CONFIG_FILE = './sentry.server.config.js' ;
26
+
28
27
const defaultSentryWebpackPluginOptions = dropUndefinedKeys ( {
29
28
url : process . env . SENTRY_URL ,
30
29
org : process . env . SENTRY_ORG ,
@@ -53,9 +52,11 @@ export function constructWebpackConfigFunction(
53
52
userNextConfig : NextConfigObject = { } ,
54
53
userSentryWebpackPluginOptions : Partial < SentryWebpackPluginOptions > = { } ,
55
54
) : 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 } ;
59
60
60
61
// if we're building server code, store the webpack output path as an env variable, so we know where to look for the
61
62
// webpack-processed version of `sentry.server.config.js` when we need it
@@ -66,7 +67,7 @@ export function constructWebpackConfigFunction(
66
67
// if user has custom webpack config (which always takes the form of a function), run it so we have actual values to
67
68
// work with
68
69
if ( 'webpack' in userNextConfig && typeof userNextConfig . webpack === 'function' ) {
69
- newConfig = userNextConfig . webpack ( newConfig , options ) ;
70
+ newConfig = userNextConfig . webpack ( newConfig , buildContext ) ;
70
71
}
71
72
72
73
// Tell webpack to inject user config files (containing the two `Sentry.init()` calls) into the appropriate output
@@ -78,10 +79,10 @@ export function constructWebpackConfigFunction(
78
79
// will call the callback which will call `f` which will call `x.y`... and on and on. Theoretically this could also
79
80
// be fixed by using `bind`, but this is way simpler.)
80
81
const origEntryProperty = newConfig . entry ;
81
- newConfig . entry = async ( ) => addSentryToEntryProperty ( origEntryProperty , options . isServer ) ;
82
+ newConfig . entry = async ( ) => addSentryToEntryProperty ( origEntryProperty , buildContext ) ;
82
83
83
84
// 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
85
86
? ! userNextConfig . sentry ?. disableServerWebpackPlugin
86
87
: ! userNextConfig . sentry ?. disableClientWebpackPlugin ;
87
88
@@ -92,7 +93,7 @@ export function constructWebpackConfigFunction(
92
93
93
94
// Next doesn't let you change this is dev even if you want to - see
94
95
// https://github.com/vercel/next.js/blob/master/errors/improper-devtool.md
95
- if ( ! options . dev ) {
96
+ if ( ! buildContext . dev ) {
96
97
newConfig . devtool = 'source-map' ;
97
98
}
98
99
@@ -103,8 +104,8 @@ export function constructWebpackConfigFunction(
103
104
// @ts -ignore Our types for the plugin are messed up somehow - TS wants this to be `SentryWebpackPlugin.default`,
104
105
// but that's not actually a thing
105
106
new SentryWebpackPlugin ( {
106
- dryRun : options . dev ,
107
- release : getSentryRelease ( options . buildId ) ,
107
+ dryRun : buildContext . dev ,
108
+ release : getSentryRelease ( buildContext . buildId ) ,
108
109
...defaultSentryWebpackPluginOptions ,
109
110
...userSentryWebpackPluginOptions ,
110
111
} ) ,
@@ -121,27 +122,23 @@ export function constructWebpackConfigFunction(
121
122
* Modify the webpack `entry` property so that the code in `sentry.server.config.js` and `sentry.client.config.js` is
122
123
* included in the the necessary bundles.
123
124
*
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
127
127
* @returns The value which the new `entry` property (which will be a function) will return (TODO: this should return
128
128
* the function, rather than the function's return value)
129
129
*/
130
130
async function addSentryToEntryProperty (
131
- origEntryProperty : WebpackEntryProperty ,
132
- isServer : boolean ,
131
+ currentEntryProperty : WebpackEntryProperty ,
132
+ buildContext : BuildContext ,
133
133
) : Promise < EntryPropertyObject > {
134
134
// The `entry` entry in a webpack config can be a string, array of strings, object, or function. By default, nextjs
135
135
// sets it to an async function which returns the promise of an object of string arrays. Because we don't know whether
136
136
// someone else has come along before us and changed that, we need to check a few things along the way. The one thing
137
137
// we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
138
138
// options. See https://webpack.js.org/configuration/entry-context/#entry.
139
139
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 } ;
145
142
146
143
// Add a new element to the `entry` array, we force webpack to create a bundle out of the user's
147
144
// `sentry.server.config.js` file and output it to `SERVER_INIT_LOCATION`. (See
@@ -152,14 +149,14 @@ async function addSentryToEntryProperty(
152
149
// because that then forces the user into a particular TS config.)
153
150
154
151
// 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 ) {
156
153
// 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
157
154
// `.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 ;
159
156
}
160
157
// On the client, it's sufficient to inject it into the `main` JS code, which is included in every browser page.
161
158
else {
162
- addFileToExistingEntryPoint ( newEntryProperty , 'main' , SENTRY_CLIENT_CONFIG_FILE ) ;
159
+ addFileToExistingEntryPoint ( newEntryProperty , 'main' , CLIENT_SDK_CONFIG_FILE ) ;
163
160
164
161
// To work around a bug in nextjs, we need to ensure that the `main.js` entry is empty (otherwise it'll choose that
165
162
// 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(
195
192
filepath : string ,
196
193
) : void {
197
194
// 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 ;
206
197
207
198
// We inject the user's client config file after the existing code so that the config file has access to
208
199
// `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 [ ] ;
215
209
216
- if ( typeof injectedInto . import === 'string' ) {
217
- importVal = [ injectedInto . import , filepath ] ;
210
+ if ( typeof currentImportValue === 'string' ) {
211
+ newImportValue = [ currentImportValue , filepath ] ;
218
212
} else {
219
- importVal = [ ...injectedInto . import , filepath ] ;
213
+ newImportValue = [ ...currentImportValue , filepath ] ;
220
214
}
221
215
222
- injectedInto = {
223
- ...injectedInto ,
224
- import : importVal ,
216
+ newEntryPoint = {
217
+ ...currentEntryPoint ,
218
+ import : newImportValue ,
225
219
} ;
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 ;
226
230
}
227
231
228
- entryProperty [ entryPointName ] = injectedInto ;
232
+ entryProperty [ entryPointName ] = newEntryPoint ;
229
233
}
230
234
231
235
/**
0 commit comments