@@ -3,7 +3,17 @@ import { createResolver } from '@nuxt/kit';
3
3
import type { Nuxt } from '@nuxt/schema' ;
4
4
import { consoleSandbox } from '@sentry/utils' ;
5
5
import type { Nitro } from 'nitropack' ;
6
+ import type { InputPluginOption } from 'rollup' ;
6
7
import type { SentryNuxtModuleOptions } from '../common/types' ;
8
+ import {
9
+ QUERY_END_INDICATOR ,
10
+ SENTRY_FUNCTIONS_REEXPORT ,
11
+ SENTRY_WRAPPED_ENTRY ,
12
+ constructFunctionReExport ,
13
+ stripQueryPart ,
14
+ } from './utils' ;
15
+
16
+ const SERVER_CONFIG_FILENAME = 'sentry.server.config' ;
7
17
8
18
/**
9
19
* Adds the `sentry.server.config.ts` file as `sentry.server.config.mjs` to the `.output` directory to be able to reference this file in the node --import option.
@@ -23,7 +33,7 @@ export function addServerConfigToBuild(
23
33
'server' in viteInlineConfig . build . rollupOptions . input
24
34
) {
25
35
// Create a rollup entry for the server config to add it as `sentry.server.config.mjs` to the build
26
- ( viteInlineConfig . build . rollupOptions . input as { [ entryName : string ] : string } ) [ 'sentry.server.config' ] =
36
+ ( viteInlineConfig . build . rollupOptions . input as { [ entryName : string ] : string } ) [ SERVER_CONFIG_FILENAME ] =
27
37
createResolver ( nuxt . options . srcDir ) . resolve ( `/${ serverConfigFile } ` ) ;
28
38
}
29
39
@@ -34,8 +44,8 @@ export function addServerConfigToBuild(
34
44
nitro . hooks . hook ( 'close' , async ( ) => {
35
45
const buildDirResolver = createResolver ( nitro . options . buildDir ) ;
36
46
const serverDirResolver = createResolver ( nitro . options . output . serverDir ) ;
37
- const source = buildDirResolver . resolve ( ' dist/server/sentry.server.config. mjs' ) ;
38
- const destination = serverDirResolver . resolve ( 'sentry.server.config. mjs' ) ;
47
+ const source = buildDirResolver . resolve ( ` dist/server/${ SERVER_CONFIG_FILENAME } . mjs` ) ;
48
+ const destination = serverDirResolver . resolve ( ` ${ SERVER_CONFIG_FILENAME } . mjs` ) ;
39
49
40
50
try {
41
51
await fs . promises . access ( source , fs . constants . F_OK ) ;
@@ -85,7 +95,7 @@ export function addSentryTopImport(moduleOptions: SentryNuxtModuleOptions, nitro
85
95
86
96
try {
87
97
fs . readFile ( entryFilePath , 'utf8' , ( err , data ) => {
88
- const updatedContent = `import './sentry.server.config .mjs';\n${ data } ` ;
98
+ const updatedContent = `import './${ SERVER_CONFIG_FILENAME } .mjs';\n${ data } ` ;
89
99
90
100
fs . writeFile ( entryFilePath , updatedContent , 'utf8' , ( ) => {
91
101
if ( moduleOptions . debug ) {
@@ -111,3 +121,94 @@ export function addSentryTopImport(moduleOptions: SentryNuxtModuleOptions, nitro
111
121
}
112
122
} ) ;
113
123
}
124
+
125
+ /**
126
+ * This function modifies the Rollup configuration to include a plugin that wraps the entry file with a dynamic import (`import()`)
127
+ * and adds the Sentry server config with the static `import` declaration.
128
+ *
129
+ * With this, the Sentry server config can be loaded before all other modules of the application (which is needed for import-in-the-middle).
130
+ * See: https://nodejs.org/api/module.html#enabling
131
+ */
132
+ export function addDynamicImportEntryFileWrapper ( nitro : Nitro , serverConfigFile : string ) : void {
133
+ if ( ! nitro . options . rollupConfig ) {
134
+ nitro . options . rollupConfig = { output : { } } ;
135
+ }
136
+
137
+ if ( nitro . options . rollupConfig ?. plugins === null || nitro . options . rollupConfig ?. plugins === undefined ) {
138
+ nitro . options . rollupConfig . plugins = [ ] ;
139
+ } else if ( ! Array . isArray ( nitro . options . rollupConfig . plugins ) ) {
140
+ // `rollupConfig.plugins` can be a single plugin, so we want to put it into an array so that we can push our own plugin
141
+ nitro . options . rollupConfig . plugins = [ nitro . options . rollupConfig . plugins ] ;
142
+ }
143
+
144
+ nitro . options . rollupConfig . plugins . push (
145
+ // @ts -expect-error - This is the correct type, but it shows an error because of two different definitions
146
+ wrapEntryWithDynamicImport ( createResolver ( nitro . options . srcDir ) . resolve ( `/${ serverConfigFile } ` ) ) ,
147
+ ) ;
148
+ }
149
+
150
+ function wrapEntryWithDynamicImport ( resolvedSentryConfigPath : string ) : InputPluginOption {
151
+ const containsSuffix = ( sourcePath : string ) : boolean => {
152
+ return sourcePath . includes ( `.mjs${ SENTRY_WRAPPED_ENTRY } ` ) || sourcePath . includes ( SENTRY_FUNCTIONS_REEXPORT ) ;
153
+ } ;
154
+
155
+ return {
156
+ name : 'sentry-wrap-entry-with-dynamic-import' ,
157
+ async resolveId ( source , importer , options ) {
158
+ if ( source . includes ( `/${ SERVER_CONFIG_FILENAME } ` ) ) {
159
+ return { id : source , moduleSideEffects : true } ;
160
+ }
161
+
162
+ if ( source === 'import-in-the-middle/hook.mjs' ) {
163
+ return { id : source , moduleSideEffects : true , external : true } ;
164
+ }
165
+
166
+ if ( options . isEntry && ! source . includes ( SENTRY_WRAPPED_ENTRY ) ) {
167
+ const resolution = await this . resolve ( source , importer , options ) ;
168
+
169
+ // If it cannot be resolved or is external, just return it
170
+ // so that Rollup can display an error
171
+ if ( ! resolution || resolution ?. external ) return resolution ;
172
+
173
+ const moduleInfo = await this . load ( resolution ) ;
174
+
175
+ moduleInfo . moduleSideEffects = true ;
176
+
177
+ const exportedFunctions = moduleInfo . exportedBindings ?. [ '.' ] ;
178
+
179
+ // checks are needed to prevent multiple attachment of the suffix
180
+ return containsSuffix ( source ) || containsSuffix ( resolution . id )
181
+ ? resolution . id
182
+ : resolution . id
183
+ // concat the query params to mark the file (also attaches names of exports - this is needed for serverless functions to re-export the handler)
184
+ . concat ( SENTRY_WRAPPED_ENTRY )
185
+ . concat (
186
+ exportedFunctions ?. length
187
+ ? SENTRY_FUNCTIONS_REEXPORT . concat ( exportedFunctions . join ( ',' ) ) . concat ( QUERY_END_INDICATOR )
188
+ : '' ,
189
+ ) ;
190
+ }
191
+ return null ;
192
+ } ,
193
+ load ( id : string ) {
194
+ if ( id . includes ( `.mjs${ SENTRY_WRAPPED_ENTRY } ` ) ) {
195
+ const entryId = stripQueryPart ( id ) ;
196
+
197
+ const reExportedFunctions = id . includes ( SENTRY_FUNCTIONS_REEXPORT )
198
+ ? constructFunctionReExport ( id , entryId )
199
+ : '' ;
200
+
201
+ return (
202
+ // Import the Sentry server config
203
+ `import ${ JSON . stringify ( resolvedSentryConfigPath ) } ;\n` +
204
+ // Dynamic import for the previous, actual entry point.
205
+ // import() can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling)
206
+ `import(${ JSON . stringify ( entryId ) } );\n` +
207
+ `${ reExportedFunctions } \n`
208
+ ) ;
209
+ }
210
+
211
+ return null ;
212
+ } ,
213
+ } ;
214
+ }
0 commit comments