@@ -3,12 +3,93 @@ 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
- type WebpackConfig = { devtool : string ; plugins : Array < { [ key : string ] : any } > } ;
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type PlainObject < T = any > = { [ key : string ] : T } ;
8
+
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
+ // proptery we're modifying is going to be known as an EntryProperty, or EP for short.
12
+
13
+ // The function which is ultimately going to be exported from `next.config.js` under the name `webpack`
14
+ type WebpackExport = ( config : WebpackConfig , options : WebpackOptions ) => WebpackConfig ;
15
+ // type WebpackExport = (config: WebpackConfig, options: WebpackOptions) => Promise<WebpackConfig>;
16
+
17
+ // The two arguments passed to the exported `webpack` function, as well as the thing it returns
18
+ type WebpackConfig = { devtool : string ; plugins : PlainObject [ ] ; entry : EntryProperty } ;
19
+ type WebpackOptions = { dev : boolean ; isServer : boolean } ;
20
+
21
+ // For our purposes, the value for `entry` is either an object, or a function which returns such an object
22
+ type EntryProperty = ( ( ) => Promise < EntryPropertyObject > ) | EntryPropertyObject ;
23
+
24
+ // Each value in that object is either a string representing a single entry point, an array of such strings, or an
25
+ // object containing either of those, along with other configuration options. In that third case, the entry point(s) are
26
+ // listed under the key `import`.
27
+ type EntryPropertyObject = PlainObject < string | Array < string > | EntryPointObject > ;
28
+ type EntryPointObject = { import : string | Array < string > } ;
29
+
30
+ // const injectSentry = async (origEntryProperty: EntryProperty, isServer: boolean): Promise<EntryPropertyObject> => {
31
+ const injectSentry = async ( origEntryProperty : EntryProperty , isServer : boolean ) : Promise < EntryProperty > => {
32
+ // Out of the box, nextjs uses the `() => Promise<EntryPropertyObject>)` flavor of EntryProperty, where the returned
33
+ // object has string arrays for values. But because we don't know whether someone else has come along before us and
34
+ // changed that, we need to check a few things along the way.
35
+
36
+ // The `entry` entry in a webpack config can be a string, array of strings, object, or function. By default, nextjs
37
+ // sets it to an async function which returns the promise of an object of string arrays. Because we don't know whether
38
+ // someone else has come along before us and changed that, we need to check a few things along the way. The one thing
39
+ // we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
40
+ // options. See https://webpack.js.org/configuration/entry-context/#entry.
41
+
42
+ let newEntryProperty = origEntryProperty ;
43
+
44
+ if ( typeof origEntryProperty === 'function' ) {
45
+ newEntryProperty = await origEntryProperty ( ) ;
46
+ }
47
+
48
+ newEntryProperty = newEntryProperty as EntryPropertyObject ;
49
+
50
+ // according to vercel, we only need to inject Sentry in one spot for server and one spot for client, and because
51
+ // those are used as bases, it will apply everywhere
52
+ const injectionPoint = isServer ? 'pages/_document' : 'main' ;
53
+ const injectee = isServer ? './sentry.server.config.js' : './sentry.client.config.js' ;
54
+
55
+ // can be a string, array of strings, or object whose `import` property is one of those two
56
+ let injectedInto = newEntryProperty [ injectionPoint ] ;
57
+
58
+ // whatever the format, add in the sentry file
59
+ injectedInto =
60
+ typeof injectedInto === 'string'
61
+ ? // string case
62
+ [ injectee , injectedInto ]
63
+ : // not a string, must be an array or object
64
+ Array . isArray ( injectedInto )
65
+ ? // array case
66
+ [ injectee , ...injectedInto ]
67
+ : // object case
68
+ {
69
+ ...injectedInto ,
70
+ import :
71
+ typeof injectedInto . import === 'string'
72
+ ? // string case for inner property
73
+ [ injectee , injectedInto . import ]
74
+ : // array case for inner property
75
+ [ injectee , ...injectedInto . import ] ,
76
+ } ;
77
+
78
+ newEntryProperty [ injectionPoint ] = injectedInto ;
79
+
80
+ // TODO: hack made necessary because promises are currently kicking my butt
81
+ if ( 'main.js' in newEntryProperty ) {
82
+ delete newEntryProperty [ 'main.js' ] ;
83
+ }
84
+
85
+ return newEntryProperty ;
86
+ } ;
87
+
7
88
type NextConfigExports = {
8
89
experimental ?: { plugins : boolean } ;
9
90
plugins ?: string [ ] ;
10
91
productionBrowserSourceMaps ?: boolean ;
11
- webpack ?: ( config : WebpackConfig , { dev } : { dev : boolean } ) => WebpackConfig ;
92
+ webpack ?: WebpackExport ;
12
93
} ;
13
94
14
95
export function withSentryConfig (
@@ -27,6 +108,8 @@ export function withSentryConfig(
27
108
include : '.next/' ,
28
109
ignore : [ 'node_modules' , 'webpack.config.js' ] ,
29
110
} ;
111
+
112
+ // warn if any of the default options for the webpack plugin are getting overridden
30
113
const webpackPluginOptionOverrides = Object . keys ( defaultWebpackPluginOptions )
31
114
. concat ( 'dryrun' )
32
115
. map ( key => key in Object . keys ( providedWebpackPluginOptions ) ) ;
@@ -38,32 +121,44 @@ export function withSentryConfig(
38
121
) ;
39
122
}
40
123
124
+ // const newWebpackExport = async (config: WebpackConfig, options: WebpackOptions): Promise<WebpackConfig> => {
125
+ const newWebpackExport = ( config : WebpackConfig , options : WebpackOptions ) : WebpackConfig => {
126
+ let newConfig = config ;
127
+
128
+ if ( typeof providedExports . webpack === 'function' ) {
129
+ newConfig = providedExports . webpack ( config , options ) ;
130
+ // newConfig = await providedExports.webpack(config, options);
131
+ }
132
+
133
+ // Ensure quality source maps in production. (Source maps aren't uploaded in dev, and besides, Next doesn't let you
134
+ // change this is dev even if you want to - see
135
+ // https://github.com/vercel/next.js/blob/master/errors/improper-devtool.md.)
136
+ if ( ! options . dev ) {
137
+ newConfig . devtool = 'source-map' ;
138
+ }
139
+
140
+ // Inject user config files (`sentry.client.confg.js` and `sentry.server.config.js`), which is where `Sentry.init()`
141
+ // is called. By adding them here, we ensure that they're bundled by webpack as part of both server code and client code.
142
+ newConfig . entry = ( injectSentry ( newConfig . entry , options . isServer ) as unknown ) as EntryProperty ;
143
+ // newConfig.entry = await injectSentry(newConfig.entry, options.isServer);
144
+ // newConfig.entry = async () => injectSentry(newConfig.entry, options.isServer);
145
+
146
+ // Add the Sentry plugin, which uploads source maps to Sentry when not in dev
147
+ newConfig . plugins . push (
148
+ // TODO it's not clear how to do this better, but there *must* be a better way
149
+ new ( ( SentryWebpackPlugin as unknown ) as typeof defaultWebpackPlugin ) ( {
150
+ dryRun : options . dev ,
151
+ ...defaultWebpackPluginOptions ,
152
+ ...providedWebpackPluginOptions ,
153
+ } ) ,
154
+ ) ;
155
+
156
+ return newConfig ;
157
+ } ;
158
+
41
159
return {
42
160
...providedExports ,
43
161
productionBrowserSourceMaps : true ,
44
- webpack : ( originalConfig , options ) => {
45
- let config = originalConfig ;
46
-
47
- if ( typeof providedExports . webpack === 'function' ) {
48
- config = providedExports . webpack ( originalConfig , options ) ;
49
- }
50
-
51
- if ( ! options . dev ) {
52
- // Ensure quality source maps in production. (Source maps aren't uploaded in dev, and besides, Next doesn't let
53
- // you change this is dev even if you want to - see
54
- // https://github.com/vercel/next.js/blob/master/errors/improper-devtool.md.)
55
- config . devtool = 'source-map' ;
56
- }
57
- config . plugins . push (
58
- // TODO it's not clear how to do this better, but there *must* be a better way
59
- new ( ( SentryWebpackPlugin as unknown ) as typeof defaultWebpackPlugin ) ( {
60
- dryRun : options . dev ,
61
- ...defaultWebpackPluginOptions ,
62
- ...providedWebpackPluginOptions ,
63
- } ) ,
64
- ) ;
65
-
66
- return config ;
67
- } ,
162
+ webpack : newWebpackExport ,
68
163
} ;
69
164
}
0 commit comments