Skip to content

Commit b5d568c

Browse files
committed
review comments
1 parent d15f512 commit b5d568c

File tree

3 files changed

+44
-31
lines changed

3 files changed

+44
-31
lines changed

packages/nuxt/src/vite/addServerConfig.ts

+22-15
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
SENTRY_FUNCTIONS_REEXPORT,
1111
SENTRY_WRAPPED_ENTRY,
1212
constructFunctionReExport,
13-
stripQueryPart,
13+
removeSentryQueryFromPath,
1414
} from './utils';
1515

1616
const SERVER_CONFIG_FILENAME = 'sentry.server.config';
@@ -147,11 +147,12 @@ export function addDynamicImportEntryFileWrapper(nitro: Nitro, serverConfigFile:
147147
);
148148
}
149149

150+
/**
151+
* A Rollup plugin which wraps the server entry with a dynamic `import()`. This makes it possible to initialize Sentry first
152+
* by using a regular `import` and load the server after that.
153+
* This also works with serverless `handler` functions, as it re-exports the `handler`.
154+
*/
150155
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-
155156
return {
156157
name: 'sentry-wrap-entry-with-dynamic-import',
157158
async resolveId(source, importer, options) {
@@ -160,27 +161,30 @@ function wrapEntryWithDynamicImport(resolvedSentryConfigPath: string): InputPlug
160161
}
161162

162163
if (source === 'import-in-the-middle/hook.mjs') {
164+
// We are importing "import-in-the-middle" in the returned code of the `load()` function below
165+
// By setting `moduleSideEffects` to `true`, the import is added to the bundle, although nothing is imported from it
166+
// By importing "import-in-the-middle/hook.mjs", we can make sure this file is included, as not all node builders are including files imported with `module.register()`.
163167
return { id: source, moduleSideEffects: true, external: true };
164168
}
165169

166-
if (options.isEntry && !source.includes(SENTRY_WRAPPED_ENTRY)) {
170+
if (options.isEntry && !source.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) {
167171
const resolution = await this.resolve(source, importer, options);
168172

169-
// If it cannot be resolved or is external, just return it
170-
// so that Rollup can display an error
173+
// If it cannot be resolved or is external, just return it so that Rollup can display an error
171174
if (!resolution || resolution?.external) return resolution;
172175

173176
const moduleInfo = await this.load(resolution);
174177

175178
moduleInfo.moduleSideEffects = true;
176179

180+
// The key `.` in `exportedBindings` refer to the exports within the file
177181
const exportedFunctions = moduleInfo.exportedBindings?.['.'];
178182

179-
// checks are needed to prevent multiple attachment of the suffix
180-
return containsSuffix(source) || containsSuffix(resolution.id)
183+
// 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
184+
return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)
181185
? resolution.id
182186
: 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)
187+
// 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)
184188
.concat(SENTRY_WRAPPED_ENTRY)
185189
.concat(
186190
exportedFunctions?.length
@@ -192,18 +196,21 @@ function wrapEntryWithDynamicImport(resolvedSentryConfigPath: string): InputPlug
192196
},
193197
load(id: string) {
194198
if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) {
195-
const entryId = stripQueryPart(id);
199+
const entryId = removeSentryQueryFromPath(id);
196200

201+
// Mostly useful for serverless `handler` functions
197202
const reExportedFunctions = id.includes(SENTRY_FUNCTIONS_REEXPORT)
198203
? constructFunctionReExport(id, entryId)
199204
: '';
200205

201206
return (
202-
// Import the Sentry server config
207+
// Regular `import` of the Sentry config
203208
`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)
209+
// Dynamic `import()` for the previous, actual entry point.
210+
// `import()` can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling)
206211
`import(${JSON.stringify(entryId)});\n` +
212+
// 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()`.
213+
"import 'import-in-the-middle/hook.mjs'\n" +
207214
`${reExportedFunctions}\n`
208215
);
209216
}

packages/nuxt/src/vite/utils.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,21 @@ export const SENTRY_FUNCTIONS_REEXPORT = '?sentry-query-functions-reexport=';
3030
export const QUERY_END_INDICATOR = 'SENTRY-QUERY-END';
3131

3232
/**
33-
* Strips a specific query part from a URL.
33+
* Strips the Sentry query part from a path.
34+
* Example: example/path?sentry-query-wrapped-entry?sentry-query-functions-reexport=foo,SENTRY-QUERY-END -> /example/path
3435
*
3536
* Only exported for testing.
3637
*/
37-
export function stripQueryPart(url: string): string {
38+
export function removeSentryQueryFromPath(url: string): string {
3839
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
3940
const regex = new RegExp(`\\${SENTRY_WRAPPED_ENTRY}.*?\\${QUERY_END_INDICATOR}`);
4041
return url.replace(regex, '');
4142
}
4243

4344
/**
44-
* Extracts and sanitizes function reexport query parameters from a query string.
45+
* Extracts and sanitizes function re-export query parameters from a query string.
46+
* If it is a default export, it is not considered for re-exporting. This function is mostly relevant for re-exporting
47+
* serverless `handler` functions.
4548
*
4649
* Only exported for testing.
4750
*/
@@ -55,7 +58,7 @@ export function extractFunctionReexportQueryParameters(query: string): string[]
5558
? match[1]
5659
.split(',')
5760
.filter(param => param !== '' && param !== 'default')
58-
// sanitize
61+
// Sanitize, as code could be injected with another rollup plugin
5962
.map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
6063
: [];
6164
}
@@ -69,8 +72,9 @@ export function constructFunctionReExport(pathWithQuery: string, entryId: string
6972
return functionNames.reduce(
7073
(functionsCode, currFunctionName) =>
7174
functionsCode.concat(
72-
`export function ${currFunctionName}(...args) {\n` +
73-
` return import(${JSON.stringify(entryId)}).then((res) => res.${currFunctionName}(...args));\n` +
75+
`export async function ${currFunctionName}(...args) {\n` +
76+
` const res = await import(${JSON.stringify(entryId)});\n` +
77+
` return res.${currFunctionName}.call(this, ...args);\n` +
7478
'}\n',
7579
),
7680
'',

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

+12-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
constructFunctionReExport,
88
extractFunctionReexportQueryParameters,
99
findDefaultSdkInitFile,
10-
stripQueryPart,
10+
removeSentryQueryFromPath,
1111
} from '../../src/vite/utils';
1212

1313
vi.mock('fs');
@@ -68,16 +68,16 @@ describe('findDefaultSdkInitFile', () => {
6868
});
6969
});
7070

71-
describe('stripQueryPart', () => {
72-
it('strips the specific query part from the URL', () => {
71+
describe('removeSentryQueryFromPath', () => {
72+
it('strips the Sentry query part from the path', () => {
7373
const url = `/example/path${SENTRY_WRAPPED_ENTRY}${SENTRY_FUNCTIONS_REEXPORT}foo,${QUERY_END_INDICATOR}`;
74-
const result = stripQueryPart(url);
74+
const result = removeSentryQueryFromPath(url);
7575
expect(result).toBe('/example/path');
7676
});
7777

78-
it('returns the same URL if the specific query part is not present', () => {
78+
it('returns the same path if the specific query part is not present', () => {
7979
const url = '/example/path?other-query=param';
80-
const result = stripQueryPart(url);
80+
const result = removeSentryQueryFromPath(url);
8181
expect(result).toBe(url);
8282
});
8383
});
@@ -108,11 +108,13 @@ describe('constructFunctionReExport', () => {
108108
const result2 = constructFunctionReExport(query2, entryId);
109109

110110
const expected = `
111-
export function foo(...args) {
112-
return import("./module").then((res) => res.foo(...args));
111+
export async function foo(...args) {
112+
const res = await import("./module");
113+
return res.foo.call(this, ...args);
113114
}
114-
export function bar(...args) {
115-
return import("./module").then((res) => res.bar(...args));
115+
export async function bar(...args) {
116+
const res = await import("./module");
117+
return res.bar.call(this, ...args);
116118
}`;
117119
expect(result.trim()).toBe(expected.trim());
118120
expect(result2.trim()).toBe(expected.trim());

0 commit comments

Comments
 (0)