Skip to content

Commit 223d054

Browse files
authored
fix: Use renderChunk for release injection for Rollup/Rolldown/Vite (#761)
* fix: Use `renderChunk` for release injection for Rollup/Rolldown/Vite * Minify injected code * Lint
1 parent 8237ac9 commit 223d054

File tree

4 files changed

+65
-186
lines changed

4 files changed

+65
-186
lines changed

packages/bundler-plugin-core/src/index.ts

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -182,63 +182,59 @@ export function sentryCliBinaryExists(): boolean {
182182
return fs.existsSync(SentryCli.getPath());
183183
}
184184

185+
// We need to be careful not to inject the snippet before any `"use strict";`s.
186+
// As an additional complication `"use strict";`s may come after any number of comments.
187+
const COMMENT_USE_STRICT_REGEX =
188+
// Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files.
189+
/^(?:\s*|\/\*(?:.|\r|\n)*?\*\/|\/\/.*[\n\r])*(?:"[^"]*";|'[^']*';)?/;
190+
191+
/**
192+
* Simplified `renderChunk` hook type from Rollup.
193+
* We can't reference the type directly because the Vite plugin complains
194+
* about type mismatches
195+
*/
196+
type RenderChunkHook = (
197+
code: string,
198+
chunk: {
199+
fileName: string;
200+
}
201+
) => {
202+
code: string;
203+
map: SourceMap;
204+
} | null;
205+
185206
export function createRollupReleaseInjectionHooks(injectionCode: string): {
186-
resolveId: UnpluginOptions["resolveId"];
187-
load: UnpluginOptions["load"];
188-
transform: UnpluginOptions["transform"];
207+
renderChunk: RenderChunkHook;
189208
} {
190-
const virtualReleaseInjectionFileId = "\0sentry-release-injection-file";
191209
return {
192-
resolveId(id: string) {
193-
if (id === virtualReleaseInjectionFileId) {
194-
return {
195-
id: virtualReleaseInjectionFileId,
196-
external: false,
197-
moduleSideEffects: true,
198-
};
199-
} else {
200-
return null;
201-
}
202-
},
203-
204-
load(id: string) {
205-
if (id === virtualReleaseInjectionFileId) {
206-
return injectionCode;
207-
} else {
208-
return null;
209-
}
210-
},
211-
212-
transform(code: string, id: string) {
213-
if (id === virtualReleaseInjectionFileId) {
214-
return null;
215-
}
216-
217-
// id may contain query and hash which will trip up our file extension logic below
218-
const idWithoutQueryAndHash = stripQueryAndHashFromPath(id);
219-
220-
if (idWithoutQueryAndHash.match(/\\node_modules\\|\/node_modules\//)) {
221-
return null;
222-
}
223-
210+
renderChunk(code: string, chunk: { fileName: string }) {
224211
if (
225-
![".js", ".ts", ".jsx", ".tsx", ".mjs"].some((ending) =>
226-
idWithoutQueryAndHash.endsWith(ending)
212+
// chunks could be any file (html, md, ...)
213+
[".js", ".mjs", ".cjs"].some((ending) =>
214+
stripQueryAndHashFromPath(chunk.fileName).endsWith(ending)
227215
)
228216
) {
229-
return null;
230-
}
217+
const ms = new MagicString(code, { filename: chunk.fileName });
231218

232-
const ms = new MagicString(code);
219+
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
233220

234-
// Appending instead of prepending has less probability of mucking with user's source maps.
235-
// Luckily import statements get hoisted to the top anyways.
236-
ms.append(`\n\n;import "${virtualReleaseInjectionFileId}";`);
221+
if (match) {
222+
// Add injected code after any comments or "use strict" at the beginning of the bundle.
223+
ms.appendLeft(match.length, injectionCode);
224+
} else {
225+
// ms.replace() doesn't work when there is an empty string match (which happens if
226+
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
227+
// need this special case here.
228+
ms.prepend(injectionCode);
229+
}
237230

238-
return {
239-
code: ms.toString(),
240-
map: ms.generateMap({ hires: "boundary" }),
241-
};
231+
return {
232+
code: ms.toString(),
233+
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
234+
};
235+
} else {
236+
return null; // returning null means not modifying the chunk at all
237+
}
242238
},
243239
};
244240
}
@@ -253,27 +249,6 @@ export function createRollupBundleSizeOptimizationHooks(replacementValues: Sentr
253249
};
254250
}
255251

256-
// We need to be careful not to inject the snippet before any `"use strict";`s.
257-
// As an additional complication `"use strict";`s may come after any number of comments.
258-
const COMMENT_USE_STRICT_REGEX =
259-
// Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files.
260-
/^(?:\s*|\/\*(?:.|\r|\n)*?\*\/|\/\/.*[\n\r])*(?:"[^"]*";|'[^']*';)?/;
261-
262-
/**
263-
* Simplified `renderChunk` hook type from Rollup.
264-
* We can't reference the type directly because the Vite plugin complains
265-
* about type mismatches
266-
*/
267-
type RenderChunkHook = (
268-
code: string,
269-
chunk: {
270-
fileName: string;
271-
}
272-
) => {
273-
code: string;
274-
map: SourceMap;
275-
} | null;
276-
277252
export function createRollupDebugIdInjectionHooks(): {
278253
renderChunk: RenderChunkHook;
279254
} {

packages/bundler-plugin-core/src/utils.ts

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -314,28 +314,17 @@ export function generateGlobalInjectorCode({
314314
}): string {
315315
// The code below is mostly ternary operators because it saves bundle size.
316316
// The checks are to support as many environments as possible. (Node.js, Browser, webworkers, etc.)
317-
let code = `(function(){
318-
var _global =
319-
typeof window !== 'undefined' ?
320-
window :
321-
typeof global !== 'undefined' ?
322-
global :
323-
typeof globalThis !== 'undefined' ?
324-
globalThis :
325-
typeof self !== 'undefined' ?
326-
self :
327-
{};
328-
329-
_global.SENTRY_RELEASE={id:${JSON.stringify(release)}};`;
317+
let code = `!function(){var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};`;
318+
319+
code += `e.SENTRY_RELEASE={id:${JSON.stringify(release)}};`;
330320

331321
if (injectBuildInformation) {
332322
const buildInfo = getBuildInformation();
333323

334-
code += `
335-
_global.SENTRY_BUILD_INFO=${JSON.stringify(buildInfo)};`;
324+
code += `e.SENTRY_BUILD_INFO=${JSON.stringify(buildInfo)};`;
336325
}
337326

338-
code += "})();";
327+
code += "}();";
339328

340329
return code;
341330
}
@@ -345,28 +334,9 @@ export function generateModuleMetadataInjectorCode(metadata: any): string {
345334
// The code below is mostly ternary operators because it saves bundle size.
346335
// The checks are to support as many environments as possible. (Node.js, Browser, webworkers, etc.)
347336
// We are merging the metadata objects in case modules are bundled twice with the plugin
348-
return `(function(){
349-
var _sentryModuleMetadataGlobal =
350-
typeof window !== "undefined"
351-
? window
352-
: typeof global !== "undefined"
353-
? global
354-
: typeof globalThis !== "undefined"
355-
? globalThis
356-
: typeof self !== "undefined"
357-
? self
358-
: {};
359-
360-
_sentryModuleMetadataGlobal._sentryModuleMetadata =
361-
_sentryModuleMetadataGlobal._sentryModuleMetadata || {};
362-
363-
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack] =
364-
Object.assign(
365-
{},
366-
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack],
367-
${JSON.stringify(metadata)}
368-
);
369-
})();`;
337+
return `!function(){var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=Object.assign({},e._sentryModuleMetadata[(new e.Error).stack],${JSON.stringify(
338+
metadata
339+
)})}();`;
370340
}
371341

372342
export function getBuildInformation(): {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`generateGlobalInjectorCode generates code with release 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e.SENTRY_RELEASE={id:\\"1.2.3\\"};}();"`;
4+
5+
exports[`generateGlobalInjectorCode generates code with release and build information 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e.SENTRY_RELEASE={id:\\"1.2.3\\"};e.SENTRY_BUILD_INFO={\\"deps\\":[\\"myDep\\",\\"rollup\\"],\\"depsVersions\\":{\\"rollup\\":3},\\"nodeVersion\\":18};}();"`;
6+
7+
exports[`generateModuleMetadataInjectorCode generates code with empty metadata object 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=Object.assign({},e._sentryModuleMetadata[(new e.Error).stack],{})}();"`;
8+
9+
exports[`generateModuleMetadataInjectorCode generates code with metadata object 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=Object.assign({},e._sentryModuleMetadata[(new e.Error).stack],{\\"file1.js\\":{\\"foo\\":\\"bar\\"},\\"file2.js\\":{\\"bar\\":\\"baz\\"}})}();"`;

packages/bundler-plugin-core/test/utils.test.ts

Lines changed: 4 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -227,21 +227,7 @@ describe("generateGlobalInjectorCode", () => {
227227
injectBuildInformation: false,
228228
});
229229

230-
expect(generatedCode).toMatchInlineSnapshot(`
231-
"(function(){
232-
var _global =
233-
typeof window !== 'undefined' ?
234-
window :
235-
typeof global !== 'undefined' ?
236-
global :
237-
typeof globalThis !== 'undefined' ?
238-
globalThis :
239-
typeof self !== 'undefined' ?
240-
self :
241-
{};
242-
243-
_global.SENTRY_RELEASE={id:\\"1.2.3\\"};})();"
244-
`);
230+
expect(generatedCode).toMatchSnapshot();
245231
});
246232

247233
it("generates code with release and build information", () => {
@@ -262,52 +248,14 @@ describe("generateGlobalInjectorCode", () => {
262248
injectBuildInformation: true,
263249
});
264250

265-
expect(generatedCode).toMatchInlineSnapshot(`
266-
"(function(){
267-
var _global =
268-
typeof window !== 'undefined' ?
269-
window :
270-
typeof global !== 'undefined' ?
271-
global :
272-
typeof globalThis !== 'undefined' ?
273-
globalThis :
274-
typeof self !== 'undefined' ?
275-
self :
276-
{};
277-
278-
_global.SENTRY_RELEASE={id:\\"1.2.3\\"};
279-
_global.SENTRY_BUILD_INFO={\\"deps\\":[\\"myDep\\",\\"rollup\\"],\\"depsVersions\\":{\\"rollup\\":3},\\"nodeVersion\\":18};})();"
280-
`);
251+
expect(generatedCode).toMatchSnapshot();
281252
});
282253
});
283254

284255
describe("generateModuleMetadataInjectorCode", () => {
285256
it("generates code with empty metadata object", () => {
286257
const generatedCode = generateModuleMetadataInjectorCode({});
287-
expect(generatedCode).toMatchInlineSnapshot(`
288-
"(function(){
289-
var _sentryModuleMetadataGlobal =
290-
typeof window !== \\"undefined\\"
291-
? window
292-
: typeof global !== \\"undefined\\"
293-
? global
294-
: typeof globalThis !== \\"undefined\\"
295-
? globalThis
296-
: typeof self !== \\"undefined\\"
297-
? self
298-
: {};
299-
300-
_sentryModuleMetadataGlobal._sentryModuleMetadata =
301-
_sentryModuleMetadataGlobal._sentryModuleMetadata || {};
302-
303-
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack] =
304-
Object.assign(
305-
{},
306-
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack],
307-
{}
308-
);
309-
})();"
310-
`);
258+
expect(generatedCode).toMatchSnapshot();
311259
});
312260

313261
it("generates code with metadata object", () => {
@@ -319,29 +267,6 @@ describe("generateModuleMetadataInjectorCode", () => {
319267
bar: "baz",
320268
},
321269
});
322-
expect(generatedCode).toMatchInlineSnapshot(`
323-
"(function(){
324-
var _sentryModuleMetadataGlobal =
325-
typeof window !== \\"undefined\\"
326-
? window
327-
: typeof global !== \\"undefined\\"
328-
? global
329-
: typeof globalThis !== \\"undefined\\"
330-
? globalThis
331-
: typeof self !== \\"undefined\\"
332-
? self
333-
: {};
334-
335-
_sentryModuleMetadataGlobal._sentryModuleMetadata =
336-
_sentryModuleMetadataGlobal._sentryModuleMetadata || {};
337-
338-
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack] =
339-
Object.assign(
340-
{},
341-
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack],
342-
{\\"file1.js\\":{\\"foo\\":\\"bar\\"},\\"file2.js\\":{\\"bar\\":\\"baz\\"}}
343-
);
344-
})();"
345-
`);
270+
expect(generatedCode).toMatchSnapshot();
346271
});
347272
});

0 commit comments

Comments
 (0)