Skip to content

Commit 6a29eba

Browse files
committed
review comments: add test
1 parent f2f75d7 commit 6a29eba

File tree

5 files changed

+94
-33
lines changed

5 files changed

+94
-33
lines changed

packages/nuxt/src/common/types.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,12 @@ export type SentryNuxtModuleOptions = {
118118
dynamicImportForServerEntry?: boolean;
119119

120120
/**
121-
* The `asyncFunctionReExports` option is only relevant when `dynamicImportForServerEntry: true` (default value).
121+
* By default—unless you configure `dynamicImportForServerEntry: false`—the SDK will try to wrap your application entrypoint
122+
* with a dynamic `import()` to ensure all dependencies can be properly instrumented.
122123
*
123-
* As the server entry file is wrapped with a dynamic `import()`, previous async function exports need to be re-exported.
124-
* The SDK detects and re-exports those exports (mostly serverless functions). This is why they are re-exported as async functions.
125-
* In case you have a custom setup and your server exports other async functions, you can override the default array with this option.
124+
* By default, the SDK will wrap the default export as well as a `handler` or `server` export from the entrypoint.
125+
* 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.
126+
* Any wrapped export is expected to be an async function.
126127
*
127128
* @default ['default', 'handler', 'server']
128129
*/

packages/nuxt/src/module.ts

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

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

packages/nuxt/src/vite/addServerConfig.ts

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from 'fs';
22
import { createResolver } from '@nuxt/kit';
33
import type { Nuxt } from '@nuxt/schema';
4-
import { consoleSandbox, flatten } from '@sentry/utils';
4+
import { consoleSandbox } from '@sentry/utils';
55
import type { Nitro } from 'nitropack';
66
import type { InputPluginOption } from 'rollup';
77
import type { SentryNuxtModuleOptions } from '../common/types';
@@ -10,6 +10,7 @@ import {
1010
SENTRY_FUNCTIONS_REEXPORT,
1111
SENTRY_WRAPPED_ENTRY,
1212
constructFunctionReExport,
13+
constructFunctionsReExportQuery,
1314
removeSentryQueryFromPath,
1415
} from './utils';
1516

@@ -141,28 +142,13 @@ function wrapEntryWithDynamicImport({
141142

142143
moduleInfo.moduleSideEffects = true;
143144

144-
// `exportedBindings` can look like this: `{ '.': [ 'handler' ] }` or `{ '.': [], './firebase-gen-1.mjs': [ 'server' ] }`
145-
// The key `.` refers to exports within the current file, while other keys show from where exports were imported first.
146-
const functionsToExport = flatten(Object.values(moduleInfo.exportedBindings || {})).filter(functionName =>
147-
asyncFunctionReExports.includes(functionName),
148-
);
149-
150-
if (debug && functionsToExport.length === 0) {
151-
consoleSandbox(() =>
152-
// eslint-disable-next-line no-console
153-
console.warn(
154-
"[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`.",
155-
),
156-
);
157-
}
158-
159145
// The enclosing `if` already checks for the suffix in `source`, but a check in `resolution.id` is needed as well to prevent multiple attachment of the suffix
160146
return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)
161147
? resolution.id
162148
: resolution.id
163149
// 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)
164150
.concat(SENTRY_WRAPPED_ENTRY)
165-
.concat(functionsToExport?.length ? SENTRY_FUNCTIONS_REEXPORT.concat(functionsToExport.join(',')) : '')
151+
.concat(constructFunctionsReExportQuery(moduleInfo.exportedBindings, asyncFunctionReExports, debug))
166152
.concat(QUERY_END_INDICATOR);
167153
}
168154
return null;

packages/nuxt/src/vite/utils.ts

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3+
import { consoleSandbox, flatten } from '@sentry/utils';
34

45
/**
56
* Find the default SDK init file for the given type (client or server).
@@ -63,20 +64,47 @@ export function extractFunctionReexportQueryParameters(query: string): string[]
6364
: [];
6465
}
6566

67+
/**
68+
* Constructs a comma-separated string with all functions that need to be re-exported later from the server entry.
69+
* It uses Rollup's `exportedBindings` to determine the functions to re-export
70+
*/
71+
export function constructFunctionsReExportQuery(
72+
exportedBindings: Record<string, string[]> | null,
73+
asyncFunctionReExports: string[],
74+
debug?: boolean,
75+
): string {
76+
// `exportedBindings` can look like this: `{ '.': [ 'handler' ] }` or `{ '.': [], './firebase-gen-1.mjs': [ 'server' ] }`
77+
// 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),
80+
);
81+
82+
if (debug && functionsToExport.length === 0) {
83+
consoleSandbox(() =>
84+
// eslint-disable-next-line no-console
85+
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`.",
87+
),
88+
);
89+
}
90+
91+
return functionsToExport?.length ? SENTRY_FUNCTIONS_REEXPORT.concat(functionsToExport.join(',')) : '';
92+
}
93+
6694
/**
6795
* Constructs a code snippet with function reexports (can be used in Rollup plugins)
6896
*/
6997
export function constructFunctionReExport(pathWithQuery: string, entryId: string): string {
7098
const functionNames = extractFunctionReexportQueryParameters(pathWithQuery);
7199

72100
return functionNames.reduce(
73-
(functionsCode, currFunctionName, idx) =>
101+
(functionsCode, currFunctionName) =>
74102
functionsCode.concat(
75-
`async function reExport${idx}(...args) {\n` +
103+
`async function reExport${currFunctionName.toUpperCase()}(...args) {\n` +
76104
` const res = await import(${JSON.stringify(entryId)});\n` +
77105
` return res.${currFunctionName}.call(this, ...args);\n` +
78106
'}\n' +
79-
`export { reExport${idx} as ${currFunctionName} };\n`,
107+
`export { reExport${currFunctionName.toUpperCase()} as ${currFunctionName} };\n`,
80108
),
81109
'',
82110
);

packages/nuxt/test/vite/utils.test.ts

+54-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
SENTRY_FUNCTIONS_REEXPORT,
66
SENTRY_WRAPPED_ENTRY,
77
constructFunctionReExport,
8+
constructFunctionsReExportQuery,
89
extractFunctionReexportQueryParameters,
910
findDefaultSdkInitFile,
1011
removeSentryQueryFromPath,
@@ -102,6 +103,38 @@ describe('extractFunctionReexportQueryParameters', () => {
102103
});
103104
});
104105

106+
describe('constructFunctionsReExportQuery', () => {
107+
it.each([
108+
[{ '.': ['handler'] }, ['handler'], '?sentry-query-functions-reexport=handler'],
109+
[{ '.': ['handler'], './module': ['server'] }, [], ''],
110+
[{ '.': ['handler'], './module': ['server'] }, ['server'], '?sentry-query-functions-reexport=server'],
111+
[{ '.': ['handler', 'otherFunction'] }, ['handler'], '?sentry-query-functions-reexport=handler'],
112+
[{ '.': ['handler', 'otherFn'] }, ['handler', 'otherFn'], '?sentry-query-functions-reexport=handler,otherFn'],
113+
[{ '.': ['bar'], './module': ['foo'] }, ['bar', 'foo'], '?sentry-query-functions-reexport=bar,foo'],
114+
])(
115+
'constructs re-export query for exportedBindings: %j and asyncFunctionReExports: %j',
116+
(exportedBindings, asyncFunctionReExports, expected) => {
117+
const result = constructFunctionsReExportQuery(exportedBindings, asyncFunctionReExports);
118+
expect(result).toBe(expected);
119+
},
120+
);
121+
122+
it('logs a warning if no functions are found for re-export and debug is true', () => {
123+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
124+
const exportedBindings = { '.': ['handler'] };
125+
const asyncFunctionReExports = ['nonExistentFunction'];
126+
const debug = true;
127+
128+
const result = constructFunctionsReExportQuery(exportedBindings, asyncFunctionReExports, debug);
129+
expect(result).toBe('');
130+
expect(consoleWarnSpy).toHaveBeenCalledWith(
131+
"[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`.",
132+
);
133+
134+
consoleWarnSpy.mockRestore();
135+
});
136+
});
137+
105138
describe('constructFunctionReExport', () => {
106139
it('constructs re-export code for given query parameters and entry ID', () => {
107140
const query = `${SENTRY_FUNCTIONS_REEXPORT}foo,bar,${QUERY_END_INDICATOR}}`;
@@ -111,16 +144,16 @@ describe('constructFunctionReExport', () => {
111144
const result2 = constructFunctionReExport(query2, entryId);
112145

113146
const expected = `
114-
async function reExport0(...args) {
147+
async function reExportFOO(...args) {
115148
const res = await import("./module");
116149
return res.foo.call(this, ...args);
117150
}
118-
export { reExport0 as foo };
119-
async function reExport1(...args) {
151+
export { reExportFOO as foo };
152+
async function reExportBAR(...args) {
120153
const res = await import("./module");
121154
return res.bar.call(this, ...args);
122155
}
123-
export { reExport1 as bar };
156+
export { reExportBAR as bar };
124157
`;
125158
expect(result.trim()).toBe(expected.trim());
126159
expect(result2.trim()).toBe(expected.trim());
@@ -132,11 +165,26 @@ export { reExport1 as bar };
132165
const result = constructFunctionReExport(query, entryId);
133166

134167
const expected = `
135-
async function reExport0(...args) {
168+
async function reExportDEFAULT(...args) {
169+
const res = await import("./index");
170+
return res.default.call(this, ...args);
171+
}
172+
export { reExportDEFAULT as default };
173+
`;
174+
expect(result.trim()).toBe(expected.trim());
175+
});
176+
177+
it('constructs re-export code for a "default" query parameters and entry ID', () => {
178+
const query = `${SENTRY_FUNCTIONS_REEXPORT}default${QUERY_END_INDICATOR}}`;
179+
const entryId = './index';
180+
const result = constructFunctionReExport(query, entryId);
181+
182+
const expected = `
183+
async function reExportDEFAULT(...args) {
136184
const res = await import("./index");
137185
return res.default.call(this, ...args);
138186
}
139-
export { reExport0 as default };
187+
export { reExportDEFAULT as default };
140188
`;
141189
expect(result.trim()).toBe(expected.trim());
142190
});

0 commit comments

Comments
 (0)