@@ -22,8 +22,6 @@ import type {
2222 WebpackModuleRule ,
2323} from './types' ;
2424
25- export { SentryWebpackPlugin } ;
26-
2725// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile
2826// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
2927// TODO: drop merged keys from override check? `includeDefaults` option?
@@ -53,6 +51,7 @@ export function constructWebpackConfigFunction(
5351 buildContext : BuildContext ,
5452 ) : WebpackConfigObject {
5553 const { isServer, dev : isDev , dir : projectDir } = buildContext ;
54+ const runtime = isServer ? ( buildContext . nextRuntime === 'edge' ? 'edge' : 'node' ) : 'browser' ;
5655
5756 let rawNewConfig = { ...incomingConfig } ;
5857
@@ -67,82 +66,77 @@ export function constructWebpackConfigFunction(
6766 const newConfig = setUpModuleRules ( rawNewConfig ) ;
6867
6968 // Add a loader which will inject code that sets global values
70- addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions ) ;
69+ addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions , buildContext ) ;
7170
7271 newConfig . module . rules . push ( {
7372 test : / n o d e _ m o d u l e s [ / \\ ] @ s e n t r y [ / \\ ] n e x t j s / ,
7473 use : [
7574 {
7675 loader : path . resolve ( __dirname , 'loaders' , 'sdkMultiplexerLoader.js' ) ,
7776 options : {
78- importTarget : buildContext . nextRuntime === 'edge' ? './edge' : './client' ,
77+ importTarget : { browser : './client' , node : './server' , edge : './edge' } [ runtime ] ,
7978 } ,
8079 } ,
8180 ] ,
8281 } ) ;
8382
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+ }
9590
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 ,
142136 } ,
143- ] ,
144- } ) ;
145- }
137+ } ,
138+ ] ,
139+ } ) ;
146140 }
147141
148142 // 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(
303297 // we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
304298 // options. See https://webpack.js.org/configuration/entry-context/#entry.
305299
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' ;
307302
308303 const newEntryProperty =
309304 typeof currentEntryProperty === 'function' ? await currentEntryProperty ( ) : { ...currentEntryProperty } ;
@@ -321,7 +316,7 @@ async function addSentryToEntryProperty(
321316
322317 // inject into all entry points which might contain user's code
323318 for ( const entryPointName in newEntryProperty ) {
324- if ( shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ) {
319+ if ( shouldAddSentryToEntryPoint ( entryPointName , runtime , userSentryOptions . excludeServerRoutes ?? [ ] ) ) {
325320 addFilesToExistingEntryPoint ( newEntryProperty , entryPointName , filesToInject ) ;
326321 } else {
327322 if (
@@ -455,49 +450,35 @@ function checkWebpackPluginOverrides(
455450 */
456451function shouldAddSentryToEntryPoint (
457452 entryPointName : string ,
458- isServer : boolean ,
459- excludeServerRoutes : Array < string | RegExp > = [ ] ,
460- isDev : boolean ,
453+ runtime : 'node' | 'browser' | 'edge' ,
454+ excludeServerRoutes : Array < string | RegExp > ,
461455) : boolean {
462456 // 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-
457+ if ( runtime === 'node' ) {
470458 // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes,
471459 // which don't have the `pages` prefix.)
460+ const entryPointRoute = entryPointName . replace ( / ^ p a g e s / , '' ) ;
472461 if ( stringMatchesSomePattern ( entryPointRoute , excludeServerRoutes , true ) ) {
473462 return false ;
474463 }
475464
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- ) {
465+ // This expression will implicitly include `pages/_app` which is called for all serverside routes and pages
466+ // regardless whether or not the user has a`_app` file.
467+ return entryPointName . startsWith ( 'pages/' ) ;
468+ } else if ( runtime === 'browser' ) {
469+ return (
470+ entryPointName === 'main' || // entrypoint for `/pages` pages
471+ entryPointName === 'main-app' // entrypoint for `/app` pages
472+ ) ;
473+ } else {
474+ // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes,
475+ // which don't have the `pages` prefix.)
476+ const entryPointRoute = entryPointName . replace ( / ^ p a g e s / , '' ) ;
477+ if ( stringMatchesSomePattern ( entryPointRoute , excludeServerRoutes , true ) ) {
491478 return false ;
492479 }
493480
494- // We want to inject Sentry into all other pages
495481 return true ;
496- } else {
497- return (
498- entryPointName === 'pages/_app' || // entrypoint for `/pages` pages
499- entryPointName === 'main-app' // entrypoint for `/app` pages
500- ) ;
501482 }
502483}
503484
@@ -526,13 +507,19 @@ export function getWebpackPluginOptions(
526507
527508 const serverInclude = isServerless
528509 ? [ { paths : [ `${ distDirAbsPath } /serverless/` ] , urlPrefix : `${ urlPrefix } /serverless` } ]
529- : [ { paths : [ `${ distDirAbsPath } /server/pages/` ] , urlPrefix : `${ urlPrefix } /server/pages` } ] . concat (
510+ : [
511+ { paths : [ `${ distDirAbsPath } /server/pages/` ] , urlPrefix : `${ urlPrefix } /server/pages` } ,
512+ { paths : [ `${ distDirAbsPath } /server/app/` ] , urlPrefix : `${ urlPrefix } /server/app` } ,
513+ ] . concat (
530514 isWebpack5 ? [ { paths : [ `${ distDirAbsPath } /server/chunks/` ] , urlPrefix : `${ urlPrefix } /server/chunks` } ] : [ ] ,
531515 ) ;
532516
533517 const clientInclude = userSentryOptions . widenClientFileUpload
534518 ? [ { paths : [ `${ distDirAbsPath } /static/chunks` ] , urlPrefix : `${ urlPrefix } /static/chunks` } ]
535- : [ { paths : [ `${ distDirAbsPath } /static/chunks/pages` ] , urlPrefix : `${ urlPrefix } /static/chunks/pages` } ] ;
519+ : [
520+ { paths : [ `${ distDirAbsPath } /static/chunks/pages` ] , urlPrefix : `${ urlPrefix } /static/chunks/pages` } ,
521+ { paths : [ `${ distDirAbsPath } /static/chunks/app` ] , urlPrefix : `${ urlPrefix } /static/chunks/app` } ,
522+ ] ;
536523
537524 const defaultPluginOptions = dropUndefinedKeys ( {
538525 include : isServer ? serverInclude : clientInclude ,
@@ -550,8 +537,7 @@ export function getWebpackPluginOptions(
550537 configFile : hasSentryProperties ? 'sentry.properties' : undefined ,
551538 stripPrefix : [ 'webpack://_N_E/' ] ,
552539 urlPrefix,
553- entries : ( entryPointName : string ) =>
554- shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ,
540+ entries : [ ] , // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead.
555541 release : getSentryRelease ( buildId ) ,
556542 dryRun : isDev ,
557543 } ) ;
@@ -675,12 +661,14 @@ function addValueInjectionLoader(
675661 newConfig : WebpackConfigObjectWithModuleRules ,
676662 userNextConfig : NextConfigObject ,
677663 userSentryOptions : UserSentryOptions ,
664+ buildContext : BuildContext ,
678665) : void {
679666 const assetPrefix = userNextConfig . assetPrefix || userNextConfig . basePath || '' ;
680667
681668 const isomorphicValues = {
682669 // `rewritesTunnel` set by the user in Next.js config
683670 __sentryRewritesTunnelPath__ : userSentryOptions . tunnelRoute ,
671+ SENTRY_RELEASE : { id : getSentryRelease ( buildContext . buildId ) } ,
684672 } ;
685673
686674 const serverValues = {
0 commit comments