@@ -15,6 +15,14 @@ const pageWrapperTemplateCode = fs.readFileSync(pageWrapperTemplatePath, { encod
1515const middlewareWrapperTemplatePath = path . resolve ( __dirname , '..' , 'templates' , 'middlewareWrapperTemplate.js' ) ;
1616const middlewareWrapperTemplateCode = fs . readFileSync ( middlewareWrapperTemplatePath , { encoding : 'utf8' } ) ;
1717
18+ const serverComponentWrapperTemplatePath = path . resolve (
19+ __dirname ,
20+ '..' ,
21+ 'templates' ,
22+ 'serverComponentWrapperTemplate.js' ,
23+ ) ;
24+ const serverComponentWrapperTemplateCode = fs . readFileSync ( serverComponentWrapperTemplatePath , { encoding : 'utf8' } ) ;
25+
1826// Just a simple placeholder to make referencing module consistent
1927const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module' ;
2028
@@ -23,8 +31,10 @@ const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPPING_TARGET_FILE__.cjs';
2331
2432type LoaderOptions = {
2533 pagesDir : string ;
34+ appDir : string ;
2635 pageExtensionRegex : string ;
2736 excludeServerRoutes : Array < RegExp | string > ;
37+ wrappingTargetKind : 'page' | 'api-route' | 'middleware' | 'page-server-component' ;
2838} ;
2939
3040/**
@@ -36,52 +46,91 @@ export default function wrappingLoader(
3646 this : LoaderThis < LoaderOptions > ,
3747 userCode : string ,
3848 userModuleSourceMap : any ,
39- ) : void | string {
49+ ) : void {
4050 // We know one or the other will be defined, depending on the version of webpack being used
4151 const {
4252 pagesDir,
53+ appDir,
4354 pageExtensionRegex,
4455 excludeServerRoutes = [ ] ,
56+ wrappingTargetKind,
4557 } = 'getOptions' in this ? this . getOptions ( ) : this . query ;
4658
4759 this . async ( ) ;
4860
49- // Get the parameterized route name from this page's filepath
50- const parameterizedRoute = path
51- // Get the path of the file insde of the pages directory
52- . relative ( pagesDir , this . resourcePath )
53- // Add a slash at the beginning
54- . replace ( / ( .* ) / , '/$1' )
55- // Pull off the file extension
56- . replace ( new RegExp ( `\\.(${ pageExtensionRegex } )` ) , '' )
57- // Any page file named `index` corresponds to root of the directory its in, URL-wise, so turn `/xyz/index` into
58- // just `/xyz`
59- . replace ( / \/ i n d e x $ / , '' )
60- // In case all of the above have left us with an empty string (which will happen if we're dealing with the
61- // homepage), sub back in the root route
62- . replace ( / ^ $ / , '/' ) ;
63-
64- // Skip explicitly-ignored pages
65- if ( stringMatchesSomePattern ( parameterizedRoute , excludeServerRoutes , true ) ) {
66- this . callback ( null , userCode , userModuleSourceMap ) ;
67- return ;
68- }
61+ let templateCode : string ;
6962
70- const middlewareJsPath = path . join ( pagesDir , '..' , 'middleware.js' ) ;
71- const middlewareTsPath = path . join ( pagesDir , '..' , 'middleware.ts' ) ;
63+ if ( wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route' ) {
64+ // Get the parameterized route name from this page's filepath
65+ const parameterizedPagesRoute = path . posix
66+ . normalize (
67+ path
68+ // Get the path of the file insde of the pages directory
69+ . relative ( pagesDir , this . resourcePath ) ,
70+ )
71+ // Add a slash at the beginning
72+ . replace ( / ( .* ) / , '/$1' )
73+ // Pull off the file extension
74+ . replace ( new RegExp ( `\\.(${ pageExtensionRegex } )` ) , '' )
75+ // Any page file named `index` corresponds to root of the directory its in, URL-wise, so turn `/xyz/index` into
76+ // just `/xyz`
77+ . replace ( / \/ i n d e x $ / , '' )
78+ // In case all of the above have left us with an empty string (which will happen if we're dealing with the
79+ // homepage), sub back in the root route
80+ . replace ( / ^ $ / , '/' ) ;
7281
73- let templateCode : string ;
74- if ( parameterizedRoute . startsWith ( '/api' ) ) {
75- templateCode = apiWrapperTemplateCode ;
76- } else if ( this . resourcePath === middlewareJsPath || this . resourcePath === middlewareTsPath ) {
82+ // Skip explicitly-ignored pages
83+ if ( stringMatchesSomePattern ( parameterizedPagesRoute , excludeServerRoutes , true ) ) {
84+ this . callback ( null , userCode , userModuleSourceMap ) ;
85+ return ;
86+ }
87+
88+ if ( wrappingTargetKind === 'page' ) {
89+ templateCode = pageWrapperTemplateCode ;
90+ } else if ( wrappingTargetKind === 'api-route' ) {
91+ templateCode = apiWrapperTemplateCode ;
92+ } else {
93+ throw new Error ( `Invariant: Could not get template code of unknown kind "${ wrappingTargetKind } "` ) ;
94+ }
95+
96+ // Inject the route and the path to the file we're wrapping into the template
97+ templateCode = templateCode . replace ( / _ _ R O U T E _ _ / g, parameterizedPagesRoute . replace ( / \\ / g, '\\\\' ) ) ;
98+ } else if ( wrappingTargetKind === 'page-server-component' ) {
99+ // Get the parameterized route name from this page's filepath
100+ const parameterizedPagesRoute = path . posix
101+ . normalize ( path . relative ( appDir , this . resourcePath ) )
102+ // Add a slash at the beginning
103+ . replace ( / ( .* ) / , '/$1' )
104+ // Pull off the file name
105+ . replace ( / \/ p a g e \. ( j s | j s x | t s x ) $ / , '' )
106+ // Remove routing groups: https://beta.nextjs.org/docs/routing/defining-routes#example-creating-multiple-root-layouts
107+ . replace ( / \/ ( \( .* ?\) \/ ) + / g, '/' )
108+ // In case all of the above have left us with an empty string (which will happen if we're dealing with the
109+ // homepage), sub back in the root route
110+ . replace ( / ^ $ / , '/' ) ;
111+
112+ // Skip explicitly-ignored pages
113+ if ( stringMatchesSomePattern ( parameterizedPagesRoute , excludeServerRoutes , true ) ) {
114+ this . callback ( null , userCode , userModuleSourceMap ) ;
115+ return ;
116+ }
117+
118+ // The following string is what Next.js injects in order to mark client components:
119+ // https://github.com/vercel/next.js/blob/295f9da393f7d5a49b0c2e15a2f46448dbdc3895/packages/next/build/analysis/get-page-static-info.ts#L37
120+ // https://github.com/vercel/next.js/blob/a1c15d84d906a8adf1667332a3f0732be615afa0/packages/next-swc/crates/core/src/react_server_components.rs#L247
121+ // We do not want to wrap client components
122+ if ( userCode . includes ( '/* __next_internal_client_entry_do_not_use__ */' ) ) {
123+ this . callback ( null , userCode , userModuleSourceMap ) ;
124+ return ;
125+ }
126+
127+ templateCode = serverComponentWrapperTemplateCode ;
128+ } else if ( wrappingTargetKind === 'middleware' ) {
77129 templateCode = middlewareWrapperTemplateCode ;
78130 } else {
79- templateCode = pageWrapperTemplateCode ;
131+ throw new Error ( `Invariant: Could not get template code of unknown kind " ${ wrappingTargetKind } "` ) ;
80132 }
81133
82- // Inject the route and the path to the file we're wrapping into the template
83- templateCode = templateCode . replace ( / _ _ R O U T E _ _ / g, parameterizedRoute . replace ( / \\ / g, '\\\\' ) ) ;
84-
85134 // Replace the import path of the wrapping target in the template with a path that the `wrapUserCode` function will understand.
86135 templateCode = templateCode . replace ( / _ _ S E N T R Y _ W R A P P I N G _ T A R G E T _ F I L E _ _ / g, WRAPPING_TARGET_MODULE_NAME ) ;
87136
@@ -97,7 +146,6 @@ export default function wrappingLoader(
97146 `[@sentry/nextjs] Could not instrument ${ this . resourcePath } . An error occurred while auto-wrapping:\n${ err } ` ,
98147 ) ;
99148 this . callback ( null , userCode , userModuleSourceMap ) ;
100- return ;
101149 } ) ;
102150}
103151
0 commit comments