@@ -3,33 +3,36 @@ import { logger } from '@sentry/utils';
3
3
import defaultWebpackPlugin , { SentryCliPluginOptions } from '@sentry/webpack-plugin' ;
4
4
import * as SentryWebpackPlugin from '@sentry/webpack-plugin' ;
5
5
6
+ const SENTRY_CLIENT_CONFIG_FILE = './sentry.client.config.js' ;
7
+ const SENTRY_SERVER_CONFIG_FILE = './sentry.server.config.js' ;
8
+ // this is where the transpiled/bundled version of `USER_SERVER_CONFIG_FILE` will end up
9
+ export const SERVER_INIT_OUTPUT_LOCATION = 'sentry/serverInit' ;
10
+
6
11
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7
12
type PlainObject < T = any > = { [ key : string ] : T } ;
8
13
9
- // Man are these types hard to name well. "Entry" = an item in some collection of items, but in our case, one of the
10
- // things we're worried about here is property (entry) in an object called... entry. So henceforth, the specific
11
- // property we're modifying is going to be known as an EntryProperty.
12
-
13
14
// The function which is ultimately going to be exported from `next.config.js` under the name `webpack`
14
15
type WebpackExport = ( config : WebpackConfig , options : WebpackOptions ) => WebpackConfig ;
15
16
16
17
// The two arguments passed to the exported `webpack` function, as well as the thing it returns
17
- type WebpackConfig = { devtool : string ; plugins : PlainObject [ ] ; entry : EntryProperty } ;
18
+ type WebpackConfig = { devtool : string ; plugins : PlainObject [ ] ; entry : EntryProperty ; output : { path : string } } ;
18
19
type WebpackOptions = { dev : boolean ; isServer : boolean ; buildId : string } ;
19
20
20
21
// For our purposes, the value for `entry` is either an object, or a function which returns such an object
21
22
type EntryProperty = ( ( ) => Promise < EntryPropertyObject > ) | EntryPropertyObject ;
22
-
23
23
// Each value in that object is either a string representing a single entry point, an array of such strings, or an
24
24
// object containing either of those, along with other configuration options. In that third case, the entry point(s) are
25
25
// listed under the key `import`.
26
- type EntryPropertyObject = PlainObject < string | Array < string > | EntryPointObject > ;
26
+ type EntryPropertyObject = PlainObject < string > | PlainObject < Array < string > > | PlainObject < EntryPointObject > ;
27
27
type EntryPointObject = { import : string | Array < string > } ;
28
28
29
- const sentryClientConfig = './sentry.client.config.js' ;
30
- const sentryServerConfig = './sentry.server.config.js' ;
31
-
32
- /** Add a file (`injectee`) to a given element (`injectionPoint`) of the `entry` property */
29
+ /**
30
+ * Add a file to a specific element of the given `entry` webpack config property.
31
+ *
32
+ * @param entryProperty The existing `entry` config object
33
+ * @param injectionPoint The key where the file should be injected
34
+ * @param injectee The path to the injected file
35
+ */
33
36
const _injectFile = ( entryProperty : EntryPropertyObject , injectionPoint : string , injectee : string ) : void => {
34
37
// can be a string, array of strings, or object whose `import` property is one of those two
35
38
let injectedInto = entryProperty [ injectionPoint ] ;
@@ -41,21 +44,19 @@ const _injectFile = (entryProperty: EntryPropertyObject, injectionPoint: string,
41
44
return ;
42
45
}
43
46
44
- // In case we inject our client config, we need to add it after the frontend code
45
- // otherwise the runtime config isn't loaded. See: https://github.com/getsentry/sentry-javascript/issues/3485
46
- const isClient = injectee === sentryClientConfig ;
47
-
47
+ // We inject the user's client config file after the existing code so that the config file has access to
48
+ // `publicRuntimeConfig`. See https://github.com/getsentry/sentry-javascript/issues/3485
48
49
if ( typeof injectedInto === 'string' ) {
49
- injectedInto = isClient ? [ injectedInto , injectee ] : [ injectee , injectedInto ] ;
50
+ injectedInto = [ injectedInto , injectee ] ;
50
51
} else if ( Array . isArray ( injectedInto ) ) {
51
- injectedInto = isClient ? [ ...injectedInto , injectee ] : [ injectee , ... injectedInto ] ;
52
+ injectedInto = [ ...injectedInto , injectee ] ;
52
53
} else {
53
- let importVal : string | string [ ] | EntryPointObject ;
54
+ let importVal : string | string [ ] ;
55
+
54
56
if ( typeof injectedInto . import === 'string' ) {
55
- importVal = isClient ? [ injectedInto . import , injectee ] : [ injectee , injectedInto . import ] ;
57
+ importVal = [ injectedInto . import , injectee ] ;
56
58
} else {
57
- // If it's not a string, the inner value is an array
58
- importVal = isClient ? [ ...injectedInto . import , injectee ] : [ injectee , ...injectedInto . import ] ;
59
+ importVal = [ ...injectedInto . import , injectee ] ;
59
60
}
60
61
61
62
injectedInto = {
@@ -68,8 +69,6 @@ const _injectFile = (entryProperty: EntryPropertyObject, injectionPoint: string,
68
69
} ;
69
70
70
71
const injectSentry = async ( origEntryProperty : EntryProperty , isServer : boolean ) : Promise < EntryProperty > => {
71
- // Out of the box, nextjs uses the `() => Promise<EntryPropertyObject>)` flavor of EntryProperty, where the returned
72
- // object has string arrays for values.
73
72
// The `entry` entry in a webpack config can be a string, array of strings, object, or function. By default, nextjs
74
73
// sets it to an async function which returns the promise of an object of string arrays. Because we don't know whether
75
74
// someone else has come along before us and changed that, we need to check a few things along the way. The one thing
@@ -80,19 +79,19 @@ const injectSentry = async (origEntryProperty: EntryProperty, isServer: boolean)
80
79
newEntryProperty = await origEntryProperty ( ) ;
81
80
}
82
81
newEntryProperty = newEntryProperty as EntryPropertyObject ;
83
- // On the server, we need to inject the SDK into both into the base page (`_document`) and into individual API routes
84
- // (which have no common base).
82
+ // Add a new element to the `entry` array, we force webpack to create a bundle out of the user's
83
+ // `sentry.server.config.js` file and output it to `SERVER_INIT_LOCATION`. (See
84
+ // https://webpack.js.org/guides/code-splitting/#entry-points.) We do this so that the user's config file is run
85
+ // through babel (and any other processors through which next runs the rest of the user-provided code - pages, API
86
+ // routes, etc.). Specifically, we need any ESM-style `import` code to get transpiled into ES5, so that we can call
87
+ // `require()` on the resulting file when we're instrumenting the sesrver. (We can't use a dynamic import there
88
+ // because that then forces the user into a particular TS config.)
85
89
if ( isServer ) {
86
- Object . keys ( newEntryProperty ) . forEach ( key => {
87
- if ( key === 'pages/_document' || key . includes ( 'pages/api' ) ) {
88
- // for some reason, because we're now in a function, we have to cast again
89
- _injectFile ( newEntryProperty as EntryPropertyObject , key , sentryServerConfig ) ;
90
- }
91
- } ) ;
90
+ newEntryProperty [ SERVER_INIT_OUTPUT_LOCATION ] = SENTRY_SERVER_CONFIG_FILE ;
92
91
}
93
92
// On the client, it's sufficient to inject it into the `main` JS code, which is included in every browser page.
94
93
else {
95
- _injectFile ( newEntryProperty , 'main' , sentryClientConfig ) ;
94
+ _injectFile ( newEntryProperty , 'main' , SENTRY_CLIENT_CONFIG_FILE ) ;
96
95
}
97
96
// TODO: hack made necessary because the async-ness of this function turns our object back into a promise, meaning the
98
97
// internal `next` code which should do this doesn't
@@ -109,11 +108,18 @@ type NextConfigExports = {
109
108
webpack ?: WebpackExport ;
110
109
} ;
111
110
111
+ /**
112
+ * Add Sentry options to the config to be exported from the user's `next.config.js` file.
113
+ *
114
+ * @param providedExports The existing config to be exported ,prior to adding Sentry
115
+ * @param providedSentryWebpackPluginOptions Configuration for SentryWebpackPlugin
116
+ * @returns The modified config to be exported
117
+ */
112
118
export function withSentryConfig (
113
119
providedExports : NextConfigExports = { } ,
114
- providedWebpackPluginOptions : Partial < SentryCliPluginOptions > = { } ,
120
+ providedSentryWebpackPluginOptions : Partial < SentryCliPluginOptions > = { } ,
115
121
) : NextConfigExports {
116
- const defaultWebpackPluginOptions = {
122
+ const defaultSentryWebpackPluginOptions = {
117
123
url : process . env . SENTRY_URL ,
118
124
org : process . env . SENTRY_ORG ,
119
125
project : process . env . SENTRY_PROJECT ,
@@ -122,17 +128,17 @@ export function withSentryConfig(
122
128
stripPrefix : [ 'webpack://_N_E/' ] ,
123
129
urlPrefix : `~/_next` ,
124
130
include : '.next/' ,
125
- ignore : [ 'node_modules ' , 'webpack.config .js' ] ,
131
+ ignore : [ '.next/cache ' , 'server/ssr-module-cache.js' , 'static/*/_ssgManifest.js' , 'static/*/_buildManifest .js'] ,
126
132
} ;
127
133
128
134
// warn if any of the default options for the webpack plugin are getting overridden
129
- const webpackPluginOptionOverrides = Object . keys ( defaultWebpackPluginOptions )
135
+ const sentryWebpackPluginOptionOverrides = Object . keys ( defaultSentryWebpackPluginOptions )
130
136
. concat ( 'dryrun' )
131
- . filter ( key => key in Object . keys ( providedWebpackPluginOptions ) ) ;
132
- if ( webpackPluginOptionOverrides . length > 0 ) {
137
+ . filter ( key => key in Object . keys ( providedSentryWebpackPluginOptions ) ) ;
138
+ if ( sentryWebpackPluginOptionOverrides . length > 0 ) {
133
139
logger . warn (
134
140
'[Sentry] You are overriding the following automatically-set SentryWebpackPlugin config options:\n' +
135
- `\t${ webpackPluginOptionOverrides . toString ( ) } ,\n` +
141
+ `\t${ sentryWebpackPluginOptionOverrides . toString ( ) } ,\n` +
136
142
"which has the possibility of breaking source map upload and application. This is only a good idea if you know what you're doing." ,
137
143
) ;
138
144
}
@@ -161,8 +167,8 @@ export function withSentryConfig(
161
167
new ( ( SentryWebpackPlugin as unknown ) as typeof defaultWebpackPlugin ) ( {
162
168
dryRun : options . dev ,
163
169
release : getSentryRelease ( options . buildId ) ,
164
- ...defaultWebpackPluginOptions ,
165
- ...providedWebpackPluginOptions ,
170
+ ...defaultSentryWebpackPluginOptions ,
171
+ ...providedSentryWebpackPluginOptions ,
166
172
} ) ,
167
173
) ;
168
174
0 commit comments