@@ -15,6 +15,14 @@ const pageWrapperTemplateCode = fs.readFileSync(pageWrapperTemplatePath, { encod
15
15
const middlewareWrapperTemplatePath = path . resolve ( __dirname , '..' , 'templates' , 'middlewareWrapperTemplate.js' ) ;
16
16
const middlewareWrapperTemplateCode = fs . readFileSync ( middlewareWrapperTemplatePath , { encoding : 'utf8' } ) ;
17
17
18
+ const serverComponentWrapperTemplatePath = path . resolve (
19
+ __dirname ,
20
+ '..' ,
21
+ 'templates' ,
22
+ 'serverComponentWrapperTemplate.js' ,
23
+ ) ;
24
+ const serverComponentWrapperTemplateCode = fs . readFileSync ( serverComponentWrapperTemplatePath , { encoding : 'utf8' } ) ;
25
+
18
26
// Just a simple placeholder to make referencing module consistent
19
27
const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module' ;
20
28
@@ -23,8 +31,10 @@ const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPPING_TARGET_FILE__.cjs';
23
31
24
32
type LoaderOptions = {
25
33
pagesDir : string ;
34
+ appDir : string ;
26
35
pageExtensionRegex : string ;
27
36
excludeServerRoutes : Array < RegExp | string > ;
37
+ wrappingTargetKind : 'page' | 'api-route' | 'middleware' | 'page-server-component' ;
28
38
} ;
29
39
30
40
/**
@@ -36,52 +46,91 @@ export default function wrappingLoader(
36
46
this : LoaderThis < LoaderOptions > ,
37
47
userCode : string ,
38
48
userModuleSourceMap : any ,
39
- ) : void | string {
49
+ ) : void {
40
50
// We know one or the other will be defined, depending on the version of webpack being used
41
51
const {
42
52
pagesDir,
53
+ appDir,
43
54
pageExtensionRegex,
44
55
excludeServerRoutes = [ ] ,
56
+ wrappingTargetKind,
45
57
} = 'getOptions' in this ? this . getOptions ( ) : this . query ;
46
58
47
59
this . async ( ) ;
48
60
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 ;
69
62
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 ( / ^ $ / , '/' ) ;
72
81
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' ) {
77
129
templateCode = middlewareWrapperTemplateCode ;
78
130
} else {
79
- templateCode = pageWrapperTemplateCode ;
131
+ throw new Error ( `Invariant: Could not get template code of unknown kind " ${ wrappingTargetKind } "` ) ;
80
132
}
81
133
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
-
85
134
// Replace the import path of the wrapping target in the template with a path that the `wrapUserCode` function will understand.
86
135
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 ) ;
87
136
@@ -97,7 +146,6 @@ export default function wrappingLoader(
97
146
`[@sentry/nextjs] Could not instrument ${ this . resourcePath } . An error occurred while auto-wrapping:\n${ err } ` ,
98
147
) ;
99
148
this . callback ( null , userCode , userModuleSourceMap ) ;
100
- return ;
101
149
} ) ;
102
150
}
103
151
0 commit comments