From 89b6e47dd898afd9a483dedd4c077ccfed6db087 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 2 Apr 2025 10:57:56 +0200 Subject: [PATCH 1/4] feat(webpack): Add `sentry/webpack-plugin/webpack5` export for webpack 5.1+ and compatible environments --- packages/webpack-plugin/README_TEMPLATE.md | 2 + packages/webpack-plugin/package.json | 15 +- ...lup.config.js => rollup.config.default.js} | 0 .../webpack-plugin/rollup.config.webpack5.js | 48 ++++ packages/webpack-plugin/src/index.ts | 202 +--------------- packages/webpack-plugin/src/plugin.ts | 227 ++++++++++++++++++ packages/webpack-plugin/src/webpack5.ts | 11 + packages/webpack-plugin/test/webpack5.test.ts | 24 ++ 8 files changed, 330 insertions(+), 199 deletions(-) rename packages/webpack-plugin/{rollup.config.js => rollup.config.default.js} (100%) create mode 100644 packages/webpack-plugin/rollup.config.webpack5.js create mode 100644 packages/webpack-plugin/src/plugin.ts create mode 100644 packages/webpack-plugin/src/webpack5.ts create mode 100644 packages/webpack-plugin/test/webpack5.test.ts diff --git a/packages/webpack-plugin/README_TEMPLATE.md b/packages/webpack-plugin/README_TEMPLATE.md index fa02bed1..2b25c4a2 100644 --- a/packages/webpack-plugin/README_TEMPLATE.md +++ b/packages/webpack-plugin/README_TEMPLATE.md @@ -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 ... diff --git a/packages/webpack-plugin/package.json b/packages/webpack-plugin/package.json index 699d6cae..8a96c746 100644 --- a/packages/webpack-plugin/package.json +++ b/packages/webpack-plugin/package.json @@ -23,16 +23,25 @@ "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", + "build:rollup": "run-p build:rollup:default build:rollup:webpack5", + "build:rollup:default": "rollup --config rollup.config.default.js", + "build:rollup:webpack5": "rollup --config rollup.config.webpack5.js", + "build:rollup:watch": "run-p build:rollup:default:watch build:rollup:webpack5:watch", + "build:rollup:default:watch": "rollup --config rollup.config.default.js --watch --no-watch.clearScreen", + "build:rollup:webpack5:watch": "rollup --config rollup.config.webpack5.js --watch --no-watch.clearScreen", "build:types": "tsc --project types.tsconfig.json", "build:types:watch": "tsc --project types.tsconfig.json --watch --preserveWatchOutput", "check:types": "run-p check:types:src check:types:test", diff --git a/packages/webpack-plugin/rollup.config.js b/packages/webpack-plugin/rollup.config.default.js similarity index 100% rename from packages/webpack-plugin/rollup.config.js rename to packages/webpack-plugin/rollup.config.default.js diff --git a/packages/webpack-plugin/rollup.config.webpack5.js b/packages/webpack-plugin/rollup.config.webpack5.js new file mode 100644 index 00000000..401f082a --- /dev/null +++ b/packages/webpack-plugin/rollup.config.webpack5.js @@ -0,0 +1,48 @@ +import resolve from "@rollup/plugin-node-resolve"; +import babel from "@rollup/plugin-babel"; +import packageJson from "./package.json"; +import modulePackage from "module"; + +const input = ["src/webpack5.ts"]; + +const extensions = [".ts"]; + +export default { + input, + external: [...Object.keys(packageJson.dependencies), ...modulePackage.builtinModules, "webpack"], + onwarn: (warning) => { + if (warning.code === "CIRCULAR_DEPENDENCY") { + // Circular dependencies are usually not a big deal for us so let's just warn about them + console.warn(warning.message); + return; + } + // Warnings are usually high-consequence for us so let's throw to catch them + throw new Error(warning.message); + }, + plugins: [ + resolve({ + extensions, + rootDir: "./src", + preferBuiltins: true, + }), + babel({ + extensions, + babelHelpers: "bundled", + include: ["src/**/*"], + }), + ], + output: [ + { + file: packageJson.exports["./webpack5"].import, + format: "esm", + exports: "named", + sourcemap: true, + }, + { + file: packageJson.exports["./webpack5"].require, + format: "cjs", + exports: "named", + sourcemap: true, + }, + ], +}; diff --git a/packages/webpack-plugin/src/index.ts b/packages/webpack-plugin/src/index.ts index 089d9cf4..fcd08b0a 100644 --- a/packages/webpack-plugin/src/index.ts +++ b/packages/webpack-plugin/src/index.ts @@ -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, - 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).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; diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts new file mode 100644 index 00000000..9aee9f46 --- /dev/null +++ b/packages/webpack-plugin/src/plugin.ts @@ -0,0 +1,227 @@ +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"; + +// since webpack 5.1 compiler contains webpack module so plugins always use correct webpack version +// https://github.com/webpack/webpack/commit/65eca2e529ce1d79b79200d4bdb1ce1b81141459 + +interface BannerPluginCallbackArg { + chunk?: { + hash?: string; + contentHash?: { + javascript?: string; + }; + }; +} + +type UnsafeBannerPlugin = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (options: any): unknown; +}; + +type UnsafeDefinePlugin = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (options: any): unknown; +}; + +function webpackReleaseInjectionPlugin( + UnsafeBannerPlugin: UnsafeBannerPlugin | undefined +): (injectionCode: string) => UnpluginOptions { + return (injectionCode: string): UnpluginOptions => ({ + 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 || UnsafeBannerPlugin; + + 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, + }) + ); + }, + }); +} + +function webpackComponentNameAnnotatePlugin(): (ignoredComponents?: string[]) => UnpluginOptions { + return (ignoredComponents?: string[]) => ({ + 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( + UnsafeDefinePlugin: UnsafeDefinePlugin | undefined +): (replacementValues: SentrySDKBuildFlags) => UnpluginOptions { + return (replacementValues: SentrySDKBuildFlags) => ({ + 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 || UnsafeDefinePlugin; + + 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( + UnsafeBannerPlugin: UnsafeBannerPlugin | undefined +): () => 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 || UnsafeBannerPlugin; + + 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, + logger: Logger, + forceExitOnBuildCompletion?: boolean +) => UnpluginOptions { + const pluginName = "sentry-webpack-debug-id-upload-plugin"; + return ( + upload: (buildArtifacts: string[]) => Promise, + logger: Logger, + forceExitOnBuildCompletion?: boolean + ) => ({ + 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).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( + UnsafeBannerPlugin: UnsafeBannerPlugin | undefined +): (injectionCode: string) => UnpluginOptions { + return (injectionCode: string) => ({ + 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 || UnsafeBannerPlugin; + + 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, + }) + ); + }, + }); +} + +export function sentryWebpackUnpluginFactory({ + BannerPlugin, + DefinePlugin, +}: { + BannerPlugin?: UnsafeBannerPlugin; + DefinePlugin?: UnsafeDefinePlugin; +} = {}): ReturnType { + return sentryUnpluginFactory({ + releaseInjectionPlugin: webpackReleaseInjectionPlugin(BannerPlugin), + componentNameAnnotatePlugin: webpackComponentNameAnnotatePlugin(), + moduleMetadataInjectionPlugin: webpackModuleMetadataInjectionPlugin(BannerPlugin), + debugIdInjectionPlugin: webpackDebugIdInjectionPlugin(BannerPlugin), + debugIdUploadPlugin: webpackDebugIdUploadPlugin(), + bundleSizeOptimizationsPlugin: webpackBundleSizeOptimizationsPlugin(DefinePlugin), + }); +} + +export 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; + }; +}; diff --git a/packages/webpack-plugin/src/webpack5.ts b/packages/webpack-plugin/src/webpack5.ts new file mode 100644 index 00000000..e2adaf30 --- /dev/null +++ b/packages/webpack-plugin/src/webpack5.ts @@ -0,0 +1,11 @@ +import { SentryWebpackPluginOptions, sentryWebpackUnpluginFactory } from "./plugin"; + +const sentryUnplugin = sentryWebpackUnpluginFactory(); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const sentryWebpackPlugin: (options?: SentryWebpackPluginOptions) => any = + sentryUnplugin.webpack; + +export { sentryCliBinaryExists } from "@sentry/bundler-plugin-core"; + +export type { SentryWebpackPluginOptions }; diff --git a/packages/webpack-plugin/test/webpack5.test.ts b/packages/webpack-plugin/test/webpack5.test.ts new file mode 100644 index 00000000..09d7df05 --- /dev/null +++ b/packages/webpack-plugin/test/webpack5.test.ts @@ -0,0 +1,24 @@ +import { Plugin } from "webpack"; +import { sentryWebpackPlugin } from "../src/webpack5"; + +jest.mock("webpack", () => { + throw new Error("Webpack 5 version of the plugin should use module from compiler."); +}); + +test("Webpack plugin should exist", () => { + expect(sentryWebpackPlugin).toBeDefined(); + expect(typeof sentryWebpackPlugin).toBe("function"); +}); + +describe("sentryWebpackPlugin", () => { + it("returns a webpack plugin", () => { + const plugin = sentryWebpackPlugin({ + authToken: "test-token", + org: "test-org", + project: "test-project", + }) as Plugin; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + expect(plugin).toEqual({ apply: expect.any(Function) }); + }); +}); From ac046ccae7ffc2fa9bfde25579b113a26cba97a5 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 2 Apr 2025 11:14:40 +0200 Subject: [PATCH 2/4] Consolidate rollup configs --- packages/webpack-plugin/package.json | 8 +--- ...lup.config.default.js => rollup.config.js} | 7 +-- .../webpack-plugin/rollup.config.webpack5.js | 48 ------------------- 3 files changed, 6 insertions(+), 57 deletions(-) rename packages/webpack-plugin/{rollup.config.default.js => rollup.config.js} (88%) delete mode 100644 packages/webpack-plugin/rollup.config.webpack5.js diff --git a/packages/webpack-plugin/package.json b/packages/webpack-plugin/package.json index 8a96c746..3731ef01 100644 --- a/packages/webpack-plugin/package.json +++ b/packages/webpack-plugin/package.json @@ -36,12 +36,8 @@ "scripts": { "build": "rimraf ./dist && run-p build:rollup build:types", "build:watch": "run-p build:rollup:watch build:types:watch", - "build:rollup": "run-p build:rollup:default build:rollup:webpack5", - "build:rollup:default": "rollup --config rollup.config.default.js", - "build:rollup:webpack5": "rollup --config rollup.config.webpack5.js", - "build:rollup:watch": "run-p build:rollup:default:watch build:rollup:webpack5:watch", - "build:rollup:default:watch": "rollup --config rollup.config.default.js --watch --no-watch.clearScreen", - "build:rollup:webpack5:watch": "rollup --config rollup.config.webpack5.js --watch --no-watch.clearScreen", + "build:rollup": "rollup --config rollup.config.js", + "build:rollup:watch": "rollup --config rollup.config.js --watch --no-watch.clearScreen", "build:types": "tsc --project types.tsconfig.json", "build:types:watch": "tsc --project types.tsconfig.json --watch --preserveWatchOutput", "check:types": "run-p check:types:src check:types:test", diff --git a/packages/webpack-plugin/rollup.config.default.js b/packages/webpack-plugin/rollup.config.js similarity index 88% rename from packages/webpack-plugin/rollup.config.default.js rename to packages/webpack-plugin/rollup.config.js index 8751124b..3f0be962 100644 --- a/packages/webpack-plugin/rollup.config.default.js +++ b/packages/webpack-plugin/rollup.config.js @@ -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"]; @@ -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, diff --git a/packages/webpack-plugin/rollup.config.webpack5.js b/packages/webpack-plugin/rollup.config.webpack5.js deleted file mode 100644 index 401f082a..00000000 --- a/packages/webpack-plugin/rollup.config.webpack5.js +++ /dev/null @@ -1,48 +0,0 @@ -import resolve from "@rollup/plugin-node-resolve"; -import babel from "@rollup/plugin-babel"; -import packageJson from "./package.json"; -import modulePackage from "module"; - -const input = ["src/webpack5.ts"]; - -const extensions = [".ts"]; - -export default { - input, - external: [...Object.keys(packageJson.dependencies), ...modulePackage.builtinModules, "webpack"], - onwarn: (warning) => { - if (warning.code === "CIRCULAR_DEPENDENCY") { - // Circular dependencies are usually not a big deal for us so let's just warn about them - console.warn(warning.message); - return; - } - // Warnings are usually high-consequence for us so let's throw to catch them - throw new Error(warning.message); - }, - plugins: [ - resolve({ - extensions, - rootDir: "./src", - preferBuiltins: true, - }), - babel({ - extensions, - babelHelpers: "bundled", - include: ["src/**/*"], - }), - ], - output: [ - { - file: packageJson.exports["./webpack5"].import, - format: "esm", - exports: "named", - sourcemap: true, - }, - { - file: packageJson.exports["./webpack5"].require, - format: "cjs", - exports: "named", - sourcemap: true, - }, - ], -}; From bad95841940c45242c5d888cf9d9db9019858d33 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 2 Apr 2025 17:53:14 +0200 Subject: [PATCH 3/4] rename plugin to webpack4and5 --- packages/webpack-plugin/src/index.ts | 2 +- packages/webpack-plugin/src/{plugin.ts => webpack4and5.ts} | 0 packages/webpack-plugin/src/webpack5.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/webpack-plugin/src/{plugin.ts => webpack4and5.ts} (100%) diff --git a/packages/webpack-plugin/src/index.ts b/packages/webpack-plugin/src/index.ts index fcd08b0a..9b1bfa24 100644 --- a/packages/webpack-plugin/src/index.ts +++ b/packages/webpack-plugin/src/index.ts @@ -1,4 +1,4 @@ -import { SentryWebpackPluginOptions, sentryWebpackUnpluginFactory } from "./plugin"; +import { SentryWebpackPluginOptions, sentryWebpackUnpluginFactory } from "./webpack4and5"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore webpack is a peer dep diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/webpack4and5.ts similarity index 100% rename from packages/webpack-plugin/src/plugin.ts rename to packages/webpack-plugin/src/webpack4and5.ts diff --git a/packages/webpack-plugin/src/webpack5.ts b/packages/webpack-plugin/src/webpack5.ts index e2adaf30..421f5eb8 100644 --- a/packages/webpack-plugin/src/webpack5.ts +++ b/packages/webpack-plugin/src/webpack5.ts @@ -1,4 +1,4 @@ -import { SentryWebpackPluginOptions, sentryWebpackUnpluginFactory } from "./plugin"; +import { SentryWebpackPluginOptions, sentryWebpackUnpluginFactory } from "./webpack4and5"; const sentryUnplugin = sentryWebpackUnpluginFactory(); From 8c6d3bee3286fbc5b9271b4fe0e7047e2d51e329 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 2 Apr 2025 17:58:13 +0200 Subject: [PATCH 4/4] add explanation why plugins are passed thru a factory --- packages/webpack-plugin/src/webpack4and5.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/webpack-plugin/src/webpack4and5.ts b/packages/webpack-plugin/src/webpack4and5.ts index 9aee9f46..e9304038 100644 --- a/packages/webpack-plugin/src/webpack4and5.ts +++ b/packages/webpack-plugin/src/webpack4and5.ts @@ -195,6 +195,14 @@ function webpackModuleMetadataInjectionPlugin( }); } +/** + * The factory function accepts BannerPlugin and DefinePlugin classes in + * order to avoid direct dependencies on webpack. + * + * This allow us to export version of the plugin for webpack 5.1+ and compatible environments. + * + * Since webpack 5.1 compiler contains webpack module so plugins always use correct webpack version. + */ export function sentryWebpackUnpluginFactory({ BannerPlugin, DefinePlugin,