Skip to content

Commit 84ff708

Browse files
committed
reexport and wrap functions
1 parent 0453e92 commit 84ff708

File tree

5 files changed

+183
-83
lines changed

5 files changed

+183
-83
lines changed

packages/nuxt/src/common/types.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,16 @@ export type SentryNuxtModuleOptions = {
130130

131131
/**
132132
* By default—unless you configure `dynamicImportForServerEntry: false`—the SDK will try to wrap your application entrypoint
133-
* with a dynamic `import()` to ensure all dependencies can be properly instrumented.
133+
* with a dynamic `import()` to ensure all dependencies can be properly instrumented. Any previous exports from the entrypoint are still exported.
134+
* Most exports of the server entrypoint are serverless functions and those are wrapped by Sentry. Other exports stay as-is.
134135
*
135136
* By default, the SDK will wrap the default export as well as a `handler` or `server` export from the entrypoint.
136137
* If your application has a different main export that is used to run the application, you can overwrite this by providing an array of export names to wrap.
137138
* Any wrapped export is expected to be an async function.
138139
*
139140
* @default ['default', 'handler', 'server']
140141
*/
141-
asyncFunctionReExports?: string[];
142+
entrypointWrappedFunctions?: string[];
142143

143144
/**
144145
* Options to be passed directly to the Sentry Rollup Plugin (`@sentry/rollup-plugin`) and Sentry Vite Plugin (`@sentry/vite-plugin`) that ship with the Sentry Nuxt SDK.

packages/nuxt/src/module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default defineNuxtModule<ModuleOptions>({
2121
const moduleOptions = {
2222
...moduleOptionsParam,
2323
dynamicImportForServerEntry: moduleOptionsParam.dynamicImportForServerEntry !== false, // default: true
24-
asyncFunctionReExports: moduleOptionsParam.asyncFunctionReExports || ['default', 'handler', 'server'],
24+
entrypointWrappedFunctions: moduleOptionsParam.entrypointWrappedFunctions || ['default', 'handler', 'server'],
2525
};
2626

2727
const moduleDirResolver = createResolver(import.meta.url);

packages/nuxt/src/vite/addServerConfig.ts

+16-12
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import type { InputPluginOption } from 'rollup';
77
import type { SentryNuxtModuleOptions } from '../common/types';
88
import {
99
QUERY_END_INDICATOR,
10-
SENTRY_FUNCTIONS_REEXPORT,
10+
SENTRY_REEXPORTED_FUNCTIONS,
1111
SENTRY_WRAPPED_ENTRY,
12+
SENTRY_WRAPPED_FUNCTIONS,
1213
constructFunctionReExport,
13-
constructFunctionsReExportQuery,
14+
constructWrappedFunctionExportQuery,
1415
removeSentryQueryFromPath,
1516
} from './utils';
1617

@@ -85,8 +86,8 @@ export function addServerConfigToBuild(
8586
export function addDynamicImportEntryFileWrapper(
8687
nitro: Nitro,
8788
serverConfigFile: string,
88-
moduleOptions: Omit<SentryNuxtModuleOptions, 'asyncFunctionReExports'> &
89-
Required<Pick<SentryNuxtModuleOptions, 'asyncFunctionReExports'>>,
89+
moduleOptions: Omit<SentryNuxtModuleOptions, 'entrypointWrappedFunctions'> &
90+
Required<Pick<SentryNuxtModuleOptions, 'entrypointWrappedFunctions'>>,
9091
): void {
9192
if (!nitro.options.rollupConfig) {
9293
nitro.options.rollupConfig = { output: {} };
@@ -102,7 +103,7 @@ export function addDynamicImportEntryFileWrapper(
102103
nitro.options.rollupConfig.plugins.push(
103104
wrapEntryWithDynamicImport({
104105
resolvedSentryConfigPath: createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`),
105-
asyncFunctionReExports: moduleOptions.asyncFunctionReExports,
106+
entrypointWrappedFunctions: moduleOptions.entrypointWrappedFunctions,
106107
}),
107108
);
108109
}
@@ -114,9 +115,9 @@ export function addDynamicImportEntryFileWrapper(
114115
*/
115116
function wrapEntryWithDynamicImport({
116117
resolvedSentryConfigPath,
117-
asyncFunctionReExports,
118+
entrypointWrappedFunctions,
118119
debug,
119-
}: { resolvedSentryConfigPath: string; asyncFunctionReExports: string[]; debug?: boolean }): InputPluginOption {
120+
}: { resolvedSentryConfigPath: string; entrypointWrappedFunctions: string[]; debug?: boolean }): InputPluginOption {
120121
return {
121122
name: 'sentry-wrap-entry-with-dynamic-import',
122123
async resolveId(source, importer, options) {
@@ -148,7 +149,9 @@ function wrapEntryWithDynamicImport({
148149
: resolution.id
149150
// Concatenates the query params to mark the file (also attaches names of re-exports - this is needed for serverless functions to re-export the handler)
150151
.concat(SENTRY_WRAPPED_ENTRY)
151-
.concat(constructFunctionsReExportQuery(moduleInfo.exportedBindings, asyncFunctionReExports, debug))
152+
.concat(
153+
constructWrappedFunctionExportQuery(moduleInfo.exportedBindings, entrypointWrappedFunctions, debug),
154+
)
152155
.concat(QUERY_END_INDICATOR);
153156
}
154157
return null;
@@ -158,9 +161,10 @@ function wrapEntryWithDynamicImport({
158161
const entryId = removeSentryQueryFromPath(id);
159162

160163
// Mostly useful for serverless `handler` functions
161-
const reExportedAsyncFunctions = id.includes(SENTRY_FUNCTIONS_REEXPORT)
162-
? constructFunctionReExport(id, entryId)
163-
: '';
164+
const reExportedFunctions =
165+
id.includes(SENTRY_WRAPPED_FUNCTIONS) || id.includes(SENTRY_REEXPORTED_FUNCTIONS)
166+
? constructFunctionReExport(id, entryId)
167+
: '';
164168

165169
return (
166170
// Regular `import` of the Sentry config
@@ -170,7 +174,7 @@ function wrapEntryWithDynamicImport({
170174
`import(${JSON.stringify(entryId)});\n` +
171175
// By importing "import-in-the-middle/hook.mjs", we can make sure this file wil be included, as not all node builders are including files imported with `module.register()`.
172176
"import 'import-in-the-middle/hook.mjs';\n" +
173-
`${reExportedAsyncFunctions}\n`
177+
`${reExportedFunctions}\n`
174178
);
175179
}
176180

packages/nuxt/src/vite/utils.ts

+72-32
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export function findDefaultSdkInitFile(type: 'server' | 'client'): string | unde
2727
}
2828

2929
export const SENTRY_WRAPPED_ENTRY = '?sentry-query-wrapped-entry';
30-
export const SENTRY_FUNCTIONS_REEXPORT = '?sentry-query-functions-reexport=';
30+
export const SENTRY_WRAPPED_FUNCTIONS = '?sentry-query-wrapped-functions=';
31+
export const SENTRY_REEXPORTED_FUNCTIONS = '?sentry-query-reexported-functions=';
3132
export const QUERY_END_INDICATOR = 'SENTRY-QUERY-END';
3233

3334
/**
@@ -49,63 +50,102 @@ export function removeSentryQueryFromPath(url: string): string {
4950
*
5051
* Only exported for testing.
5152
*/
52-
export function extractFunctionReexportQueryParameters(query: string): string[] {
53+
export function extractFunctionReexportQueryParameters(query: string): { wrap: string[]; reexport: string[] } {
5354
// Regex matches the comma-separated params between the functions query
5455
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
55-
const regex = new RegExp(`\\${SENTRY_FUNCTIONS_REEXPORT}(.*?)\\${QUERY_END_INDICATOR}`);
56-
const match = query.match(regex);
57-
58-
return match && match[1]
59-
? match[1]
60-
.split(',')
61-
.filter(param => param !== '')
62-
// Sanitize, as code could be injected with another rollup plugin
63-
.map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
64-
: [];
56+
const wrapRegex = new RegExp(
57+
`\\${SENTRY_WRAPPED_FUNCTIONS}(.*?)(\\${QUERY_END_INDICATOR}|\\${SENTRY_REEXPORTED_FUNCTIONS})`,
58+
);
59+
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
60+
const reexportRegex = new RegExp(`\\${SENTRY_REEXPORTED_FUNCTIONS}(.*?)(\\${QUERY_END_INDICATOR})`);
61+
62+
const wrapMatch = query.match(wrapRegex);
63+
const reexportMatch = query.match(reexportRegex);
64+
65+
const wrap =
66+
wrapMatch && wrapMatch[1]
67+
? wrapMatch[1]
68+
.split(',')
69+
.filter(param => param !== '')
70+
.map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
71+
: [];
72+
73+
const reexport =
74+
reexportMatch && reexportMatch[1]
75+
? reexportMatch[1]
76+
.split(',')
77+
.filter(param => param !== '' && param !== 'default')
78+
.map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
79+
: [];
80+
81+
return { wrap, reexport };
6582
}
6683

6784
/**
6885
* Constructs a comma-separated string with all functions that need to be re-exported later from the server entry.
6986
* It uses Rollup's `exportedBindings` to determine the functions to re-export
7087
*/
71-
export function constructFunctionsReExportQuery(
88+
export function constructWrappedFunctionExportQuery(
7289
exportedBindings: Record<string, string[]> | null,
73-
asyncFunctionReExports: string[],
90+
entrypointWrappedFunctions: string[],
7491
debug?: boolean,
7592
): string {
7693
// `exportedBindings` can look like this: `{ '.': [ 'handler' ] }` or `{ '.': [], './firebase-gen-1.mjs': [ 'server' ] }`
7794
// The key `.` refers to exports within the current file, while other keys show from where exports were imported first.
78-
const functionsToExport = flatten(Object.values(exportedBindings || {})).filter(functionName =>
79-
asyncFunctionReExports.includes(functionName),
95+
const functionsToExport = flatten(Object.values(exportedBindings || {})).reduce(
96+
(functions, currFunctionName) => {
97+
if (entrypointWrappedFunctions.includes(currFunctionName)) {
98+
functions.wrap.push(currFunctionName);
99+
} else {
100+
functions.reexport.push(currFunctionName);
101+
}
102+
return functions;
103+
},
104+
{ wrap: [], reexport: [] } as { wrap: string[]; reexport: string[] },
80105
);
81106

82-
if (debug && functionsToExport.length === 0) {
107+
if (debug && functionsToExport.wrap.length === 0) {
83108
consoleSandbox(() =>
84109
// eslint-disable-next-line no-console
85110
console.warn(
86-
"[Sentry] No functions found for re-export. In case your server needs to export async functions other than `handler` or `server`, consider adding the name(s) to Sentry's build options `sentry.asyncFunctionReExports` in your `nuxt.config.ts`.",
111+
"[Sentry] No functions found to wrap. In case your server needs to export async functions other than `handler` or `server`, consider adding the name(s) to Sentry's build options `sentry.entrypointWrappedFunctions` in your `nuxt.config.ts`.",
87112
),
88113
);
89114
}
90115

91-
return functionsToExport?.length ? SENTRY_FUNCTIONS_REEXPORT.concat(functionsToExport.join(',')) : '';
116+
const wrapQuery = functionsToExport.wrap.length
117+
? `${SENTRY_WRAPPED_FUNCTIONS}${functionsToExport.wrap.join(',')}`
118+
: '';
119+
const reexportQuery = functionsToExport.reexport.length
120+
? `${SENTRY_REEXPORTED_FUNCTIONS}${functionsToExport.reexport.join(',')}`
121+
: '';
122+
123+
return [wrapQuery, reexportQuery].filter(Boolean).join('');
92124
}
93125

94126
/**
95-
* Constructs a code snippet with function reexports (can be used in Rollup plugins)
127+
* Constructs a code snippet with function reexports (can be used in Rollup plugins as a return value for `load()`)
96128
*/
97129
export function constructFunctionReExport(pathWithQuery: string, entryId: string): string {
98-
const functionNames = extractFunctionReexportQueryParameters(pathWithQuery);
99-
100-
return functionNames.reduce(
101-
(functionsCode, currFunctionName) =>
102-
functionsCode.concat(
103-
`async function reExport${currFunctionName.toUpperCase()}(...args) {\n` +
104-
` const res = await import(${JSON.stringify(entryId)});\n` +
105-
` return res.${currFunctionName}.call(this, ...args);\n` +
106-
'}\n' +
107-
`export { reExport${currFunctionName.toUpperCase()} as ${currFunctionName} };\n`,
130+
const { wrap: wrapFunctions, reexport: reexportFunctions } = extractFunctionReexportQueryParameters(pathWithQuery);
131+
132+
return wrapFunctions
133+
.reduce(
134+
(functionsCode, currFunctionName) =>
135+
functionsCode.concat(
136+
`async function ${currFunctionName}_sentryWrapped(...args) {\n` +
137+
` const res = await import(${JSON.stringify(entryId)});\n` +
138+
` return res.${currFunctionName}.call(this, ...args);\n` +
139+
'}\n' +
140+
`export { ${currFunctionName}_sentryWrapped as ${currFunctionName} };\n`,
141+
),
142+
'',
143+
)
144+
.concat(
145+
reexportFunctions.reduce(
146+
(functionsCode, currFunctionName) =>
147+
functionsCode.concat(`export { ${currFunctionName} } from ${JSON.stringify(entryId)};`),
148+
'',
108149
),
109-
'',
110-
);
150+
);
111151
}

0 commit comments

Comments
 (0)