11/* eslint-disable complexity */
22/* eslint-disable max-lines */
33import { getSentryRelease } from '@sentry/node' ;
4- import { arrayify , dropUndefinedKeys , escapeStringForRegex , logger , stringMatchesSomePattern } from '@sentry/utils' ;
4+ import { arrayify , dropUndefinedKeys , escapeStringForRegex , logger } from '@sentry/utils' ;
55import { default as SentryWebpackPlugin } from '@sentry/webpack-plugin' ;
66import * as chalk from 'chalk' ;
77import * as fs from 'fs' ;
@@ -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 ) ) {
325320 addFilesToExistingEntryPoint ( newEntryProperty , entryPointName , filesToInject ) ;
326321 } else {
327322 if (
@@ -453,51 +448,19 @@ function checkWebpackPluginOverrides(
453448 * @param excludeServerRoutes A list of excluded serverside entrypoints provided by the user
454449 * @returns `true` if sentry code should be injected, and `false` otherwise
455450 */
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 {
462452 // 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' ) {
497458 return (
498- entryPointName === 'pages/_app ' || // entrypoint for `/pages` pages
459+ entryPointName === 'main ' || // entrypoint for `/pages` pages
499460 entryPointName === 'main-app' // entrypoint for `/app` pages
500461 ) ;
462+ } else {
463+ return true ;
501464 }
502465}
503466
@@ -526,13 +489,19 @@ export function getWebpackPluginOptions(
526489
527490 const serverInclude = isServerless
528491 ? [ { 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 (
530496 isWebpack5 ? [ { paths : [ `${ distDirAbsPath } /server/chunks/` ] , urlPrefix : `${ urlPrefix } /server/chunks` } ] : [ ] ,
531497 ) ;
532498
533499 const clientInclude = userSentryOptions . widenClientFileUpload
534500 ? [ { 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+ ] ;
536505
537506 const defaultPluginOptions = dropUndefinedKeys ( {
538507 include : isServer ? serverInclude : clientInclude ,
@@ -550,8 +519,7 @@ export function getWebpackPluginOptions(
550519 configFile : hasSentryProperties ? 'sentry.properties' : undefined ,
551520 stripPrefix : [ 'webpack://_N_E/' ] ,
552521 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.
555523 release : getSentryRelease ( buildId ) ,
556524 dryRun : isDev ,
557525 } ) ;
@@ -675,12 +643,14 @@ function addValueInjectionLoader(
675643 newConfig : WebpackConfigObjectWithModuleRules ,
676644 userNextConfig : NextConfigObject ,
677645 userSentryOptions : UserSentryOptions ,
646+ buildContext : BuildContext ,
678647) : void {
679648 const assetPrefix = userNextConfig . assetPrefix || userNextConfig . basePath || '' ;
680649
681650 const isomorphicValues = {
682651 // `rewritesTunnel` set by the user in Next.js config
683652 __sentryRewritesTunnelPath__ : userSentryOptions . tunnelRoute ,
653+ SENTRY_RELEASE : { id : getSentryRelease ( buildContext . buildId ) } ,
684654 } ;
685655
686656 const serverValues = {
0 commit comments