Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 55 additions & 97 deletions packages/bundler-plugin-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ import {
stripQueryAndHashFromPath,
} from "./utils";

interface SentryUnpluginFactoryOptions {
type InjectionPlugin = (
injectionCode: string,
debugIds: boolean,
logger: Logger
) => UnpluginOptions;
type LegacyPlugins = {
releaseInjectionPlugin: (injectionCode: string) => UnpluginOptions;
componentNameAnnotatePlugin?: (ignoredComponents?: string[]) => UnpluginOptions;
moduleMetadataInjectionPlugin: (injectionCode: string) => UnpluginOptions;
debugIdInjectionPlugin: (logger: Logger) => UnpluginOptions;
};

interface SentryUnpluginFactoryOptions {
injectionPlugin: InjectionPlugin | LegacyPlugins;
componentNameAnnotatePlugin?: (ignoredComponents?: string[]) => UnpluginOptions;
debugIdUploadPlugin: (
upload: (buildArtifacts: string[]) => Promise<void>,
logger: Logger,
Expand All @@ -38,10 +47,8 @@ interface SentryUnpluginFactoryOptions {
* Creates an unplugin instance used to create Sentry plugins for Vite, Rollup, esbuild, and Webpack.
*/
export function sentryUnpluginFactory({
releaseInjectionPlugin,
injectionPlugin,
componentNameAnnotatePlugin,
moduleMetadataInjectionPlugin,
debugIdInjectionPlugin,
debugIdUploadPlugin,
bundleSizeOptimizationsPlugin,
}: SentryUnpluginFactoryOptions): UnpluginInstance<Options | undefined, true> {
Expand Down Expand Up @@ -94,6 +101,8 @@ export function sentryUnpluginFactory({
plugins.push(bundleSizeOptimizationsPlugin(bundleSizeOptimizationReplacementValues));
}

let injectionCode = "";

if (!options.release.inject) {
logger.debug(
"Release injection disabled via `release.inject` option. Will not inject release."
Expand All @@ -103,18 +112,31 @@ export function sentryUnpluginFactory({
"No release name provided. Will not inject release. Please set the `release.name` option to identify your release."
);
} else {
const injectionCode = generateGlobalInjectorCode({
const code = generateGlobalInjectorCode({
release: options.release.name,
injectBuildInformation: options._experiments.injectBuildInformation || false,
});
plugins.push(releaseInjectionPlugin(injectionCode));
if (typeof injectionPlugin !== "function") {
plugins.push(injectionPlugin.releaseInjectionPlugin(code));
} else {
injectionCode += code;
}
}

if (Object.keys(sentryBuildPluginManager.bundleMetadata).length > 0) {
const injectionCode = generateModuleMetadataInjectorCode(
sentryBuildPluginManager.bundleMetadata
);
plugins.push(moduleMetadataInjectionPlugin(injectionCode));
const code = generateModuleMetadataInjectorCode(sentryBuildPluginManager.bundleMetadata);
if (typeof injectionPlugin !== "function") {
plugins.push(injectionPlugin.moduleMetadataInjectionPlugin(code));
} else {
injectionCode += code;
}
}

if (
typeof injectionPlugin === "function" &&
(injectionCode !== "" || options.sourcemaps?.disable !== true)
) {
plugins.push(injectionPlugin(injectionCode, options.sourcemaps?.disable !== true, logger));
}

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

if (options.sourcemaps?.disable !== true) {
plugins.push(debugIdInjectionPlugin(logger));
if (typeof injectionPlugin !== "function") {
plugins.push(injectionPlugin.debugIdInjectionPlugin(logger));
}

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

export function createRollupReleaseInjectionHooks(injectionCode: string): {
renderChunk: RenderChunkHook;
} {
return {
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
if (!isJsFile(chunk.fileName)) {
return null; // returning null means not modifying the chunk at all
}

// Skip empty chunks and HTML facade chunks (Vite MPA)
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
return null;
}

const ms = new MagicString(code, { filename: chunk.fileName });

const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];

if (match) {
// Add injected code after any comments or "use strict" at the beginning of the bundle.
ms.appendLeft(match.length, injectionCode);
} else {
// ms.replace() doesn't work when there is an empty string match (which happens if
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
// need this special case here.
ms.prepend(injectionCode);
}

return {
code: ms.toString(),
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
};
},
};
}

export function createRollupBundleSizeOptimizationHooks(replacementValues: SentrySDKBuildFlags): {
transform: UnpluginOptions["transform"];
} {
Expand All @@ -297,7 +285,10 @@ export function createRollupBundleSizeOptimizationHooks(replacementValues: Sentr
};
}

export function createRollupDebugIdInjectionHooks(): {
export function createRollupInjectionHooks(
injectionCode: string,
debugIds: boolean
): {
renderChunk: RenderChunkHook;
} {
return {
Expand All @@ -311,68 +302,35 @@ export function createRollupDebugIdInjectionHooks(): {
return null;
}

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

if (
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
chunkEndSnippet.includes("//# debugId=")
) {
return null; // Debug ID already present, skip injection
}

const debugId = stringToUUID(code); // generate a deterministic debug ID
const codeToInject = getDebugIdSnippet(debugId);

const ms = new MagicString(code, { filename: chunk.fileName });

const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];
let codeToInject = injectionCode;

if (match) {
// Add injected code after any comments or "use strict" at the beginning of the bundle.
ms.appendLeft(match.length, codeToInject);
} else {
// ms.replace() doesn't work when there is an empty string match (which happens if
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
// need this special case here.
ms.prepend(codeToInject);
}
if (debugIds) {
// Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI)
const chunkStartSnippet = code.slice(0, 6000);
const chunkEndSnippet = code.slice(-500);

return {
code: ms.toString(),
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
};
},
};
}

export function createRollupModuleMetadataInjectionHooks(injectionCode: string): {
renderChunk: RenderChunkHook;
} {
return {
renderChunk(code: string, chunk: { fileName: string; facadeModuleId?: string | null }) {
if (!isJsFile(chunk.fileName)) {
return null; // returning null means not modifying the chunk at all
}
if (
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
chunkEndSnippet.includes("//# debugId=")
) {
return null; // Debug ID already present, skip injection
}
Comment thread
timfish marked this conversation as resolved.

// Skip empty chunks and HTML facade chunks (Vite MPA)
if (shouldSkipCodeInjection(code, chunk.facadeModuleId)) {
return null;
const debugId = stringToUUID(code); // generate a deterministic debug ID
codeToInject += getDebugIdSnippet(debugId);
}

const ms = new MagicString(code, { filename: chunk.fileName });

const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];

if (match) {
// Add injected code after any comments or "use strict" at the beginning of the bundle.
ms.appendLeft(match.length, injectionCode);
ms.appendLeft(match.length, codeToInject);
} else {
// ms.replace() doesn't work when there is an empty string match (which happens if
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
// need this special case here.
ms.prepend(injectionCode);
ms.prepend(codeToInject);
}

return {
Expand Down
8 changes: 5 additions & 3 deletions packages/esbuild-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,11 @@ function esbuildBundleSizeOptimizationsPlugin(
}

const sentryUnplugin = sentryUnpluginFactory({
releaseInjectionPlugin: esbuildReleaseInjectionPlugin,
debugIdInjectionPlugin: esbuildDebugIdInjectionPlugin,
moduleMetadataInjectionPlugin: esbuildModuleMetadataInjectionPlugin,
injectionPlugin: {
releaseInjectionPlugin: esbuildReleaseInjectionPlugin,
debugIdInjectionPlugin: esbuildDebugIdInjectionPlugin,
moduleMetadataInjectionPlugin: esbuildModuleMetadataInjectionPlugin,
},
debugIdUploadPlugin: esbuildDebugIdUploadPlugin,
bundleSizeOptimizationsPlugin: esbuildBundleSizeOptimizationsPlugin,
});
Expand Down
28 changes: 5 additions & 23 deletions packages/rollup-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
sentryUnpluginFactory,
Options,
createRollupReleaseInjectionHooks,
createRollupModuleMetadataInjectionHooks,
createRollupDebugIdInjectionHooks,
createRollupInjectionHooks,
createRollupDebugIdUploadHooks,
SentrySDKBuildFlags,
createRollupBundleSizeOptimizationHooks,
Expand All @@ -12,31 +10,17 @@ import {
} from "@sentry/bundler-plugin-core";
import type { UnpluginOptions } from "unplugin";

function rollupReleaseInjectionPlugin(injectionCode: string): UnpluginOptions {
return {
name: "sentry-rollup-release-injection-plugin",
rollup: createRollupReleaseInjectionHooks(injectionCode),
};
}

function rollupComponentNameAnnotatePlugin(ignoredComponents?: string[]): UnpluginOptions {
return {
name: "sentry-rollup-component-name-annotate-plugin",
rollup: createComponentNameAnnotateHooks(ignoredComponents),
};
}

function rollupDebugIdInjectionPlugin(): UnpluginOptions {
return {
name: "sentry-rollup-debug-id-injection-plugin",
rollup: createRollupDebugIdInjectionHooks(),
};
}

function rollupModuleMetadataInjectionPlugin(injectionCode: string): UnpluginOptions {
function rollupInjectionPlugin(injectionCode: string, debugIds: boolean): UnpluginOptions {
return {
name: "sentry-rollup-module-metadata-injection-plugin",
rollup: createRollupModuleMetadataInjectionHooks(injectionCode),
name: "sentry-rollup-injection-plugin",
rollup: createRollupInjectionHooks(injectionCode, debugIds),
};
}

Expand All @@ -61,10 +45,8 @@ function rollupBundleSizeOptimizationsPlugin(
}

const sentryUnplugin = sentryUnpluginFactory({
releaseInjectionPlugin: rollupReleaseInjectionPlugin,
injectionPlugin: rollupInjectionPlugin,
componentNameAnnotatePlugin: rollupComponentNameAnnotatePlugin,
debugIdInjectionPlugin: rollupDebugIdInjectionPlugin,
moduleMetadataInjectionPlugin: rollupModuleMetadataInjectionPlugin,
debugIdUploadPlugin: rollupDebugIdUploadPlugin,
bundleSizeOptimizationsPlugin: rollupBundleSizeOptimizationsPlugin,
});
Expand Down
26 changes: 4 additions & 22 deletions packages/vite-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
sentryUnpluginFactory,
Options,
createRollupReleaseInjectionHooks,
createRollupModuleMetadataInjectionHooks,
createRollupDebugIdInjectionHooks,
createRollupInjectionHooks,
createRollupDebugIdUploadHooks,
SentrySDKBuildFlags,
createRollupBundleSizeOptimizationHooks,
Expand All @@ -12,13 +10,13 @@ import {
} from "@sentry/bundler-plugin-core";
import { UnpluginOptions, VitePlugin } from "unplugin";

function viteReleaseInjectionPlugin(injectionCode: string): UnpluginOptions {
function viteInjectionPlugin(injectionCode: string, debugIds: boolean): UnpluginOptions {
return {
name: "sentry-vite-release-injection-plugin",
// run `post` to avoid tripping up @rollup/plugin-commonjs when cjs is used
// as we inject an `import` statement
enforce: "post" as const, // need this so that vite runs the resolveId hook
vite: createRollupReleaseInjectionHooks(injectionCode),
vite: createRollupInjectionHooks(injectionCode, debugIds),
};
}

Expand All @@ -30,20 +28,6 @@ function viteComponentNameAnnotatePlugin(ignoredComponents?: string[]): Unplugin
};
}

function viteDebugIdInjectionPlugin(): UnpluginOptions {
return {
name: "sentry-vite-debug-id-injection-plugin",
vite: createRollupDebugIdInjectionHooks(),
};
}

function viteModuleMetadataInjectionPlugin(injectionCode: string): UnpluginOptions {
return {
name: "sentry-vite-module-metadata-injection-plugin",
vite: createRollupModuleMetadataInjectionHooks(injectionCode),
};
}

function viteDebugIdUploadPlugin(
upload: (buildArtifacts: string[]) => Promise<void>,
logger: Logger,
Expand All @@ -65,10 +49,8 @@ function viteBundleSizeOptimizationsPlugin(
}

const sentryUnplugin = sentryUnpluginFactory({
releaseInjectionPlugin: viteReleaseInjectionPlugin,
injectionPlugin: viteInjectionPlugin,
componentNameAnnotatePlugin: viteComponentNameAnnotatePlugin,
debugIdInjectionPlugin: viteDebugIdInjectionPlugin,
moduleMetadataInjectionPlugin: viteModuleMetadataInjectionPlugin,
debugIdUploadPlugin: viteDebugIdUploadPlugin,
bundleSizeOptimizationsPlugin: viteBundleSizeOptimizationsPlugin,
});
Expand Down
Loading
Loading