1
1
/* eslint-disable complexity */
2
2
/* eslint-disable max-lines */
3
3
import { getSentryRelease } from '@sentry/node' ;
4
- import { arrayify , dropUndefinedKeys , escapeStringForRegex , logger , stringMatchesSomePattern } from '@sentry/utils' ;
4
+ import { arrayify , dropUndefinedKeys , escapeStringForRegex , logger } from '@sentry/utils' ;
5
5
import { default as SentryWebpackPlugin } from '@sentry/webpack-plugin' ;
6
6
import * as chalk from 'chalk' ;
7
7
import * as fs from 'fs' ;
@@ -22,8 +22,6 @@ import type {
22
22
WebpackModuleRule ,
23
23
} from './types' ;
24
24
25
- export { SentryWebpackPlugin } ;
26
-
27
25
// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile
28
26
// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
29
27
// TODO: drop merged keys from override check? `includeDefaults` option?
@@ -53,6 +51,7 @@ export function constructWebpackConfigFunction(
53
51
buildContext : BuildContext ,
54
52
) : WebpackConfigObject {
55
53
const { isServer, dev : isDev , dir : projectDir } = buildContext ;
54
+ const runtime = isServer ? ( buildContext . nextRuntime === 'edge' ? 'edge' : 'node' ) : 'browser' ;
56
55
57
56
let rawNewConfig = { ...incomingConfig } ;
58
57
@@ -67,82 +66,77 @@ export function constructWebpackConfigFunction(
67
66
const newConfig = setUpModuleRules ( rawNewConfig ) ;
68
67
69
68
// Add a loader which will inject code that sets global values
70
- addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions ) ;
69
+ addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions , buildContext ) ;
71
70
72
71
newConfig . module . rules . push ( {
73
72
test : / n o d e _ m o d u l e s [ / \\ ] @ s e n t r y [ / \\ ] n e x t j s / ,
74
73
use : [
75
74
{
76
75
loader : path . resolve ( __dirname , 'loaders' , 'sdkMultiplexerLoader.js' ) ,
77
76
options : {
78
- importTarget : buildContext . nextRuntime === 'edge' ? './edge' : './client' ,
77
+ importTarget : { browser : './client' , node : './server' , edge : './edge' } [ runtime ] ,
79
78
} ,
80
79
} ,
81
80
] ,
82
81
} ) ;
83
82
84
- if ( isServer ) {
85
- if ( userSentryOptions . autoInstrumentServerFunctions !== false ) {
86
- let pagesDirPath : string ;
87
- if (
88
- fs . existsSync ( path . join ( projectDir , 'pages' ) ) &&
89
- fs . lstatSync ( path . join ( projectDir , 'pages' ) ) . isDirectory ( )
90
- ) {
91
- pagesDirPath = path . join ( projectDir , 'pages' ) ;
92
- } else {
93
- pagesDirPath = path . join ( projectDir , 'src' , 'pages' ) ;
94
- }
83
+ if ( isServer && userSentryOptions . autoInstrumentServerFunctions !== false ) {
84
+ let pagesDirPath : string ;
85
+ if ( fs . existsSync ( path . join ( projectDir , 'pages' ) ) && fs . lstatSync ( path . join ( projectDir , 'pages' ) ) . isDirectory ( ) ) {
86
+ pagesDirPath = path . join ( projectDir , 'pages' ) ;
87
+ } else {
88
+ pagesDirPath = path . join ( projectDir , 'src' , 'pages' ) ;
89
+ }
95
90
96
- const middlewareJsPath = path . join ( pagesDirPath , '..' , 'middleware.js' ) ;
97
- const middlewareTsPath = path . join ( pagesDirPath , '..' , 'middleware.ts' ) ;
98
-
99
- // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
100
- const pageExtensions = userNextConfig . pageExtensions || [ 'tsx' , 'ts' , 'jsx' , 'js' ] ;
101
- const dotPrefixedPageExtensions = pageExtensions . map ( ext => `.${ ext } ` ) ;
102
- const pageExtensionRegex = pageExtensions . map ( escapeStringForRegex ) . join ( '|' ) ;
103
-
104
- // It is very important that we insert our loader at the beginning of the array because we expect any sort of transformations/transpilations (e.g. TS -> JS) to already have happened.
105
- newConfig . module . rules . unshift ( {
106
- test : resourcePath => {
107
- // We generally want to apply the loader to all API routes, pages and to the middleware file.
108
-
109
- // `resourcePath` may be an absolute path or a path relative to the context of the webpack config
110
- let absoluteResourcePath : string ;
111
- if ( path . isAbsolute ( resourcePath ) ) {
112
- absoluteResourcePath = resourcePath ;
113
- } else {
114
- absoluteResourcePath = path . join ( projectDir , resourcePath ) ;
115
- }
116
- const normalizedAbsoluteResourcePath = path . normalize ( absoluteResourcePath ) ;
117
-
118
- if (
119
- // Match everything inside pages/ with the appropriate file extension
120
- normalizedAbsoluteResourcePath . startsWith ( pagesDirPath ) &&
121
- dotPrefixedPageExtensions . some ( ext => normalizedAbsoluteResourcePath . endsWith ( ext ) )
122
- ) {
123
- return true ;
124
- } else if (
125
- // Match middleware.js and middleware.ts
126
- normalizedAbsoluteResourcePath === middlewareJsPath ||
127
- normalizedAbsoluteResourcePath === middlewareTsPath
128
- ) {
129
- return userSentryOptions . autoInstrumentMiddleware ?? true ;
130
- } else {
131
- return false ;
132
- }
133
- } ,
134
- use : [
135
- {
136
- loader : path . resolve ( __dirname , 'loaders' , 'wrappingLoader.js' ) ,
137
- options : {
138
- pagesDir : pagesDirPath ,
139
- pageExtensionRegex,
140
- excludeServerRoutes : userSentryOptions . excludeServerRoutes ,
141
- } ,
91
+ const middlewareJsPath = path . join ( pagesDirPath , '..' , 'middleware.js' ) ;
92
+ const middlewareTsPath = path . join ( pagesDirPath , '..' , 'middleware.ts' ) ;
93
+
94
+ // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
95
+ const pageExtensions = userNextConfig . pageExtensions || [ 'tsx' , 'ts' , 'jsx' , 'js' ] ;
96
+ const dotPrefixedPageExtensions = pageExtensions . map ( ext => `.${ ext } ` ) ;
97
+ const pageExtensionRegex = pageExtensions . map ( escapeStringForRegex ) . join ( '|' ) ;
98
+
99
+ // It is very important that we insert our loader at the beginning of the array because we expect any sort of transformations/transpilations (e.g. TS -> JS) to already have happened.
100
+ newConfig . module . rules . unshift ( {
101
+ test : resourcePath => {
102
+ // We generally want to apply the loader to all API routes, pages and to the middleware file.
103
+
104
+ // `resourcePath` may be an absolute path or a path relative to the context of the webpack config
105
+ let absoluteResourcePath : string ;
106
+ if ( path . isAbsolute ( resourcePath ) ) {
107
+ absoluteResourcePath = resourcePath ;
108
+ } else {
109
+ absoluteResourcePath = path . join ( projectDir , resourcePath ) ;
110
+ }
111
+ const normalizedAbsoluteResourcePath = path . normalize ( absoluteResourcePath ) ;
112
+
113
+ if (
114
+ // Match everything inside pages/ with the appropriate file extension
115
+ normalizedAbsoluteResourcePath . startsWith ( pagesDirPath ) &&
116
+ dotPrefixedPageExtensions . some ( ext => normalizedAbsoluteResourcePath . endsWith ( ext ) )
117
+ ) {
118
+ return true ;
119
+ } else if (
120
+ // Match middleware.js and middleware.ts
121
+ normalizedAbsoluteResourcePath === middlewareJsPath ||
122
+ normalizedAbsoluteResourcePath === middlewareTsPath
123
+ ) {
124
+ return userSentryOptions . autoInstrumentMiddleware ?? true ;
125
+ } else {
126
+ return false ;
127
+ }
128
+ } ,
129
+ use : [
130
+ {
131
+ loader : path . resolve ( __dirname , 'loaders' , 'wrappingLoader.js' ) ,
132
+ options : {
133
+ pagesDir : pagesDirPath ,
134
+ pageExtensionRegex,
135
+ excludeServerRoutes : userSentryOptions . excludeServerRoutes ,
142
136
} ,
143
- ] ,
144
- } ) ;
145
- }
137
+ } ,
138
+ ] ,
139
+ } ) ;
146
140
}
147
141
148
142
// The SDK uses syntax (ES6 and ES6+ features like object spread) which isn't supported by older browsers. For users
@@ -303,7 +297,8 @@ async function addSentryToEntryProperty(
303
297
// we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
304
298
// options. See https://webpack.js.org/configuration/entry-context/#entry.
305
299
306
- const { isServer, dir : projectDir , dev : isDev , nextRuntime } = buildContext ;
300
+ const { isServer, dir : projectDir , nextRuntime } = buildContext ;
301
+ const runtime = isServer ? ( buildContext . nextRuntime === 'edge' ? 'edge' : 'node' ) : 'browser' ;
307
302
308
303
const newEntryProperty =
309
304
typeof currentEntryProperty === 'function' ? await currentEntryProperty ( ) : { ...currentEntryProperty } ;
@@ -321,7 +316,7 @@ async function addSentryToEntryProperty(
321
316
322
317
// inject into all entry points which might contain user's code
323
318
for ( const entryPointName in newEntryProperty ) {
324
- if ( shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ) {
319
+ if ( shouldAddSentryToEntryPoint ( entryPointName , runtime ) ) {
325
320
addFilesToExistingEntryPoint ( newEntryProperty , entryPointName , filesToInject ) ;
326
321
} else {
327
322
if (
@@ -453,51 +448,19 @@ function checkWebpackPluginOverrides(
453
448
* @param excludeServerRoutes A list of excluded serverside entrypoints provided by the user
454
449
* @returns `true` if sentry code should be injected, and `false` otherwise
455
450
*/
456
- function shouldAddSentryToEntryPoint (
457
- entryPointName : string ,
458
- isServer : boolean ,
459
- excludeServerRoutes : Array < string | RegExp > = [ ] ,
460
- isDev : boolean ,
461
- ) : boolean {
451
+ function shouldAddSentryToEntryPoint ( entryPointName : string , runtime : 'node' | 'browser' | 'edge' ) : boolean {
462
452
// On the server side, by default we inject the `Sentry.init()` code into every page (with a few exceptions).
463
- if ( isServer ) {
464
- if ( entryPointName === 'middleware' ) {
465
- return true ;
466
- }
467
-
468
- const entryPointRoute = entryPointName . replace ( / ^ p a g e s / , '' ) ;
469
-
470
- // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes,
471
- // which don't have the `pages` prefix.)
472
- if ( stringMatchesSomePattern ( entryPointRoute , excludeServerRoutes , true ) ) {
473
- return false ;
474
- }
475
-
476
- // In dev mode, page routes aren't considered entrypoints so we inject the init call in the `/_app` entrypoint which
477
- // always exists, even if the user didn't add a `_app` page themselves
478
- if ( isDev ) {
479
- return entryPointRoute === '/_app' ;
480
- }
481
-
482
- if (
483
- // All non-API pages contain both of these components, and we don't want to inject more than once, so as long as
484
- // we're doing the individual pages, it's fine to skip these. (Note: Even if a given user doesn't have either or
485
- // both of these in their `pages/` folder, they'll exist as entrypoints because nextjs will supply default
486
- // versions.)
487
- entryPointRoute === '/_app' ||
488
- entryPointRoute === '/_document' ||
489
- ! entryPointName . startsWith ( 'pages/' )
490
- ) {
491
- return false ;
492
- }
493
-
494
- // We want to inject Sentry into all other pages
495
- return true ;
496
- } else {
453
+ if ( runtime === 'node' ) {
454
+ // This expression will implicitly include `pages/_app` which is called for all serverside routes and pages
455
+ // regardless whether or not the user has a`_app` file.
456
+ return entryPointName . startsWith ( 'pages/' ) ;
457
+ } else if ( runtime === 'browser' ) {
497
458
return (
498
- entryPointName === 'pages/_app ' || // entrypoint for `/pages` pages
459
+ entryPointName === 'main ' || // entrypoint for `/pages` pages
499
460
entryPointName === 'main-app' // entrypoint for `/app` pages
500
461
) ;
462
+ } else {
463
+ return true ;
501
464
}
502
465
}
503
466
@@ -526,13 +489,19 @@ export function getWebpackPluginOptions(
526
489
527
490
const serverInclude = isServerless
528
491
? [ { paths : [ `${ distDirAbsPath } /serverless/` ] , urlPrefix : `${ urlPrefix } /serverless` } ]
529
- : [ { paths : [ `${ distDirAbsPath } /server/pages/` ] , urlPrefix : `${ urlPrefix } /server/pages` } ] . concat (
492
+ : [
493
+ { paths : [ `${ distDirAbsPath } /server/pages/` ] , urlPrefix : `${ urlPrefix } /server/pages` } ,
494
+ { paths : [ `${ distDirAbsPath } /server/app/` ] , urlPrefix : `${ urlPrefix } /server/app` } ,
495
+ ] . concat (
530
496
isWebpack5 ? [ { paths : [ `${ distDirAbsPath } /server/chunks/` ] , urlPrefix : `${ urlPrefix } /server/chunks` } ] : [ ] ,
531
497
) ;
532
498
533
499
const clientInclude = userSentryOptions . widenClientFileUpload
534
500
? [ { paths : [ `${ distDirAbsPath } /static/chunks` ] , urlPrefix : `${ urlPrefix } /static/chunks` } ]
535
- : [ { paths : [ `${ distDirAbsPath } /static/chunks/pages` ] , urlPrefix : `${ urlPrefix } /static/chunks/pages` } ] ;
501
+ : [
502
+ { paths : [ `${ distDirAbsPath } /static/chunks/pages` ] , urlPrefix : `${ urlPrefix } /static/chunks/pages` } ,
503
+ { paths : [ `${ distDirAbsPath } /static/chunks/app` ] , urlPrefix : `${ urlPrefix } /static/chunks/app` } ,
504
+ ] ;
536
505
537
506
const defaultPluginOptions = dropUndefinedKeys ( {
538
507
include : isServer ? serverInclude : clientInclude ,
@@ -550,8 +519,7 @@ export function getWebpackPluginOptions(
550
519
configFile : hasSentryProperties ? 'sentry.properties' : undefined ,
551
520
stripPrefix : [ 'webpack://_N_E/' ] ,
552
521
urlPrefix,
553
- entries : ( entryPointName : string ) =>
554
- shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ,
522
+ entries : [ ] , // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead.
555
523
release : getSentryRelease ( buildId ) ,
556
524
dryRun : isDev ,
557
525
} ) ;
@@ -675,12 +643,14 @@ function addValueInjectionLoader(
675
643
newConfig : WebpackConfigObjectWithModuleRules ,
676
644
userNextConfig : NextConfigObject ,
677
645
userSentryOptions : UserSentryOptions ,
646
+ buildContext : BuildContext ,
678
647
) : void {
679
648
const assetPrefix = userNextConfig . assetPrefix || userNextConfig . basePath || '' ;
680
649
681
650
const isomorphicValues = {
682
651
// `rewritesTunnel` set by the user in Next.js config
683
652
__sentryRewritesTunnelPath__ : userSentryOptions . tunnelRoute ,
653
+ SENTRY_RELEASE : { id : getSentryRelease ( buildContext . buildId ) } ,
684
654
} ;
685
655
686
656
const serverValues = {
0 commit comments