Skip to content

Commit 647fae9

Browse files
committed
fix: skip code injection in facade and empty code chunks
1 parent ab98b4f commit 647fae9

File tree

1 file changed

+108
-66
lines changed
  • packages/bundler-plugin-core/src

1 file changed

+108
-66
lines changed

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

Lines changed: 108 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -206,44 +206,76 @@ type RenderChunkHook = (
206206
code: string,
207207
chunk: {
208208
fileName: string;
209+
facadeModuleId?: string | null;
209210
}
210211
) => {
211212
code: string;
212213
map: SourceMap;
213214
} | null;
214215

216+
/**
217+
* Checks if a chunk should be skipped for code injection
218+
*
219+
* This is necessary to handle Vite's MPA (multi-page application) mode where
220+
* HTML entry points create "facade" chunks that should not contain injected code.
221+
* See: https://github.com/getsentry/sentry-javascript-bundler-plugins/issues/829
222+
*
223+
* @param code - The chunk's code content
224+
* @param facadeModuleId - The facade module ID (if any) - HTML files create facade chunks
225+
* @returns true if the chunk should be skipped
226+
*/
227+
function shouldSkipCodeInjection(code: string, facadeModuleId: string | null | undefined): boolean {
228+
// Skip empty chunks - these are placeholder chunks that should be optimized away
229+
if (code.trim().length === 0) {
230+
return true;
231+
}
232+
233+
// Skip HTML facade chunks
234+
// They only contain import statements and should not have Sentry code injected
235+
if (facadeModuleId && stripQueryAndHashFromPath(facadeModuleId).endsWith(".html")) {
236+
return true;
237+
}
238+
239+
return false;
240+
}
241+
215242
export function createRollupReleaseInjectionHooks(injectionCode: string): {
216243
renderChunk: RenderChunkHook;
217244
} {
218245
return {
219-
renderChunk(code: string, chunk: { fileName: string }) {
246+
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
220247
if (
221248
// chunks could be any file (html, md, ...)
222-
[".js", ".mjs", ".cjs"].some((ending) =>
249+
![".js", ".mjs", ".cjs"].some((ending) =>
223250
stripQueryAndHashFromPath(chunk.fileName).endsWith(ending)
224251
)
225252
) {
226-
const ms = new MagicString(code, { filename: chunk.fileName });
253+
return null; // returning null means not modifying the chunk at all
254+
}
227255

228-
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
256+
// Skip empty chunks and HTML facade chunks (Vite MPA)
257+
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
258+
return null;
259+
}
229260

230-
if (match) {
231-
// Add injected code after any comments or "use strict" at the beginning of the bundle.
232-
ms.appendLeft(match.length, injectionCode);
233-
} else {
234-
// ms.replace() doesn't work when there is an empty string match (which happens if
235-
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
236-
// need this special case here.
237-
ms.prepend(injectionCode);
238-
}
261+
const ms = new MagicString(code, { filename: chunk.fileName });
239262

240-
return {
241-
code: ms.toString(),
242-
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
243-
};
263+
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
264+
265+
if (match) {
266+
// Add injected code after any comments or "use strict" at the beginning of the bundle.
267+
ms.appendLeft(match.length, injectionCode);
244268
} else {
245-
return null; // returning null means not modifying the chunk at all
269+
// ms.replace() doesn't work when there is an empty string match (which happens if
270+
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
271+
// need this special case here.
272+
ms.prepend(injectionCode);
246273
}
274+
275+
return {
276+
code: ms.toString(),
277+
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
278+
};
247279
},
248280
};
249281
}
@@ -262,48 +294,53 @@ export function createRollupDebugIdInjectionHooks(): {
262294
renderChunk: RenderChunkHook;
263295
} {
264296
return {
265-
renderChunk(code: string, chunk: { fileName: string }) {
297+
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
266298
if (
267299
// chunks could be any file (html, md, ...)
268-
[".js", ".mjs", ".cjs"].some((ending) =>
300+
![".js", ".mjs", ".cjs"].some((ending) =>
269301
stripQueryAndHashFromPath(chunk.fileName).endsWith(ending)
270302
)
271303
) {
272-
// Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI)
273-
const chunkStartSnippet = code.slice(0, 6000);
274-
const chunkEndSnippet = code.slice(-500);
275-
276-
if (
277-
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
278-
chunkEndSnippet.includes("//# debugId=")
279-
) {
280-
return null; // Debug ID already present, skip injection
281-
}
304+
return null; // returning null means not modifying the chunk at all
305+
}
306+
307+
// Skip empty chunks and HTML facade chunks (Vite MPA)
308+
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
309+
return null;
310+
}
282311

283-
const debugId = stringToUUID(code); // generate a deterministic debug ID
284-
const codeToInject = getDebugIdSnippet(debugId);
312+
// Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI)
313+
const chunkStartSnippet = code.slice(0, 6000);
314+
const chunkEndSnippet = code.slice(-500);
285315

286-
const ms = new MagicString(code, { filename: chunk.fileName });
316+
if (
317+
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
318+
chunkEndSnippet.includes("//# debugId=")
319+
) {
320+
return null; // Debug ID already present, skip injection
321+
}
287322

288-
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
323+
const debugId = stringToUUID(code); // generate a deterministic debug ID
324+
const codeToInject = getDebugIdSnippet(debugId);
289325

290-
if (match) {
291-
// Add injected code after any comments or "use strict" at the beginning of the bundle.
292-
ms.appendLeft(match.length, codeToInject);
293-
} else {
294-
// ms.replace() doesn't work when there is an empty string match (which happens if
295-
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
296-
// need this special case here.
297-
ms.prepend(codeToInject);
298-
}
326+
const ms = new MagicString(code, { filename: chunk.fileName });
299327

300-
return {
301-
code: ms.toString(),
302-
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
303-
};
328+
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
329+
330+
if (match) {
331+
// Add injected code after any comments or "use strict" at the beginning of the bundle.
332+
ms.appendLeft(match.length, codeToInject);
304333
} else {
305-
return null; // returning null means not modifying the chunk at all
334+
// ms.replace() doesn't work when there is an empty string match (which happens if
335+
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
336+
// need this special case here.
337+
ms.prepend(codeToInject);
306338
}
339+
340+
return {
341+
code: ms.toString(),
342+
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
343+
};
307344
},
308345
};
309346
}
@@ -312,34 +349,39 @@ export function createRollupModuleMetadataInjectionHooks(injectionCode: string):
312349
renderChunk: RenderChunkHook;
313350
} {
314351
return {
315-
renderChunk(code: string, chunk: { fileName: string }) {
352+
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
316353
if (
317354
// chunks could be any file (html, md, ...)
318-
[".js", ".mjs", ".cjs"].some((ending) =>
355+
![".js", ".mjs", ".cjs"].some((ending) =>
319356
stripQueryAndHashFromPath(chunk.fileName).endsWith(ending)
320357
)
321358
) {
322-
const ms = new MagicString(code, { filename: chunk.fileName });
359+
return null; // returning null means not modifying the chunk at all
360+
}
323361

324-
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
362+
// Skip empty chunks and HTML facade chunks (Vite MPA)
363+
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
364+
return null;
365+
}
325366

326-
if (match) {
327-
// Add injected code after any comments or "use strict" at the beginning of the bundle.
328-
ms.appendLeft(match.length, injectionCode);
329-
} else {
330-
// ms.replace() doesn't work when there is an empty string match (which happens if
331-
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
332-
// need this special case here.
333-
ms.prepend(injectionCode);
334-
}
367+
const ms = new MagicString(code, { filename: chunk.fileName });
335368

336-
return {
337-
code: ms.toString(),
338-
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
339-
};
369+
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
370+
371+
if (match) {
372+
// Add injected code after any comments or "use strict" at the beginning of the bundle.
373+
ms.appendLeft(match.length, injectionCode);
340374
} else {
341-
return null; // returning null means not modifying the chunk at all
375+
// ms.replace() doesn't work when there is an empty string match (which happens if
376+
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
377+
// need this special case here.
378+
ms.prepend(injectionCode);
342379
}
380+
381+
return {
382+
code: ms.toString(),
383+
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
384+
};
343385
},
344386
};
345387
}

0 commit comments

Comments
 (0)