Skip to content

feat(webpack): Add sentry/webpack-plugin/webpack5 export for webpack 5.1+ and compatible environments #715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 2, 2025
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
2 changes: 2 additions & 0 deletions packages/webpack-plugin/README_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pnpm add @sentry/webpack-plugin --save-dev
```js
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");
// for webpack 5.1 and webpack compatible environments
// const { sentryWebpackPlugin } = require("@sentry/webpack-plugin/webpack5");

module.exports = {
// ... other config above ...
Expand Down
7 changes: 6 additions & 1 deletion packages/webpack-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@
"import": "./dist/esm/index.mjs",
"require": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts"
},
"./webpack5": {
"import": "./dist/esm/webpack5.mjs",
"require": "./dist/cjs/webpack5.js",
"types": "./dist/types/webpack5.d.ts"
}
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.mjs",
"types": "dist/types/index.d.ts",
"scripts": {
"build": "rimraf ./out && run-p build:rollup build:types",
"build": "rimraf ./dist && run-p build:rollup build:types",
"build:watch": "run-p build:rollup:watch build:types:watch",
"build:rollup": "rollup --config rollup.config.js",
"build:rollup:watch": "rollup --config rollup.config.js --watch --no-watch.clearScreen",
Expand Down
7 changes: 4 additions & 3 deletions packages/webpack-plugin/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import babel from "@rollup/plugin-babel";
import packageJson from "./package.json";
import modulePackage from "module";

const input = ["src/index.ts"];
const input = ["src/index.ts", "src/webpack5.ts"];

const extensions = [".ts"];

Expand Down Expand Up @@ -33,13 +33,14 @@ export default {
],
output: [
{
file: packageJson.module,
dir: "./dist/esm",
format: "esm",
exports: "named",
sourcemap: true,
entryFileNames: "[name].mjs",
},
{
file: packageJson.main,
dir: "./dist/cjs",
format: "cjs",
exports: "named",
sourcemap: true,
Expand Down
202 changes: 6 additions & 196 deletions packages/webpack-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,208 +1,18 @@
import {
getDebugIdSnippet,
Options,
sentryUnpluginFactory,
stringToUUID,
SentrySDKBuildFlags,
createComponentNameAnnotateHooks,
Logger,
} from "@sentry/bundler-plugin-core";
import * as path from "path";
import { UnpluginOptions } from "unplugin";
import { v4 as uuidv4 } from "uuid";
import { SentryWebpackPluginOptions, sentryWebpackUnpluginFactory } from "./plugin";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack is a peer dep
import * as webpack4or5 from "webpack";

interface BannerPluginCallbackArg {
chunk?: {
hash?: string;
contentHash?: {
javascript?: string;
};
};
}
const BannerPlugin = webpack4or5?.BannerPlugin || webpack4or5?.default?.BannerPlugin;

function webpackReleaseInjectionPlugin(injectionCode: string): UnpluginOptions {
return {
name: "sentry-webpack-release-injection-plugin",
webpack(compiler) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const BannerPlugin =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
compiler?.webpack?.BannerPlugin ||
webpack4or5?.BannerPlugin ||
webpack4or5?.default?.BannerPlugin;
compiler.options.plugins = compiler.options.plugins || [];
compiler.options.plugins.push(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
new BannerPlugin({
raw: true,
include: /\.(js|ts|jsx|tsx|mjs|cjs)(\?[^?]*)?(#[^#]*)?$/,
banner: injectionCode,
})
);
},
};
}
const DefinePlugin = webpack4or5?.DefinePlugin || webpack4or5?.default?.DefinePlugin;

function webpackComponentNameAnnotatePlugin(ignoredComponents?: string[]): UnpluginOptions {
return {
name: "sentry-webpack-component-name-annotate-plugin",
enforce: "pre",
// Webpack needs this hook for loader logic, so the plugin is not run on unsupported file types
transformInclude(id) {
return id.endsWith(".tsx") || id.endsWith(".jsx");
},
transform: createComponentNameAnnotateHooks(ignoredComponents).transform,
};
}

function webpackBundleSizeOptimizationsPlugin(
replacementValues: SentrySDKBuildFlags
): UnpluginOptions {
return {
name: "sentry-webpack-bundle-size-optimizations-plugin",
webpack(compiler) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const DefinePlugin =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
compiler?.webpack?.DefinePlugin ||
webpack4or5?.DefinePlugin ||
webpack4or5?.default?.DefinePlugin;
compiler.options.plugins = compiler.options.plugins || [];
compiler.options.plugins.push(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
new DefinePlugin({
...replacementValues,
})
);
},
};
}

function webpackDebugIdInjectionPlugin(): UnpluginOptions {
return {
name: "sentry-webpack-debug-id-injection-plugin",
webpack(compiler) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const BannerPlugin =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
compiler?.webpack?.BannerPlugin ||
webpack4or5?.BannerPlugin ||
webpack4or5?.default?.BannerPlugin;
compiler.options.plugins = compiler.options.plugins || [];
compiler.options.plugins.push(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
new BannerPlugin({
raw: true,
include: /\.(js|ts|jsx|tsx|mjs|cjs)(\?[^?]*)?(#[^#]*)?$/,
banner: (arg?: BannerPluginCallbackArg) => {
const hash = arg?.chunk?.contentHash?.javascript ?? arg?.chunk?.hash;
const debugId = hash ? stringToUUID(hash) : uuidv4();
return getDebugIdSnippet(debugId);
},
})
);
},
};
}

function webpackDebugIdUploadPlugin(
upload: (buildArtifacts: string[]) => Promise<void>,
logger: Logger,
forceExitOnBuildCompletion?: boolean
): UnpluginOptions {
const pluginName = "sentry-webpack-debug-id-upload-plugin";
return {
name: pluginName,
webpack(compiler) {
compiler.hooks.afterEmit.tapAsync(pluginName, (compilation, callback: () => void) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const outputPath = (compilation.outputOptions.path as string | undefined) ?? path.resolve();
const buildArtifacts = Object.keys(compilation.assets as Record<string, unknown>).map(
(asset) => path.join(outputPath, asset)
);
void upload(buildArtifacts).then(() => {
callback();
});
});

if (forceExitOnBuildCompletion && compiler.options.mode === "production") {
compiler.hooks.done.tap(pluginName, () => {
setTimeout(() => {
logger.debug("Exiting process after debug file upload");
process.exit(0);
});
});
}
},
};
}

function webpackModuleMetadataInjectionPlugin(injectionCode: string): UnpluginOptions {
return {
name: "sentry-webpack-module-metadata-injection-plugin",
webpack(compiler) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const BannerPlugin =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore webpack version compatibility shenanigans
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
compiler?.webpack?.BannerPlugin ||
webpack4or5?.BannerPlugin ||
webpack4or5?.default?.BannerPlugin;
compiler.options.plugins = compiler.options.plugins || [];
compiler.options.plugins.push(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
new BannerPlugin({
raw: true,
include: /\.(js|ts|jsx|tsx|mjs|cjs)(\?[^?]*)?(#[^#]*)?$/,
banner: injectionCode,
})
);
},
};
}

const sentryUnplugin = sentryUnpluginFactory({
releaseInjectionPlugin: webpackReleaseInjectionPlugin,
componentNameAnnotatePlugin: webpackComponentNameAnnotatePlugin,
moduleMetadataInjectionPlugin: webpackModuleMetadataInjectionPlugin,
debugIdInjectionPlugin: webpackDebugIdInjectionPlugin,
debugIdUploadPlugin: webpackDebugIdUploadPlugin,
bundleSizeOptimizationsPlugin: webpackBundleSizeOptimizationsPlugin,
const sentryUnplugin = sentryWebpackUnpluginFactory({
BannerPlugin,
DefinePlugin,
});

type SentryWebpackPluginOptions = Options & {
_experiments?: Options["_experiments"] & {
/**
* If enabled, the webpack plugin will exit the build process after the build completes.
* Use this with caution, as it will terminate the process.
*
* More information: https://github.com/getsentry/sentry-javascript-bundler-plugins/issues/345
*
* @default false
*/
forceExitOnBuildCompletion?: boolean;
};
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const sentryWebpackPlugin: (options?: SentryWebpackPluginOptions) => any =
sentryUnplugin.webpack;
Expand Down
Loading
Loading