@@ -42,6 +42,14 @@ function esbuildReleaseInjectionPlugin(injectionCode: string): UnpluginOptions {
4242 } ;
4343}
4444
45+ // Shared set to track entry points that have been wrapped by the metadata plugin.
46+ // This allows the debug ID plugin to know when an import is coming from a metadata proxy.
47+ const metadataProxyEntryPoints = new Set < string > ( ) ;
48+
49+ // Set to track which paths have already been wrapped with debug ID injection.
50+ // This prevents the debug ID plugin from wrapping the same module multiple times.
51+ const debugIdWrappedPaths = new Set < string > ( ) ;
52+
4553function esbuildDebugIdInjectionPlugin ( logger : Logger ) : UnpluginOptions {
4654 const pluginName = "sentry-esbuild-debug-id-injection-plugin" ;
4755 const stubNamespace = "sentry-debug-id-stub" ;
@@ -58,33 +66,60 @@ function esbuildDebugIdInjectionPlugin(logger: Logger): UnpluginOptions {
5866 }
5967
6068 onResolve ( { filter : / .* / } , ( args ) => {
61- if ( args . kind !== "entry-point" ) {
69+ // We want to inject debug IDs into entry points. However, when the module metadata
70+ // injection plugin is also active, it intercepts entry points first and creates a
71+ // proxy module that re-imports the original entry point. In that case, we need to
72+ // also intercept those imports (which have kind === "import-statement") and inject
73+ // debug IDs there too.
74+ const isEntryPoint = args . kind === "entry-point" ;
75+
76+ // Check if this import is coming from a metadata proxy module.
77+ // The metadata plugin registers entry points it wraps in the shared Set.
78+ const isImportFromMetadataProxy =
79+ args . kind === "import-statement" &&
80+ args . importer !== undefined &&
81+ metadataProxyEntryPoints . has ( args . importer ) ;
82+
83+ if ( ! isEntryPoint && ! isImportFromMetadataProxy ) {
6284 return ;
63- } else {
64- // Injected modules via the esbuild `inject` option do also have `kind == "entry-point"`.
65- // We do not want to inject debug IDs into those files because they are already bundled into the entrypoints
66- if ( initialOptions . inject ?. includes ( args . path ) ) {
67- return ;
68- }
85+ }
6986
70- return {
71- pluginName,
72- // needs to be an abs path, otherwise esbuild will complain
73- path : path . isAbsolute ( args . path ) ? args . path : path . join ( args . resolveDir , args . path ) ,
74- pluginData : {
75- isProxyResolver : true ,
76- originalPath : args . path ,
77- originalResolveDir : args . resolveDir ,
78- } ,
79- // We need to add a suffix here, otherwise esbuild will mark the entrypoint as resolved and won't traverse
80- // the module tree any further down past the proxy module because we're essentially creating a dependency
81- // loop back to the proxy module.
82- // By setting a suffix we're telling esbuild that the entrypoint and proxy module are two different things,
83- // making it re-resolve the entrypoint when it is imported from the proxy module.
84- // Super confusing? Yes. Works? Apparently... Let's see.
85- suffix : "?sentryProxyModule=true" ,
86- } ;
87+ // Injected modules via the esbuild `inject` option do also have `kind == "entry-point"`.
88+ // We do not want to inject debug IDs into those files because they are already bundled into the entrypoints
89+ if ( initialOptions . inject ?. includes ( args . path ) ) {
90+ return ;
8791 }
92+
93+ const resolvedPath = path . isAbsolute ( args . path )
94+ ? args . path
95+ : path . join ( args . resolveDir , args . path ) ;
96+
97+ // Check if we've already wrapped this path. This can happen when both the metadata
98+ // plugin and debug ID plugin are active - the debug ID proxy imports the original
99+ // module, and since its importer path matches the metadata proxy entry point,
100+ // we might try to wrap it again. Prevent this by tracking wrapped paths.
101+ if ( debugIdWrappedPaths . has ( resolvedPath ) ) {
102+ return ;
103+ }
104+ debugIdWrappedPaths . add ( resolvedPath ) ;
105+
106+ return {
107+ pluginName,
108+ // needs to be an abs path, otherwise esbuild will complain
109+ path : resolvedPath ,
110+ pluginData : {
111+ isProxyResolver : true ,
112+ originalPath : args . path ,
113+ originalResolveDir : args . resolveDir ,
114+ } ,
115+ // We need to add a suffix here, otherwise esbuild will mark the entrypoint as resolved and won't traverse
116+ // the module tree any further down past the proxy module because we're essentially creating a dependency
117+ // loop back to the proxy module.
118+ // By setting a suffix we're telling esbuild that the entrypoint and proxy module are two different things,
119+ // making it re-resolve the entrypoint when it is imported from the proxy module.
120+ // Super confusing? Yes. Works? Apparently... Let's see.
121+ suffix : "?sentryProxyModule=true" ,
122+ } ;
88123 } ) ;
89124
90125 onLoad ( { filter : / .* / } , ( args ) => {
@@ -152,10 +187,19 @@ function esbuildModuleMetadataInjectionPlugin(injectionCode: string): UnpluginOp
152187 return ;
153188 }
154189
190+ const resolvedPath = path . isAbsolute ( args . path )
191+ ? args . path
192+ : path . join ( args . resolveDir , args . path ) ;
193+
194+ // Register this entry point so the debug ID plugin knows to wrap imports from
195+ // this proxy module. This is needed because the debug ID plugin runs after us and
196+ // needs to know when an import is coming from our proxy module.
197+ metadataProxyEntryPoints . add ( resolvedPath ) ;
198+
155199 return {
156200 pluginName,
157201 // needs to be an abs path, otherwise esbuild will complain
158- path : path . isAbsolute ( args . path ) ? args . path : path . join ( args . resolveDir , args . path ) ,
202+ path : resolvedPath ,
159203 pluginData : {
160204 isMetadataProxyResolver : true ,
161205 originalPath : args . path ,
0 commit comments