Skip to content

Commit 0a7cd3c

Browse files
committed
review comments
1 parent d15f512 commit 0a7cd3c

File tree

3 files changed

+45
-31
lines changed

3 files changed

+45
-31
lines changed

packages/nuxt/src/vite/addServerConfig.ts

+23-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,31 @@ 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()`.
167+
// Prevents the error "Failed to register ESM hook Error: Cannot find module 'import-in-the-middle/hook.mjs'"
163168
return { id: source, moduleSideEffects: true, external: true };
164169
}
165170

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

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

173177
const moduleInfo = await this.load(resolution);
174178

175179
moduleInfo.moduleSideEffects = true;
176180

181+
// The key `.` in `exportedBindings` refer to the exports within the file
177182
const exportedFunctions = moduleInfo.exportedBindings?.['.'];
178183

179-
// checks are needed to prevent multiple attachment of the suffix
180-
return containsSuffix(source) || containsSuffix(resolution.id)
184+
// 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
185+
return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)
181186
? resolution.id
182187
: 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)
188+
// 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)
184189
.concat(SENTRY_WRAPPED_ENTRY)
185190
.concat(
186191
exportedFunctions?.length
@@ -192,18 +197,21 @@ function wrapEntryWithDynamicImport(resolvedSentryConfigPath: string): InputPlug
192197
},
193198
load(id: string) {
194199
if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) {
195-
const entryId = stripQueryPart(id);
200+
const entryId = removeSentryQueryFromPath(id);
196201

202+
// Mostly useful for serverless `handler` functions
197203
const reExportedFunctions = id.includes(SENTRY_FUNCTIONS_REEXPORT)
198204
? constructFunctionReExport(id, entryId)
199205
: '';
200206

201207
return (
202-
// Import the Sentry server config
208+
// Regular `import` of the Sentry config
203209
`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)
210+
// Dynamic `import()` for the previous, actual entry point.
211+
// `import()` can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling)
206212
`import(${JSON.stringify(entryId)});\n` +
213+
// 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()`.
214+
"import 'import-in-the-middle/hook.mjs'\n" +
207215
`${reExportedFunctions}\n`
208216
);
209217
}

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)