Skip to content

Commit 825daf3

Browse files
authored
feat: Combine injection plugins (#844)
1 parent 7baa78c commit 825daf3

8 files changed

Lines changed: 97 additions & 243 deletions

File tree

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

Lines changed: 55 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,20 @@ import {
2020
stripQueryAndHashFromPath,
2121
} from "./utils";
2222

23-
interface SentryUnpluginFactoryOptions {
23+
type InjectionPlugin = (
24+
injectionCode: string,
25+
debugIds: boolean,
26+
logger: Logger
27+
) => UnpluginOptions;
28+
type LegacyPlugins = {
2429
releaseInjectionPlugin: (injectionCode: string) => UnpluginOptions;
25-
componentNameAnnotatePlugin?: (ignoredComponents?: string[]) => UnpluginOptions;
2630
moduleMetadataInjectionPlugin: (injectionCode: string) => UnpluginOptions;
2731
debugIdInjectionPlugin: (logger: Logger) => UnpluginOptions;
32+
};
33+
34+
interface SentryUnpluginFactoryOptions {
35+
injectionPlugin: InjectionPlugin | LegacyPlugins;
36+
componentNameAnnotatePlugin?: (ignoredComponents?: string[]) => UnpluginOptions;
2837
debugIdUploadPlugin: (
2938
upload: (buildArtifacts: string[]) => Promise<void>,
3039
logger: Logger,
@@ -38,10 +47,8 @@ interface SentryUnpluginFactoryOptions {
3847
* Creates an unplugin instance used to create Sentry plugins for Vite, Rollup, esbuild, and Webpack.
3948
*/
4049
export function sentryUnpluginFactory({
41-
releaseInjectionPlugin,
50+
injectionPlugin,
4251
componentNameAnnotatePlugin,
43-
moduleMetadataInjectionPlugin,
44-
debugIdInjectionPlugin,
4552
debugIdUploadPlugin,
4653
bundleSizeOptimizationsPlugin,
4754
}: SentryUnpluginFactoryOptions): UnpluginInstance<Options | undefined, true> {
@@ -94,6 +101,8 @@ export function sentryUnpluginFactory({
94101
plugins.push(bundleSizeOptimizationsPlugin(bundleSizeOptimizationReplacementValues));
95102
}
96103

104+
let injectionCode = "";
105+
97106
if (!options.release.inject) {
98107
logger.debug(
99108
"Release injection disabled via `release.inject` option. Will not inject release."
@@ -103,18 +112,31 @@ export function sentryUnpluginFactory({
103112
"No release name provided. Will not inject release. Please set the `release.name` option to identify your release."
104113
);
105114
} else {
106-
const injectionCode = generateGlobalInjectorCode({
115+
const code = generateGlobalInjectorCode({
107116
release: options.release.name,
108117
injectBuildInformation: options._experiments.injectBuildInformation || false,
109118
});
110-
plugins.push(releaseInjectionPlugin(injectionCode));
119+
if (typeof injectionPlugin !== "function") {
120+
plugins.push(injectionPlugin.releaseInjectionPlugin(code));
121+
} else {
122+
injectionCode += code;
123+
}
111124
}
112125

113126
if (Object.keys(sentryBuildPluginManager.bundleMetadata).length > 0) {
114-
const injectionCode = generateModuleMetadataInjectorCode(
115-
sentryBuildPluginManager.bundleMetadata
116-
);
117-
plugins.push(moduleMetadataInjectionPlugin(injectionCode));
127+
const code = generateModuleMetadataInjectorCode(sentryBuildPluginManager.bundleMetadata);
128+
if (typeof injectionPlugin !== "function") {
129+
plugins.push(injectionPlugin.moduleMetadataInjectionPlugin(code));
130+
} else {
131+
injectionCode += code;
132+
}
133+
}
134+
135+
if (
136+
typeof injectionPlugin === "function" &&
137+
(injectionCode !== "" || options.sourcemaps?.disable !== true)
138+
) {
139+
plugins.push(injectionPlugin(injectionCode, options.sourcemaps?.disable !== true, logger));
118140
}
119141

120142
// Add plugin to create and finalize releases, and also take care of adding commits and legacy sourcemaps
@@ -132,7 +154,9 @@ export function sentryUnpluginFactory({
132154
});
133155

134156
if (options.sourcemaps?.disable !== true) {
135-
plugins.push(debugIdInjectionPlugin(logger));
157+
if (typeof injectionPlugin !== "function") {
158+
plugins.push(injectionPlugin.debugIdInjectionPlugin(logger));
159+
}
136160

137161
if (options.sourcemaps?.disable !== "disable-upload") {
138162
// This option is only strongly typed for the webpack plugin, where it is used. It has no effect on other plugins
@@ -251,42 +275,6 @@ function shouldSkipCodeInjection(code: string, facadeModuleId: string | null | u
251275
return false;
252276
}
253277

254-
export function createRollupReleaseInjectionHooks(injectionCode: string): {
255-
renderChunk: RenderChunkHook;
256-
} {
257-
return {
258-
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
259-
if (!isJsFile(chunk.fileName)) {
260-
return null; // returning null means not modifying the chunk at all
261-
}
262-
263-
// Skip empty chunks and HTML facade chunks (Vite MPA)
264-
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
265-
return null;
266-
}
267-
268-
const ms = new MagicString(code, { filename: chunk.fileName });
269-
270-
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
271-
272-
if (match) {
273-
// Add injected code after any comments or "use strict" at the beginning of the bundle.
274-
ms.appendLeft(match.length, injectionCode);
275-
} else {
276-
// ms.replace() doesn't work when there is an empty string match (which happens if
277-
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
278-
// need this special case here.
279-
ms.prepend(injectionCode);
280-
}
281-
282-
return {
283-
code: ms.toString(),
284-
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
285-
};
286-
},
287-
};
288-
}
289-
290278
export function createRollupBundleSizeOptimizationHooks(replacementValues: SentrySDKBuildFlags): {
291279
transform: UnpluginOptions["transform"];
292280
} {
@@ -297,7 +285,10 @@ export function createRollupBundleSizeOptimizationHooks(replacementValues: Sentr
297285
};
298286
}
299287

300-
export function createRollupDebugIdInjectionHooks(): {
288+
export function createRollupInjectionHooks(
289+
injectionCode: string,
290+
debugIds: boolean
291+
): {
301292
renderChunk: RenderChunkHook;
302293
} {
303294
return {
@@ -311,22 +302,25 @@ export function createRollupDebugIdInjectionHooks(): {
311302
return null;
312303
}
313304

314-
// Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI)
315-
const chunkStartSnippet = code.slice(0, 6000);
316-
const chunkEndSnippet = code.slice(-500);
305+
let codeToInject = injectionCode;
317306

318-
if (
319-
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
320-
chunkEndSnippet.includes("//# debugId=")
321-
) {
322-
return null; // Debug ID already present, skip injection
323-
}
307+
if (debugIds) {
308+
// Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI)
309+
const chunkStartSnippet = code.slice(0, 6000);
310+
const chunkEndSnippet = code.slice(-500);
324311

325-
const debugId = stringToUUID(code); // generate a deterministic debug ID
326-
const codeToInject = getDebugIdSnippet(debugId);
312+
if (
313+
!(
314+
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
315+
chunkEndSnippet.includes("//# debugId=")
316+
)
317+
) {
318+
const debugId = stringToUUID(code); // generate a deterministic debug ID
319+
codeToInject += getDebugIdSnippet(debugId);
320+
}
321+
}
327322

328323
const ms = new MagicString(code, { filename: chunk.fileName });
329-
330324
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
331325

332326
if (match) {
@@ -347,42 +341,6 @@ export function createRollupDebugIdInjectionHooks(): {
347341
};
348342
}
349343

350-
export function createRollupModuleMetadataInjectionHooks(injectionCode: string): {
351-
renderChunk: RenderChunkHook;
352-
} {
353-
return {
354-
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
355-
if (!isJsFile(chunk.fileName)) {
356-
return null; // returning null means not modifying the chunk at all
357-
}
358-
359-
// Skip empty chunks and HTML facade chunks (Vite MPA)
360-
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
361-
return null;
362-
}
363-
364-
const ms = new MagicString(code, { filename: chunk.fileName });
365-
366-
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
367-
368-
if (match) {
369-
// Add injected code after any comments or "use strict" at the beginning of the bundle.
370-
ms.appendLeft(match.length, injectionCode);
371-
} else {
372-
// ms.replace() doesn't work when there is an empty string match (which happens if
373-
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
374-
// need this special case here.
375-
ms.prepend(injectionCode);
376-
}
377-
378-
return {
379-
code: ms.toString(),
380-
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
381-
};
382-
},
383-
};
384-
}
385-
386344
export function createRollupDebugIdUploadHooks(
387345
upload: (buildArtifacts: string[]) => Promise<void>,
388346
_logger: Logger,

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

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { Compiler } from "webpack";
2-
import {
3-
getDebugIdSnippet,
4-
sentryUnpluginFactory,
5-
createRollupDebugIdInjectionHooks,
6-
} from "../src";
2+
import { getDebugIdSnippet, sentryUnpluginFactory, createRollupInjectionHooks } from "../src";
73
import { containsOnlyImports } from "../src/utils";
84

95
describe("getDebugIdSnippet", () => {
@@ -146,8 +142,8 @@ app.mount('#app');
146142
});
147143
});
148144

149-
describe("createRollupDebugIdInjectionHooks", () => {
150-
const hooks = createRollupDebugIdInjectionHooks();
145+
describe("createRollupInjectionHooks", () => {
146+
const hooks = createRollupInjectionHooks("", true);
151147

152148
describe("renderChunk", () => {
153149
it("should inject debug ID into clean JavaScript files", () => {
@@ -212,7 +208,7 @@ describe("createRollupDebugIdInjectionHooks", () => {
212208
],
213209
])("should NOT inject when debug ID already exists (%s)", (_description, code) => {
214210
const result = hooks.renderChunk(code, { fileName: "bundle.js" });
215-
expect(result).toBeNull();
211+
expect(result?.code).not.toContain("_sentryDebugIds");
216212
});
217213

218214
it("should only check boundaries for performance (not entire file)", () => {
@@ -336,20 +332,12 @@ bootstrap();`;
336332
});
337333

338334
describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
339-
const mockReleaseInjectionPlugin = jest.fn((_injectionCode: string) => ({
340-
name: "mock-release-injection-plugin",
341-
}));
342-
343335
const mockComponentNameAnnotatePlugin = jest.fn(() => ({
344336
name: "mock-component-name-annotate-plugin",
345337
}));
346338

347-
const mockModuleMetadataInjectionPlugin = jest.fn((_injectionCode: string) => ({
348-
name: "mock-module-metadata-injection-plugin",
349-
}));
350-
351-
const mockDebugIdInjectionPlugin = jest.fn(() => ({
352-
name: "mock-debug-id-injection-plugin",
339+
const mockInjectionPlugin = jest.fn(() => ({
340+
name: "mock-injection-plugin",
353341
}));
354342

355343
const mockDebugIdUploadPlugin = jest.fn(() => ({
@@ -362,10 +350,8 @@ describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
362350

363351
const createUnpluginInstance = (): ReturnType<typeof sentryUnpluginFactory> => {
364352
return sentryUnpluginFactory({
365-
releaseInjectionPlugin: mockReleaseInjectionPlugin,
353+
injectionPlugin: mockInjectionPlugin,
366354
componentNameAnnotatePlugin: mockComponentNameAnnotatePlugin,
367-
moduleMetadataInjectionPlugin: mockModuleMetadataInjectionPlugin,
368-
debugIdInjectionPlugin: mockDebugIdInjectionPlugin,
369355
debugIdUploadPlugin: mockDebugIdUploadPlugin,
370356
bundleSizeOptimizationsPlugin: mockBundleSizeOptimizationsPlugin,
371357
});
@@ -423,7 +409,7 @@ describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
423409
const pluginNames = plugins.map((plugin) => plugin.name);
424410

425411
// Should include debug ID injection but not upload
426-
expect(pluginNames).toContain("mock-debug-id-injection-plugin");
412+
expect(pluginNames).toContain("mock-injection-plugin");
427413
expect(pluginNames).not.toContain("mock-debug-id-upload-plugin");
428414

429415
// Should still include other core plugins
@@ -452,7 +438,7 @@ describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
452438
const pluginNames = plugins.map((plugin) => plugin.name);
453439

454440
// Should include both debug ID related plugins
455-
expect(pluginNames).toContain("mock-debug-id-injection-plugin");
441+
expect(pluginNames).toContain("mock-injection-plugin");
456442
expect(pluginNames).toContain("mock-debug-id-upload-plugin");
457443

458444
// Should include other core plugins
@@ -479,7 +465,7 @@ describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
479465
const pluginNames = plugins.map((plugin) => plugin.name);
480466

481467
// Should include both debug ID related plugins by default
482-
expect(pluginNames).toContain("mock-debug-id-injection-plugin");
468+
expect(pluginNames).toContain("mock-injection-plugin");
483469
expect(pluginNames).toContain("mock-debug-id-upload-plugin");
484470

485471
// Should include other core plugins
@@ -506,7 +492,7 @@ describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
506492
const pluginNames = plugins.map((plugin) => plugin.name);
507493

508494
// Should include both debug ID related plugins by default
509-
expect(pluginNames).toContain("mock-debug-id-injection-plugin");
495+
expect(pluginNames).toContain("mock-injection-plugin");
510496
expect(pluginNames).toContain("mock-debug-id-upload-plugin");
511497

512498
// Should include other core plugins

packages/esbuild-plugin/src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,11 @@ function esbuildBundleSizeOptimizationsPlugin(
317317
}
318318

319319
const sentryUnplugin = sentryUnpluginFactory({
320-
releaseInjectionPlugin: esbuildReleaseInjectionPlugin,
321-
debugIdInjectionPlugin: esbuildDebugIdInjectionPlugin,
322-
moduleMetadataInjectionPlugin: esbuildModuleMetadataInjectionPlugin,
320+
injectionPlugin: {
321+
releaseInjectionPlugin: esbuildReleaseInjectionPlugin,
322+
debugIdInjectionPlugin: esbuildDebugIdInjectionPlugin,
323+
moduleMetadataInjectionPlugin: esbuildModuleMetadataInjectionPlugin,
324+
},
323325
debugIdUploadPlugin: esbuildDebugIdUploadPlugin,
324326
bundleSizeOptimizationsPlugin: esbuildBundleSizeOptimizationsPlugin,
325327
});

0 commit comments

Comments
 (0)