Skip to content

Commit c576aff

Browse files
authored
feat: Replace entries option with releaseInjectionTargets (#123)
1 parent a73d0dd commit c576aff

File tree

17 files changed

+204
-223
lines changed

17 files changed

+204
-223
lines changed

MIGRATION.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ sentryWebpackPlugin({
2323
});
2424
```
2525

26+
### Replacing `entries` option with `releaseInjectionTargets`
27+
28+
Previously, the `entries` option was used to filter for _entrypoints_ that the plugin should inject the release into.
29+
Releases were only injected into entrypoint files of a bundle.
30+
In version 2, releases are injected into every module that is part of a bundle.
31+
Don't worry, your bundler will only include the injected release code once.
32+
Instead of using the `entries` option to filter for _entrypoints_, the `releaseInjectionTargets` option can now be used to filter for _modules_ that the plugin should inject the release into.
33+
Matching behaviour stays the same.
34+
2635
### Injecting `SENTRY_RELEASES` Map
2736

2837
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`.

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

Lines changed: 31 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -28,52 +28,20 @@ import { Hub } from "@sentry/node";
2828
// This probably doesn't work for all bundlers but for rollup it does.
2929
const RELEASE_INJECTOR_ID = "\0sentry-release-injector";
3030

31+
const ALLOWED_TRANSFORMATION_FILE_ENDINGS = [".js", ".ts", ".jsx", ".tsx", ".cjs", ".mjs"];
32+
3133
/**
3234
* The sentry bundler plugin concerns itself with two things:
3335
* - Release injection
3436
* - Sourcemaps upload
3537
*
3638
* Release injection:
3739
*
38-
* Per default the sentry bundler plugin will inject a global `SENTRY_RELEASE` variable into the entrypoint of all bundles.
39-
* On a technical level this is done by appending an import (`import "sentry-release-injector;"`) to all entrypoint files
40-
* of the user code (see `transformInclude` and `transform` hooks). This import is then resolved by the sentry plugin
41-
* to a virtual module that sets the global variable (see `resolveId` and `load` hooks).
42-
*
43-
* The resulting output approximately looks like this:
44-
*
45-
* ```text
46-
* entrypoint1.js (user file)
47-
* ┌─────────────────────────┐ ┌─────────────────────────────────────────────────┐
48-
* │ │ │ import { myFunction } from "./my-library.js"; │
49-
* │ sentry-bundler-plugin │ │ │
50-
* │ │ │ const myResult = myFunction(); │
51-
* └---------│--------------- │ export { myResult }; │
52-
* │ │ │
53-
* │ injects │ // injected by sentry plugin │
54-
* ├───────────────────► import "sentry-release-injector"; ─────────────────────┐
55-
* │ └─────────────────────────────────────────────────┘ │
56-
* │ │
57-
* │ │
58-
* │ entrypoint2.js (user file) │
59-
* │ ┌─────────────────────────────────────────────────┐ │
60-
* │ │ export function myFunction() { │ │
61-
* │ │ return "Hello world!"; │ │
62-
* │ │ } │ │
63-
* │ │ │ │
64-
* │ injects │ // injected by sentry plugin │ │
65-
* └───────────────────► import "sentry-release-injector"; ─────────────────────┤
66-
* └─────────────────────────────────────────────────┘ │
67-
* │
68-
* │
69-
* sentry-release-injector │
70-
* ┌──────────────────────────────────┐ │
71-
* │ │ is resolved │
72-
* │ global.SENTRY_RELEASE = { ... } │ by plugin │
73-
* │ // + a little more logic │<─────────────────────┘
74-
* │ │ (only once)
75-
* └──────────────────────────────────┘
76-
* ```
40+
* Per default the sentry bundler plugin will inject a global `SENTRY_RELEASE` into each JavaScript/TypeScript module
41+
* that is part of the bundle. On a technical level this is done by appending an import (`import "sentry-release-injector;"`)
42+
* to all entrypoint files of the user code (see `transformInclude` and `transform` hooks). This import is then resolved
43+
* by the sentry plugin to a virtual module that sets the global variable (see `resolveId` and `load` hooks).
44+
* If a user wants to inject the release into a particular set of modules they can use the `releaseInjectionTargets` option.
7745
*
7846
* Source maps upload:
7947
*
@@ -128,12 +96,6 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
12896

12997
sentryHub.setUser({ id: internalOptions.org });
13098

131-
// This is `nonEntrypointSet` instead of `entrypointSet` because this set is filled in the `resolveId` hook and there
132-
// we don't have guaranteed access to *absolute* paths of files if they're entrypoints. For non-entrypoints we're
133-
// guaranteed to have absolute paths - we're then using the paths in later hooks to make decisions about whether a
134-
// file is an entrypoint or a non-entrypoint.
135-
const nonEntrypointSet = new Set<string>();
136-
13799
let transaction: Transaction | undefined;
138100
let releaseInjectionSpan: Span | undefined;
139101

@@ -192,10 +154,6 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
192154
level: "info",
193155
});
194156

195-
if (!isEntry) {
196-
nonEntrypointSet.add(id);
197-
}
198-
199157
if (id === RELEASE_INJECTOR_ID) {
200158
return RELEASE_INJECTOR_ID;
201159
} else {
@@ -235,7 +193,7 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
235193

236194
/**
237195
* This hook determines whether we want to transform a module. In the sentry bundler plugin we want to transform every entrypoint
238-
* unless configured otherwise with the `entries` option.
196+
* unless configured otherwise with the `releaseInjectionTargets` option.
239197
*
240198
* @param id Always the absolute (fully resolved) path to the module.
241199
* @returns `true` or `false` depending on whether we want to transform the module. For the sentry bundler plugin we only
@@ -247,28 +205,38 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
247205
level: "info",
248206
});
249207

250-
if (internalOptions.entries) {
251-
// If there's an `entries` option transform (ie. inject the release varible) when the file path matches the option.
252-
if (typeof internalOptions.entries === "function") {
253-
return internalOptions.entries(id);
208+
// We don't want to transform our injected code.
209+
if (id === RELEASE_INJECTOR_ID) {
210+
return false;
211+
}
212+
213+
if (internalOptions.releaseInjectionTargets) {
214+
// If there's an `releaseInjectionTargets` option transform (ie. inject the release varible) when the file path matches the option.
215+
if (typeof internalOptions.releaseInjectionTargets === "function") {
216+
return internalOptions.releaseInjectionTargets(id);
254217
}
255218

256-
return internalOptions.entries.some((entry) => {
219+
return internalOptions.releaseInjectionTargets.some((entry) => {
257220
if (entry instanceof RegExp) {
258221
return entry.test(id);
259222
} else {
260223
return id === entry;
261224
}
262225
});
263-
}
226+
} else {
227+
const pathIsOrdinary = !id.includes("?") && !id.includes("#");
264228

265-
// We want to transform (release injection) every module except for "sentry-release-injector".
266-
return id !== RELEASE_INJECTOR_ID && !nonEntrypointSet.has(id);
229+
const pathHasAllowedFileEnding = ALLOWED_TRANSFORMATION_FILE_ENDINGS.some(
230+
(allowedFileEnding) => id.endsWith(allowedFileEnding)
231+
);
232+
233+
return pathIsOrdinary && pathHasAllowedFileEnding;
234+
}
267235
},
268236

269237
/**
270238
* This hook is responsible for injecting the "sentry release injector" imoprt statement into each entrypoint unless
271-
* configured otherwise with the `entries` option (logic for that is in the `transformInclude` hook).
239+
* configured otherwise with the `releaseInjectionTargets` option (logic for that is in the `transformInclude` hook).
272240
*
273241
* @param code Code of the file to transform.
274242
* @param id Always the absolute (fully resolved) path to the module.
@@ -281,11 +249,11 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
281249
});
282250

283251
// The MagicString library allows us to generate sourcemaps for the changes we make to the user code.
284-
const ms: MagicString = new MagicString(code); // Very stupid author's note: For some absurd reason, when we add a JSDoc to this hook, the TS language server starts complaining about `ms` and adding a type annotation helped so that's why it's here. (┛ಠ_ಠ)┛彡┻━┻
252+
const ms = new MagicString(code);
285253

286-
// appending instead of prepending has less probability of mucking with user'sadly
287-
// source maps and import statements get to the top anyways
288-
ms.append(`import "${RELEASE_INJECTOR_ID}";`);
254+
// Appending instead of prepending has less probability of mucking with user's source maps.
255+
// Luckily import statements get hoisted to the top anyways.
256+
ms.append(`;\nimport "${RELEASE_INJECTOR_ID}";`);
289257

290258
if (unpluginMetaContext.framework === "esbuild") {
291259
// esbuild + unplugin is buggy at the moment when we return an object with a `map` (sourcemap) property.

packages/bundler-plugin-core/src/options-mapping.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type OptionalInternalOptions = Partial<
3434
>;
3535

3636
type NormalizedInternalOptions = {
37-
entries: (string | RegExp)[] | ((filePath: string) => boolean) | undefined;
37+
releaseInjectionTargets: (string | RegExp)[] | ((filePath: string) => boolean) | undefined;
3838
include: InternalIncludeEntry[];
3939
};
4040

@@ -108,7 +108,7 @@ export function normalizeUserOptions(userOptions: UserOptions): InternalOptions
108108
// Optional options
109109
setCommits: userOptions.setCommits,
110110
deploy: userOptions.deploy,
111-
entries: normalizeEntries(userOptions.entries),
111+
releaseInjectionTargets: normalizeReleaseInjectionTargets(userOptions.releaseInjectionTargets),
112112
dist: userOptions.dist,
113113
errorHandler: userOptions.errorHandler,
114114
configFile: userOptions.configFile,
@@ -124,17 +124,18 @@ export function normalizeUserOptions(userOptions: UserOptions): InternalOptions
124124
}
125125

126126
/**
127-
* Converts the user-facing `entries` option to the internal `entries` option
127+
* Converts the user-facing `releaseInjectionTargets` option to the internal
128+
* `releaseInjectionTargets` option
128129
*/
129-
function normalizeEntries(
130-
userEntries: UserOptions["entries"]
130+
function normalizeReleaseInjectionTargets(
131+
userReleaseInjectionTargets: UserOptions["releaseInjectionTargets"]
131132
): (string | RegExp)[] | ((filePath: string) => boolean) | undefined {
132-
if (userEntries === undefined) {
133+
if (userReleaseInjectionTargets === undefined) {
133134
return undefined;
134-
} else if (typeof userEntries === "function") {
135-
return userEntries;
135+
} else if (typeof userReleaseInjectionTargets === "function") {
136+
return userReleaseInjectionTargets;
136137
} else {
137-
return arrayify(userEntries);
138+
return arrayify(userReleaseInjectionTargets);
138139
}
139140
}
140141

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,19 @@ export type Options = Omit<IncludeEntry, "paths"> & {
6363
dist?: string;
6464

6565
/**
66-
* Filter for bundle entry points that should contain the provided release.
66+
* Filter for modules that the release should be injected in.
6767
*
68-
* This option takes a string, a regular expression, or an array containing strings, regular expressions, or both.
69-
* It's also possible to provide a filter function that takes the absolute path of a processed entrypoint and should
70-
* return `true` if the release should be injected into the entrypoint and `false` otherwise. String values of this
71-
* option require a full match with the absolute path of the bundle.
68+
* This option takes a string, a regular expression, or an array containing strings,
69+
* regular expressions, or both. It's also possible to provide a filter function
70+
* that takes the absolute path of a processed module. It should return `true`
71+
* if the release should be injected into the module and `false` otherwise. String
72+
* values of this option require a full match with the absolute path of the module.
7273
*
73-
* By default, the release will be injected into all entry points.
74+
* By default, the release will be injected into all modules - however, bundlers
75+
* will include the injected release code only once per entrypoint.
76+
* If release injection should be disabled, provide an empty array here.
7477
*/
75-
entries?: (string | RegExp)[] | RegExp | string | ((filePath: string) => boolean);
78+
releaseInjectionTargets?: (string | RegExp)[] | RegExp | string | ((filePath: string) => boolean);
7679

7780
/**
7881
* Determines if the Sentry release record should be automatically finalized

0 commit comments

Comments
 (0)