@@ -22,7 +22,11 @@ import type {
2222 WebpackModuleRule ,
2323} from './types' ;
2424
25- export { SentryWebpackPlugin } ;
25+ const RUNTIME_TO_SDK_ENTRYPOINT_MAP = {
26+ browser : './client' ,
27+ node : './server' ,
28+ edge : './edge' ,
29+ } as const ;
2630
2731// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile
2832// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
@@ -53,6 +57,7 @@ export function constructWebpackConfigFunction(
5357 buildContext : BuildContext ,
5458 ) : WebpackConfigObject {
5559 const { isServer, dev : isDev , dir : projectDir } = buildContext ;
60+ const runtime = isServer ? ( buildContext . nextRuntime === 'edge' ? 'edge' : 'node' ) : 'browser' ;
5661
5762 let rawNewConfig = { ...incomingConfig } ;
5863
@@ -67,82 +72,77 @@ export function constructWebpackConfigFunction(
6772 const newConfig = setUpModuleRules ( rawNewConfig ) ;
6873
6974 // Add a loader which will inject code that sets global values
70- addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions ) ;
75+ addValueInjectionLoader ( newConfig , userNextConfig , userSentryOptions , buildContext ) ;
7176
7277 newConfig . module . rules . push ( {
7378 test : / n o d e _ m o d u l e s [ / \\ ] @ s e n t r y [ / \\ ] n e x t j s / ,
7479 use : [
7580 {
7681 loader : path . resolve ( __dirname , 'loaders' , 'sdkMultiplexerLoader.js' ) ,
7782 options : {
78- importTarget : buildContext . nextRuntime === 'edge' ? './edge' : './client' ,
83+ importTarget : RUNTIME_TO_SDK_ENTRYPOINT_MAP [ runtime ] ,
7984 } ,
8085 } ,
8186 ] ,
8287 } ) ;
8388
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+ }
9596
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 ,
142142 } ,
143- ] ,
144- } ) ;
145- }
143+ } ,
144+ ] ,
145+ } ) ;
146146 }
147147
148148 // 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(
303303 // we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
304304 // options. See https://webpack.js.org/configuration/entry-context/#entry.
305305
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' ;
307308
308309 const newEntryProperty =
309310 typeof currentEntryProperty === 'function' ? await currentEntryProperty ( ) : { ...currentEntryProperty } ;
@@ -321,7 +322,7 @@ async function addSentryToEntryProperty(
321322
322323 // inject into all entry points which might contain user's code
323324 for ( const entryPointName in newEntryProperty ) {
324- if ( shouldAddSentryToEntryPoint ( entryPointName , isServer , userSentryOptions . excludeServerRoutes , isDev ) ) {
325+ if ( shouldAddSentryToEntryPoint ( entryPointName , runtime , userSentryOptions . excludeServerRoutes ?? [ ] ) ) {
325326 addFilesToExistingEntryPoint ( newEntryProperty , entryPointName , filesToInject ) ;
326327 } else {
327328 if (
@@ -455,49 +456,31 @@ function checkWebpackPluginOverrides(
455456 */
456457function shouldAddSentryToEntryPoint (
457458 entryPointName : string ,
458- isServer : boolean ,
459- excludeServerRoutes : Array < string | RegExp > = [ ] ,
460- isDev : boolean ,
459+ runtime : 'node' | 'browser' | 'edge' ,
460+ excludeServerRoutes : Array < string | RegExp > ,
461461) : boolean {
462462 // 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' ) {
470464 // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes,
471465 // which don't have the `pages` prefix.)
466+ const entryPointRoute = entryPointName . replace ( / ^ p a g e s / , '' ) ;
472467 if ( stringMatchesSomePattern ( entryPointRoute , excludeServerRoutes , true ) ) {
473468 return false ;
474469 }
475470
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' ) {
497475 return (
498- entryPointName === 'pages/_app ' || // entrypoint for `/pages` pages
476+ entryPointName === 'main ' || // entrypoint for `/pages` pages
499477 entryPointName === 'main-app' // entrypoint for `/app` pages
500478 ) ;
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 ) ;
501484 }
502485}
503486
@@ -526,13 +509,19 @@ export function getWebpackPluginOptions(
526509
527510 const serverInclude = isServerless
528511 ? [ { 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 (
530516 isWebpack5 ? [ { paths : [ `${ distDirAbsPath } /server/chunks/` ] , urlPrefix : `${ urlPrefix } /server/chunks` } ] : [ ] ,
531517 ) ;
532518
533519 const clientInclude = userSentryOptions . widenClientFileUpload
534520 ? [ { 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+ ] ;
536525
537526 const defaultPluginOptions = dropUndefinedKeys ( {
538527 include : isServer ? serverInclude : clientInclude ,
@@ -550,8 +539,7 @@ export function getWebpackPluginOptions(
550539 configFile : hasSentryProperties ? 'sentry.properties' : undefined ,
551540 stripPrefix : [ 'webpack://_N_E/' ] ,
552541 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.
555543 release : getSentryRelease ( buildId ) ,
556544 dryRun : isDev ,
557545 } ) ;
@@ -675,12 +663,16 @@ function addValueInjectionLoader(
675663 newConfig : WebpackConfigObjectWithModuleRules ,
676664 userNextConfig : NextConfigObject ,
677665 userSentryOptions : UserSentryOptions ,
666+ buildContext : BuildContext ,
678667) : void {
679668 const assetPrefix = userNextConfig . assetPrefix || userNextConfig . basePath || '' ;
680669
681670 const isomorphicValues = {
682671 // `rewritesTunnel` set by the user in Next.js config
683672 __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 ) } ,
684676 } ;
685677
686678 const serverValues = {
0 commit comments