From e302d5666951abcf4d283f59bb3f31a9d0345c47 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Nov 2022 14:08:46 +0100 Subject: [PATCH 1/5] feat(core): Add `SENTRY_RELEASES` variable injection --- packages/bundler-plugin-core/src/index.ts | 30 +++++++++++++-- .../releases-injection/input/entrypoint.js | 2 + .../releases-injection.test.ts | 38 +++++++++++++++++++ .../fixtures/releases-injection/setup.ts | 14 +++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 packages/integration-tests/fixtures/releases-injection/input/entrypoint.js create mode 100644 packages/integration-tests/fixtures/releases-injection/releases-injection.test.ts create mode 100644 packages/integration-tests/fixtures/releases-injection/setup.ts diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 4142b0d7..60b427d0 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -13,7 +13,7 @@ import "@sentry/tracing"; import { addSpanToTransaction, captureMinimalError, makeSentryClient } from "./sentry/telemetry"; import { Span, Transaction } from "@sentry/types"; import { createLogger } from "./sentry/logger"; -import { normalizeUserOptions } from "./options-mapping"; +import { InternalOptions, normalizeUserOptions } from "./options-mapping"; import { getSentryCli } from "./sentry/cli"; // We prefix the polyfill id with \0 to tell other plugins not to try to load or transform it. @@ -185,7 +185,11 @@ const unplugin = createUnplugin((options, unpluginMetaContext) => { }); if (id === RELEASE_INJECTOR_ID) { - return generateGlobalInjectorCode({ release: internalOptions.release }); + return generateGlobalInjectorCode({ + release: internalOptions.release, + org: internalOptions.org, + project: internalOptions.project, + }); } else { return undefined; } @@ -320,10 +324,18 @@ const unplugin = createUnplugin((options, unpluginMetaContext) => { * Generates code for the "sentry-release-injector" which is responsible for setting the global `SENTRY_RELEASE` * variable. */ -function generateGlobalInjectorCode({ release }: { release: string }) { +function generateGlobalInjectorCode({ + release, + org, + project, +}: { + release: string; + org?: string; + project?: string; +}) { // The code below is mostly ternary operators because it saves bundle size. // The checks are to support as many environments as possible. (Node.js, Browser, webworkers, etc.) - return ` + let code = ` var _global = typeof window !== 'undefined' ? window : @@ -334,6 +346,16 @@ function generateGlobalInjectorCode({ release }: { release: string }) { {}; _global.SENTRY_RELEASE={id:"${release}"};`; + + if (project) { + const key = org ? `${project}@${org}` : project; + code += ` + _global.SENTRY_RELEASES=_global.SENTRY_RELEASES || {}; + _global.SENTRY_RELEASES["${key}"]={id:"${release}"}; + `; + } + + return code; } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/integration-tests/fixtures/releases-injection/input/entrypoint.js b/packages/integration-tests/fixtures/releases-injection/input/entrypoint.js new file mode 100644 index 00000000..1f0821b8 --- /dev/null +++ b/packages/integration-tests/fixtures/releases-injection/input/entrypoint.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call +process.stdout.write(global.SENTRY_RELEASES["releasesProject@releasesOrg"].id.toString()); diff --git a/packages/integration-tests/fixtures/releases-injection/releases-injection.test.ts b/packages/integration-tests/fixtures/releases-injection/releases-injection.test.ts new file mode 100644 index 00000000..7d889cec --- /dev/null +++ b/packages/integration-tests/fixtures/releases-injection/releases-injection.test.ts @@ -0,0 +1,38 @@ +import childProcess from "child_process"; +import path from "path"; + +/** + * Runs a node file in a seprate process. + * + * @param bundlePath Path of node file to run + * @returns Stdout of the process + */ +function checkBundle(bundlePath: string): void { + const processOutput = childProcess.execSync(`node ${bundlePath}`, { encoding: "utf-8" }); + expect(processOutput).toBe("I AM A RELEASE!"); +} + +test("esbuild bundle", () => { + expect.assertions(1); + checkBundle(path.join(__dirname, "./out/esbuild/index.js")); +}); + +test("rollup bundle", () => { + expect.assertions(1); + checkBundle(path.join(__dirname, "./out/rollup/index.js")); +}); + +test("vite bundle", () => { + expect.assertions(1); + checkBundle(path.join(__dirname, "./out/vite/index.js")); +}); + +test("webpack 4 bundle", () => { + expect.assertions(1); + checkBundle(path.join(__dirname, "./out/webpack4/index.js")); +}); + +test("webpack 5 bundle", () => { + expect.assertions(1); + checkBundle(path.join(__dirname, "./out/webpack5/index.js")); +}); diff --git a/packages/integration-tests/fixtures/releases-injection/setup.ts b/packages/integration-tests/fixtures/releases-injection/setup.ts new file mode 100644 index 00000000..b6841ede --- /dev/null +++ b/packages/integration-tests/fixtures/releases-injection/setup.ts @@ -0,0 +1,14 @@ +import { Options } from "@sentry/bundler-plugin-core"; +import * as path from "path"; +import { createCjsBundles } from "../../utils/create-cjs-bundles"; + +const entryPointPath = path.resolve(__dirname, "./input/entrypoint.js"); +const outputDir = path.resolve(__dirname, "./out"); + +createCjsBundles({ index: entryPointPath }, outputDir, { + release: "I AM A RELEASE!", + project: "releasesProject", + org: "releasesOrg", + include: outputDir, + dryRun: true, +} as Options); From ef914022873cf0d36ba0b14ef1fbca553081a23a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Nov 2022 14:37:57 +0100 Subject: [PATCH 2/5] make this behaviour opt-in --- packages/bundler-plugin-core/src/index.ts | 5 ++++- packages/bundler-plugin-core/src/options-mapping.ts | 2 ++ packages/bundler-plugin-core/src/types.ts | 9 +++++++++ .../bundler-plugin-core/test/option-mappings.test.ts | 2 ++ .../fixtures/releases-injection/setup.ts | 1 + 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 60b427d0..393e2fde 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -187,6 +187,7 @@ const unplugin = createUnplugin((options, unpluginMetaContext) => { if (id === RELEASE_INJECTOR_ID) { return generateGlobalInjectorCode({ release: internalOptions.release, + injectReleasesMap: internalOptions.injectReleasesMap, org: internalOptions.org, project: internalOptions.project, }); @@ -326,10 +327,12 @@ const unplugin = createUnplugin((options, unpluginMetaContext) => { */ function generateGlobalInjectorCode({ release, + injectReleasesMap, org, project, }: { release: string; + injectReleasesMap: boolean; org?: string; project?: string; }) { @@ -347,7 +350,7 @@ function generateGlobalInjectorCode({ _global.SENTRY_RELEASE={id:"${release}"};`; - if (project) { + if (injectReleasesMap && project) { const key = org ? `${project}@${org}` : project; code += ` _global.SENTRY_RELEASES=_global.SENTRY_RELEASES || {}; diff --git a/packages/bundler-plugin-core/src/options-mapping.ts b/packages/bundler-plugin-core/src/options-mapping.ts index 9c7daaec..8485f22e 100644 --- a/packages/bundler-plugin-core/src/options-mapping.ts +++ b/packages/bundler-plugin-core/src/options-mapping.ts @@ -15,6 +15,7 @@ type RequiredInternalOptions = Required< | "silent" | "cleanArtifacts" | "telemetry" + | "injectReleasesMap" > >; @@ -100,6 +101,7 @@ export function normalizeUserOptions(userOptions: UserOptions): InternalOptions entries, include, configFile: userOptions.configFile, + injectReleasesMap: userOptions.injectReleasesMap ?? false, }; } diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 941ca96e..54059978 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -179,6 +179,15 @@ export type Options = Omit & { * defaults from ~/.sentryclirc are always loaded */ configFile?: string; + + /** + * If set to true, the plugin will inject an additional `SENTRY_RELEASES` variable that + * maps from `{org}@{project}` to the `release` value. This might be helpful for webpack + * module federation or micro frontend setups. + * + * Defaults to `false` + */ + injectReleasesMap?: boolean; }; export type IncludeEntry = { diff --git a/packages/bundler-plugin-core/test/option-mappings.test.ts b/packages/bundler-plugin-core/test/option-mappings.test.ts index 8267bc29..f440b9f5 100644 --- a/packages/bundler-plugin-core/test/option-mappings.test.ts +++ b/packages/bundler-plugin-core/test/option-mappings.test.ts @@ -35,6 +35,7 @@ describe("normalizeUserOptions()", () => { telemetry: true, url: "https://sentry.io/", vcsRemote: "origin", + injectReleasesMap: false, }); }); @@ -78,6 +79,7 @@ describe("normalizeUserOptions()", () => { telemetry: true, url: "https://sentry.io/", vcsRemote: "origin", + injectReleasesMap: false, }); }); }); diff --git a/packages/integration-tests/fixtures/releases-injection/setup.ts b/packages/integration-tests/fixtures/releases-injection/setup.ts index b6841ede..8807e676 100644 --- a/packages/integration-tests/fixtures/releases-injection/setup.ts +++ b/packages/integration-tests/fixtures/releases-injection/setup.ts @@ -11,4 +11,5 @@ createCjsBundles({ index: entryPointPath }, outputDir, { org: "releasesOrg", include: outputDir, dryRun: true, + injectReleasesMap: true, } as Options); From cda42bda4d5b596ec1b35e43467ab5b9ae0326b8 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Nov 2022 14:43:53 +0100 Subject: [PATCH 3/5] add migration note --- MIGRATION.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 81d0ec68..bedd2e73 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -48,3 +48,9 @@ In version 2 we removed this functionality because it lead to intransparent nami Going forward, if you need similar functionality, we recommend providing folder paths in the `include` and `include.paths` options and narrowing down the matched files with the `ignore`, `ignoreFile` or `ext` options. The `ignore` and `ignoreFile` options will still allow globbing patterns. + +### Injecting `SENTRY_RELEASES` Map + +Previously, the webpack plugin always injected a `SENTRY_RELEASES` map into the global object which would map from `project@org` to the `release` value. In version 2, we made this behaviour opt-in by setting the `injectReleasesMap` option in the plugin options to `true`. + +The purpose was to support module-federated projects or micro frontend setups where multiple projects would want to access the global release variable. However, Sentry SDKs by default never accessed this variable so it would require manual user-intervention to only make use of it. Making this behaviour opt-in decreases the bundle size impact of our plugin for the majority of users. From 0599fe39908a79b1d74e2d8b498a2acd0cb4dd42 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Nov 2022 14:45:19 +0100 Subject: [PATCH 4/5] adjust wording --- MIGRATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index bedd2e73..5963841a 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -51,6 +51,6 @@ The `ignore` and `ignoreFile` options will still allow globbing patterns. ### Injecting `SENTRY_RELEASES` Map -Previously, the webpack plugin always injected a `SENTRY_RELEASES` map into the global object which would map from `project@org` to the `release` value. In version 2, we made this behaviour opt-in by setting the `injectReleasesMap` option in the plugin options to `true`. +Previously, the webpack plugin always injected a `SENTRY_RELEASES` variable into the global object which would map from `project@org` to the `release` value. In version 2, we made this behaviour opt-in by setting the `injectReleasesMap` option in the plugin options to `true`. -The purpose was to support module-federated projects or micro frontend setups where multiple projects would want to access the global release variable. However, Sentry SDKs by default never accessed this variable so it would require manual user-intervention to only make use of it. Making this behaviour opt-in decreases the bundle size impact of our plugin for the majority of users. +The purpose of this option is to support module-federated projects or micro frontend setups where multiple projects would want to access the global release variable. However, Sentry SDKs by default never accessed this variable so it would require manual user-intervention to make use of it. Making this behaviour opt-in decreases the bundle size impact of our plugin for the majority of users. From ecac79150bcbe998697065adfa47986daec2d75a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Nov 2022 14:50:54 +0100 Subject: [PATCH 5/5] fix linter error --- packages/bundler-plugin-core/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 393e2fde..a371e3d7 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -13,7 +13,7 @@ import "@sentry/tracing"; import { addSpanToTransaction, captureMinimalError, makeSentryClient } from "./sentry/telemetry"; import { Span, Transaction } from "@sentry/types"; import { createLogger } from "./sentry/logger"; -import { InternalOptions, normalizeUserOptions } from "./options-mapping"; +import { normalizeUserOptions } from "./options-mapping"; import { getSentryCli } from "./sentry/cli"; // We prefix the polyfill id with \0 to tell other plugins not to try to load or transform it.