@@ -115,6 +115,88 @@ function esbuildDebugIdInjectionPlugin(): UnpluginOptions {
115115 } ;
116116}
117117
118+ function esbuildModuleMetadataInjectionPlugin ( injectionCode : string ) : UnpluginOptions {
119+ const pluginName = "sentry-esbuild-module-metadata-injection-plugin" ;
120+ const stubNamespace = "sentry-module-metadata-stub" ;
121+
122+ return {
123+ name : pluginName ,
124+
125+ esbuild : {
126+ setup ( { onLoad, onResolve } ) {
127+ onResolve ( { filter : / .* / } , ( args ) => {
128+ if ( args . kind !== "entry-point" ) {
129+ return ;
130+ } else {
131+ return {
132+ pluginName,
133+ // needs to be an abs path, otherwise esbuild will complain
134+ path : path . isAbsolute ( args . path ) ? args . path : path . join ( args . resolveDir , args . path ) ,
135+ pluginData : {
136+ isMetadataProxyResolver : true ,
137+ originalPath : args . path ,
138+ originalResolveDir : args . resolveDir ,
139+ } ,
140+ // We need to add a suffix here, otherwise esbuild will mark the entrypoint as resolved and won't traverse
141+ // the module tree any further down past the proxy module because we're essentially creating a dependency
142+ // loop back to the proxy module.
143+ // By setting a suffix we're telling esbuild that the entrypoint and proxy module are two different things,
144+ // making it re-resolve the entrypoint when it is imported from the proxy module.
145+ // Super confusing? Yes. Works? Apparently... Let's see.
146+ suffix : "?sentryMetadataProxyModule=true" ,
147+ } ;
148+ }
149+ } ) ;
150+
151+ onLoad ( { filter : / .* / } , ( args ) => {
152+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
153+ if ( ! ( args . pluginData ?. isMetadataProxyResolver as undefined | boolean ) ) {
154+ return null ;
155+ }
156+
157+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
158+ const originalPath = args . pluginData . originalPath as string ;
159+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
160+ const originalResolveDir = args . pluginData . originalResolveDir as string ;
161+
162+ return {
163+ loader : "js" ,
164+ pluginName,
165+ // We need to use JSON.stringify below so that any escape backslashes stay escape backslashes, in order not to break paths on windows
166+ contents : `
167+ import "_sentry-module-metadata-injection-stub";
168+ import * as OriginalModule from ${ JSON . stringify ( originalPath ) } ;
169+ export default OriginalModule.default;
170+ export * from ${ JSON . stringify ( originalPath ) } ;` ,
171+ resolveDir : originalResolveDir ,
172+ } ;
173+ } ) ;
174+
175+ onResolve ( { filter : / _ s e n t r y - m o d u l e - m e t a d a t a - i n j e c t i o n - s t u b / } , ( args ) => {
176+ return {
177+ path : args . path ,
178+ sideEffects : true ,
179+ pluginName,
180+ namespace : stubNamespace ,
181+ suffix : "?sentry-module-id=" + uuidv4 ( ) , // create different module, each time this is resolved
182+ } ;
183+ } ) ;
184+
185+ onLoad (
186+ { filter : / _ s e n t r y - m o d u l e - m e t a d a t a - i n j e c t i o n - s t u b / , namespace : stubNamespace } ,
187+ ( ) => {
188+ return {
189+ loader : "js" ,
190+ pluginName,
191+ contents : injectionCode ,
192+ } ;
193+ }
194+ ) ;
195+ } ,
196+ } ,
197+ } ;
198+ }
199+
118200function esbuildDebugIdUploadPlugin (
119201 upload : ( buildArtifacts : string [ ] ) => Promise < void >
120202) : UnpluginOptions {
@@ -135,6 +217,7 @@ function esbuildDebugIdUploadPlugin(
135217const sentryUnplugin = sentryUnpluginFactory ( {
136218 releaseInjectionPlugin : esbuildReleaseInjectionPlugin ,
137219 debugIdInjectionPlugin : esbuildDebugIdInjectionPlugin ,
220+ moduleMetadataInjectionPlugin : esbuildModuleMetadataInjectionPlugin ,
138221 debugIdUploadPlugin : esbuildDebugIdUploadPlugin ,
139222} ) ;
140223
0 commit comments