Skip to content

Commit ea9bd01

Browse files
committed
fix(esbuild): ensure debug IDs are injected alongside moduleMetadata
1 parent 77f1afa commit ea9bd01

File tree

1 file changed

+69
-25
lines changed

1 file changed

+69
-25
lines changed

packages/esbuild-plugin/src/index.ts

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
4553
function 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

Comments
 (0)