@@ -28,17 +28,20 @@ import { getDependencies, getPackageJson, parseMajorVersion } from "./utils";
2828
2929const ALLOWED_TRANSFORMATION_FILE_ENDINGS = [ ".js" , ".ts" , ".jsx" , ".tsx" , ".mjs" ] ;
3030
31+ const releaseInjectionFilePath = require . resolve (
32+ "@sentry/bundler-plugin-core/sentry-release-injection-file"
33+ ) ;
3134/**
3235 * The sentry bundler plugin concerns itself with two things:
3336 * - Release injection
3437 * - Sourcemaps upload
3538 *
3639 * Release injection:
37- * Per default the sentry bundler plugin will inject a global `SENTRY_RELEASE` into
38- * each JavaScript/TypeScript entrypoint . On a technical level this is done by identifying
39- * entrypoints in the `resolveId` hook and prepending user code in the `transform` hook.
40- * If a user wants to inject the release into a particular set of modules instead,
41- * they can use the `releaseInjectionTargets` option.
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.
4245 *
4346 * Source maps upload:
4447 *
@@ -102,8 +105,6 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
102105 let transaction : Transaction | undefined ;
103106 let releaseInjectionSpan : Span | undefined ;
104107
105- const absolueEntrypointPaths = new Set < string > ( ) ;
106-
107108 return {
108109 name : "sentry-plugin" ,
109110 enforce : "pre" , // needed for Vite to call resolveId hook
@@ -164,11 +165,6 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
164165 */
165166 resolveId ( id , importer , { isEntry } ) {
166167 logger . debug ( 'Called "resolveId":' , { id, importer, isEntry } ) ;
167-
168- if ( isEntry ) {
169- absolueEntrypointPaths . add ( path . resolve ( path . normalize ( id ) ) ) ;
170- }
171-
172168 return undefined ;
173169 } ,
174170
@@ -187,6 +183,10 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
187183 // a windows style path to `releaseInjectionTargets`
188184 const normalizedId = path . normalize ( id ) ;
189185
186+ if ( id . includes ( "sentry-release-injection-file" ) ) {
187+ return true ;
188+ }
189+
190190 if ( internalOptions . releaseInjectionTargets ) {
191191 // If there's an `releaseInjectionTargets` option transform (ie. inject the release varible) when the file path matches the option.
192192 if ( typeof internalOptions . releaseInjectionTargets === "function" ) {
@@ -201,16 +201,14 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
201201 return normalizedId === normalizedEntry ;
202202 }
203203 } ) ;
204- } else if ( absolueEntrypointPaths . has ( normalizedId ) ) {
204+ } else {
205205 const pathIsOrdinary = ! normalizedId . includes ( "?" ) && ! normalizedId . includes ( "#" ) ;
206206
207207 const pathHasAllowedFileEnding = ALLOWED_TRANSFORMATION_FILE_ENDINGS . some (
208208 ( allowedFileEnding ) => normalizedId . endsWith ( allowedFileEnding )
209209 ) ;
210210
211211 return pathIsOrdinary && pathHasAllowedFileEnding ;
212- } else {
213- return false ;
214212 }
215213 } ,
216214
@@ -232,15 +230,23 @@ const unplugin = createUnplugin<Options>((options, unpluginMetaContext) => {
232230 // The MagicString library allows us to generate sourcemaps for the changes we make to the user code.
233231 const ms = new MagicString ( code ) ;
234232
235- ms . prepend (
236- generateGlobalInjectorCode ( {
237- release : await releaseNamePromise ,
238- injectReleasesMap : internalOptions . injectReleasesMap ,
239- injectBuildInformation : internalOptions . _experiments . injectBuildInformation || false ,
240- org : internalOptions . org ,
241- project : internalOptions . project ,
242- } )
243- ) ;
233+ if ( code . includes ( "_sentry_release_injection_file" ) ) {
234+ // Appending instead of prepending has less probability of mucking with user's source maps.
235+ ms . append (
236+ generateGlobalInjectorCode ( {
237+ release : await releaseNamePromise ,
238+ injectReleasesMap : internalOptions . injectReleasesMap ,
239+ injectBuildInformation : internalOptions . _experiments . injectBuildInformation || false ,
240+ org : internalOptions . org ,
241+ project : internalOptions . project ,
242+ } )
243+ ) ;
244+ } else {
245+ // Appending instead of prepending has less probability of mucking with user's source maps.
246+ // Luckily import statements get hoisted to the top anyways.
247+ // The import needs to be an absolute path because Rollup doesn't bundle stuff in `node_modules` by default when bundling CJS (unless the import path is absolute or the node-resolve-plugin is used).
248+ ms . append ( `;\nimport "${ releaseInjectionFilePath . replace ( / \\ / g, "\\\\" ) } ";` ) ;
249+ }
244250
245251 if ( unpluginMetaContext . framework === "esbuild" ) {
246252 // esbuild + unplugin is buggy at the moment when we return an object with a `map` (sourcemap) property.
0 commit comments