Skip to content

Commit 70c696b

Browse files
queeliusclaude
andcommitted
fix(esbuild): Avoid "Import 'default' will always be undefined" warning
When using @sentry/esbuild-plugin with an entry point that has no default export (common for Express.js apps), the proxy module generates `export default OriginalModule.default` which triggers esbuild's "import-is-undefined" warning. Fix by wrapping the default export access in a function call, which prevents esbuild's static analysis from detecting the potentially missing default while still correctly forwarding it when present. Fixes #747 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d58faea commit 70c696b

File tree

2 files changed

+158
-2
lines changed

2 files changed

+158
-2
lines changed

packages/esbuild-plugin/src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,15 @@ export function sentryEsbuildPlugin(userOptions: Options = {}): any {
253253
return {
254254
loader: "js",
255255
pluginName,
256+
// We wrap the default export access in a function call to prevent esbuild's
257+
// static analysis from generating an "Import 'default' will always be undefined"
258+
// warning when the original module has no default export. The function wrapper
259+
// is transparent and correctly forwards the default export when it exists.
256260
contents: `
257261
import "_sentry-debug-id-injection-stub";
258-
import * as OriginalModule from ${JSON.stringify(originalPath)};
259-
export default OriginalModule.default;
262+
import * as _sentry_original_module from ${JSON.stringify(originalPath)};
263+
var _sentry_default_export = (function(m) { return m.default; })(_sentry_original_module);
264+
export { _sentry_default_export as default };
260265
export * from ${JSON.stringify(originalPath)};`,
261266
resolveDir: originalResolveDir,
262267
};
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { sentryEsbuildPlugin } from "../src";
2+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
3+
import * as esbuild from "esbuild";
4+
import * as path from "path";
5+
import * as fs from "fs";
6+
import * as os from "os";
7+
8+
describe("esbuild proxy module default export handling", () => {
9+
let tmpDir: string;
10+
11+
beforeAll(() => {
12+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sentry-esbuild-test-"));
13+
});
14+
15+
afterAll(() => {
16+
fs.rmSync(tmpDir, { recursive: true, force: true });
17+
});
18+
19+
it("should build successfully without warnings when entry point has no default export", async () => {
20+
const inputFile = path.join(tmpDir, "no-default.ts");
21+
fs.writeFileSync(
22+
inputFile,
23+
[
24+
'import * as path from "path";',
25+
'console.log("No default export here", path.sep);',
26+
"",
27+
].join("\n")
28+
);
29+
30+
const outDir = path.join(tmpDir, "out-no-default");
31+
const result = await esbuild.build({
32+
entryPoints: [inputFile],
33+
sourcemap: true,
34+
bundle: true,
35+
outdir: outDir,
36+
platform: "node",
37+
plugins: [
38+
sentryEsbuildPlugin({
39+
telemetry: false,
40+
release: { name: "test-release", create: false },
41+
}),
42+
],
43+
write: true,
44+
});
45+
46+
// If esbuild version is new enough, warnings about "Import 'default' will always be
47+
// undefined" will be captured in result.warnings. On older esbuild versions this array
48+
// may be empty regardless, so the test at least verifies no build errors occur.
49+
const importUndefinedWarnings = (result.warnings || []).filter(
50+
(w) =>
51+
w.text.includes("Import") &&
52+
w.text.includes("default") &&
53+
w.text.includes("undefined")
54+
);
55+
56+
expect(importUndefinedWarnings).toHaveLength(0);
57+
});
58+
59+
it("should preserve default export when the entry point has one", async () => {
60+
const inputFile = path.join(tmpDir, "with-default.ts");
61+
fs.writeFileSync(
62+
inputFile,
63+
[
64+
"export const foo = 42;",
65+
'export default function main() { return "hello"; }',
66+
"",
67+
].join("\n")
68+
);
69+
70+
const outDir = path.join(tmpDir, "out-with-default");
71+
const result = await esbuild.build({
72+
entryPoints: [inputFile],
73+
sourcemap: true,
74+
bundle: true,
75+
outdir: outDir,
76+
platform: "node",
77+
format: "esm",
78+
plugins: [
79+
sentryEsbuildPlugin({
80+
telemetry: false,
81+
release: { name: "test-release", create: false },
82+
}),
83+
],
84+
write: true,
85+
});
86+
87+
// Should produce no warnings
88+
const importUndefinedWarnings = (result.warnings || []).filter(
89+
(w) =>
90+
w.text.includes("Import") &&
91+
w.text.includes("default") &&
92+
w.text.includes("undefined")
93+
);
94+
expect(importUndefinedWarnings).toHaveLength(0);
95+
96+
// Verify the output contains the default export function
97+
const outputFiles = fs.readdirSync(outDir).filter((f) => f.endsWith(".js"));
98+
expect(outputFiles.length).toBeGreaterThan(0);
99+
100+
const outputContent = fs.readFileSync(
101+
path.join(outDir, outputFiles[0] as string),
102+
"utf-8"
103+
);
104+
expect(outputContent).toContain("main");
105+
});
106+
107+
it("should preserve named exports when the entry point has no default export", async () => {
108+
const inputFile = path.join(tmpDir, "named-only.ts");
109+
fs.writeFileSync(
110+
inputFile,
111+
['export const foo = 42;', 'export const bar = "hello";', ""].join("\n")
112+
);
113+
114+
const outDir = path.join(tmpDir, "out-named-only");
115+
const result = await esbuild.build({
116+
entryPoints: [inputFile],
117+
sourcemap: true,
118+
bundle: true,
119+
outdir: outDir,
120+
platform: "node",
121+
format: "esm",
122+
plugins: [
123+
sentryEsbuildPlugin({
124+
telemetry: false,
125+
release: { name: "test-release", create: false },
126+
}),
127+
],
128+
write: true,
129+
});
130+
131+
// Should produce no warnings
132+
const importUndefinedWarnings = (result.warnings || []).filter(
133+
(w) =>
134+
w.text.includes("Import") &&
135+
w.text.includes("default") &&
136+
w.text.includes("undefined")
137+
);
138+
expect(importUndefinedWarnings).toHaveLength(0);
139+
140+
// Verify the output contains the named exports
141+
const outputFiles = fs.readdirSync(outDir).filter((f) => f.endsWith(".js"));
142+
expect(outputFiles.length).toBeGreaterThan(0);
143+
144+
const outputContent = fs.readFileSync(
145+
path.join(outDir, outputFiles[0] as string),
146+
"utf-8"
147+
);
148+
expect(outputContent).toContain("foo");
149+
expect(outputContent).toContain("bar");
150+
});
151+
});

0 commit comments

Comments
 (0)