@@ -22,7 +22,11 @@ import type {
22
22
WebpackModuleRule ,
23
23
} from './types' ;
24
24
25
- export { SentryWebpackPlugin } ;
25
+ const RUNTIME_TO_SDK_ENTRYPOINT_MAP = {
26
+ browser : './client' ,
27
+ node : './server' ,
28
+ edge : './edge' ,
29
+ } as const ;
26
30
27
31
// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile
28
32
// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
@@ -53,6 +57,7 @@ export function constructWebpackConfigFunction(
53
57
buildContext : BuildContext ,
54
58
) : WebpackConfigObject {
55
59
const { isServer, dev : isDev , dir : projectDir } = buildContext ;
60
+ const runtime = isServer ? ( buildContext . nextRuntime === 'edge' ? 'edge' : 'node' ) : 'browser' ;
56
61
57
62
let rawNewConfig = { ...incomingConfig } ;
58
63
@@ -67,82 +72,77 @@ export function constructWebpackConfigFunction(
67
72
const newConfig = setUpModuleRules ( rawNewConfig ) ;
68
73
69
74
// Add a loader which will inject code that sets global values
70
- addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions ) ;
75
+ addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions , buildContext ) ;
71
76
72
77
newConfig . module . rules . push ( {
73
78
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
79
use : [
75
80
{
76
81
loader : path . resolve ( __dirname , 'loaders' , 'sdkMultiplexerLoader.js' ) ,
77
82
options : {
78
- importTarget : buildContext . nextRuntime === 'edge' ? './edge' : './client' ,
83
+ importTarget : RUNTIME_TO_SDK_ENTRYPOINT_MAP [ runtime ] ,
79
84
} ,
80
85
} ,
81
86
] ,
82
87
} ) ;
83
88
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
- }
89
+ if ( isServer && userSentryOptions . autoInstrumentServerFunctions !== false ) {
90
+ let pagesDirPath : string ;
91
+ if ( fs . existsSync ( path . join ( projectDir , 'pages' ) ) && fs . lstatSync ( path . join ( projectDir , 'pages' ) ) . isDirectory ( ) ) {
92
+ pagesDirPath = path . join ( projectDir , 'pages' ) ;
93
+ } else {
94
+ pagesDirPath = path . join ( projectDir , 'src' , 'pages' ) ;
95
+ }
95
96
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
- } ,
97
+ const middlewareJsPath = path . join ( pagesDirPath , '..' , 'middleware.js' ) ;
98
+ const middlewareTsPath = path . join ( pagesDirPath , '..' , 'middleware.ts' ) ;
99
+
100
+ // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
101
+ const pageExtensions = userNextConfig . pageExtensions || [ 'tsx' , 'ts' , 'jsx' , 'js' ] ;
102
+ const dotPrefixedPageExtensions = pageExtensions . map ( ext => `.${ ext } ` ) ;
103
+ const pageExtensionRegex = pageExtensions . map ( escapeStringForRegex ) . join ( '|' ) ;
104
+
105
+ // 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.
106
+ newConfig . module . rules . unshift ( {
107
+ test : resourcePath => {
108
+ // We generally want to apply the loader to all API routes, pages and to the middleware file.
109
+
110
+ // `resourcePath` may be an absolute path or a path relative to the context of the webpack config
111
+ let absoluteResourcePath : string ;
112
+ if ( path . isAbsolute ( resourcePath ) ) {
113
+ absoluteResourcePath = resourcePath ;
114
+ } else {
115
+ absoluteResourcePath = path . join ( projectDir , resourcePath ) ;
116
+ }
117
+ const normalizedAbsoluteResourcePath = path . normalize ( absoluteResourcePath ) ;
118
+
119
+ if (
120
+ // Match everything inside pages/ with the appropriate file extension
121
+ normalizedAbsoluteResourcePath . startsWith ( pagesDirPath ) &&
122
+ dotPrefixedPageExtensions . some ( ext => normalizedAbsoluteResourcePath . endsWith ( ext ) )
123
+ ) {
124
+ return true ;
125
+ } else if (
126
+ // Match middleware.js and middleware.ts
127
+ normalizedAbsoluteResourcePath === middlewareJsPath ||
128
+ normalizedAbsoluteResourcePath === middlewareTsPath
129
+ ) {
130
+ return userSentryOptions . autoInstrumentMiddleware ?? true ;
131
+ } else {
132
+ return false ;
133
+ }
134
+ } ,
135
+ use : [
136
+ {
137
+ loader : path . resolve ( __dirname , 'loaders' , 'wrappingLoader.js' ) ,
138
+ options : {
139
+ pagesDir : pagesDirPath ,
140
+ pageExtensionRegex,
141
+ excludeServerRoutes : userSentryOptions . excludeServerRoutes ,
142
142
} ,
143
- ] ,
144
- } ) ;
145
- }
143
+ } ,
144
+ ] ,
145
+ } ) ;
146
146
}
147
147
148
148
// The SDK uses syntax (ES6 and ES6+ features like object spread) which isn't supported by older browsers. For users
@@ -303,7 +303,8 @@ async function addSentryToEntryProperty(
303
303
// we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
304
304
// options. See https://webpack.js.org/configuration/entry-context/#entry.
305
305
306
- const { isServer, dir : projectDir , dev : isDev , nextRuntime } = buildContext ;
306
+ const { isServer, dir : projectDir , nextRuntime } = buildContext ;
307
+ const runtime = isServer ? ( buildContext . nextRuntime === 'edge' ? 'edge' : 'node' ) : 'browser' ;
307
308
308
309
const newEntryProperty =
309
310
typeof currentEntryProperty === 'function' ? await currentEntryProperty ( ) : { ...currentEntryProperty } ;
@@ -321,7 +322,7 @@ async function addSentryToEntryProperty(
321
322
322
323
// inject into all entry points which might contain user's code
323
324
for ( const entryPointName in newEntryProperty ) {
324
- if ( shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ) {
325
+ if ( shouldAddSentryToEntryPoint ( entryPointName , runtime , userSentryOptions . excludeServerRoutes ?? [ ] ) ) {
325
326
addFilesToExistingEntryPoint ( newEntryProperty , entryPointName , filesToInject ) ;
326
327
} else {
327
328
if (
@@ -455,49 +456,31 @@ function checkWebpackPluginOverrides(
455
456
*/
456
457
function shouldAddSentryToEntryPoint (
457
458
entryPointName : string ,
458
- isServer : boolean ,
459
- excludeServerRoutes : Array < string | RegExp > = [ ] ,
460
- isDev : boolean ,
459
+ runtime : 'node' | 'browser' | 'edge' ,
460
+ excludeServerRoutes : Array < string | RegExp > ,
461
461
) : boolean {
462
462
// 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
-
463
+ if ( runtime === 'node' ) {
470
464
// User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes,
471
465
// which don't have the `pages` prefix.)
466
+ const entryPointRoute = entryPointName . replace ( / ^ p a g e s / , '' ) ;
472
467
if ( stringMatchesSomePattern ( entryPointRoute , excludeServerRoutes , true ) ) {
473
468
return false ;
474
469
}
475
470
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 {
471
+ // This expression will implicitly include `pages/_app` which is called for all serverside routes and pages
472
+ // regardless whether or not the user has a`_app` file.
473
+ return entryPointName . startsWith ( 'pages/' ) ;
474
+ } else if ( runtime === 'browser' ) {
497
475
return (
498
- entryPointName === 'pages/_app ' || // entrypoint for `/pages` pages
476
+ entryPointName === 'main ' || // entrypoint for `/pages` pages
499
477
entryPointName === 'main-app' // entrypoint for `/app` pages
500
478
) ;
479
+ } else {
480
+ // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes,
481
+ // which don't have the `pages` prefix.)
482
+ const entryPointRoute = entryPointName . replace ( / ^ p a g e s / , '' ) ;
483
+ return ! stringMatchesSomePattern ( entryPointRoute , excludeServerRoutes , true ) ;
501
484
}
502
485
}
503
486
@@ -526,13 +509,19 @@ export function getWebpackPluginOptions(
526
509
527
510
const serverInclude = isServerless
528
511
? [ { paths : [ `${ distDirAbsPath } /serverless/` ] , urlPrefix : `${ urlPrefix } /serverless` } ]
529
- : [ { paths : [ `${ distDirAbsPath } /server/pages/` ] , urlPrefix : `${ urlPrefix } /server/pages` } ] . concat (
512
+ : [
513
+ { paths : [ `${ distDirAbsPath } /server/pages/` ] , urlPrefix : `${ urlPrefix } /server/pages` } ,
514
+ { paths : [ `${ distDirAbsPath } /server/app/` ] , urlPrefix : `${ urlPrefix } /server/app` } ,
515
+ ] . concat (
530
516
isWebpack5 ? [ { paths : [ `${ distDirAbsPath } /server/chunks/` ] , urlPrefix : `${ urlPrefix } /server/chunks` } ] : [ ] ,
531
517
) ;
532
518
533
519
const clientInclude = userSentryOptions . widenClientFileUpload
534
520
? [ { paths : [ `${ distDirAbsPath } /static/chunks` ] , urlPrefix : `${ urlPrefix } /static/chunks` } ]
535
- : [ { paths : [ `${ distDirAbsPath } /static/chunks/pages` ] , urlPrefix : `${ urlPrefix } /static/chunks/pages` } ] ;
521
+ : [
522
+ { paths : [ `${ distDirAbsPath } /static/chunks/pages` ] , urlPrefix : `${ urlPrefix } /static/chunks/pages` } ,
523
+ { paths : [ `${ distDirAbsPath } /static/chunks/app` ] , urlPrefix : `${ urlPrefix } /static/chunks/app` } ,
524
+ ] ;
536
525
537
526
const defaultPluginOptions = dropUndefinedKeys ( {
538
527
include : isServer ? serverInclude : clientInclude ,
@@ -550,8 +539,7 @@ export function getWebpackPluginOptions(
550
539
configFile : hasSentryProperties ? 'sentry.properties' : undefined ,
551
540
stripPrefix : [ 'webpack://_N_E/' ] ,
552
541
urlPrefix,
553
- entries : ( entryPointName : string ) =>
554
- shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ,
542
+ entries : [ ] , // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead.
555
543
release : getSentryRelease ( buildId ) ,
556
544
dryRun : isDev ,
557
545
} ) ;
@@ -675,12 +663,16 @@ function addValueInjectionLoader(
675
663
newConfig : WebpackConfigObjectWithModuleRules ,
676
664
userNextConfig : NextConfigObject ,
677
665
userSentryOptions : UserSentryOptions ,
666
+ buildContext : BuildContext ,
678
667
) : void {
679
668
const assetPrefix = userNextConfig . assetPrefix || userNextConfig . basePath || '' ;
680
669
681
670
const isomorphicValues = {
682
671
// `rewritesTunnel` set by the user in Next.js config
683
672
__sentryRewritesTunnelPath__ : userSentryOptions . tunnelRoute ,
673
+
674
+ // The webpack plugin's release injection breaks the `app` directory so we inject the release manually here instead.
675
+ SENTRY_RELEASE : { id : getSentryRelease ( buildContext . buildId ) } ,
684
676
} ;
685
677
686
678
const serverValues = {
0 commit comments