Compare commits

..

1 Commits

Author SHA1 Message Date
Claude Bot
106ef125c0 fix(resolver): allow long data URLs in module resolution
Data URLs can be very long (e.g. zip.js embeds ~25KB worker code in data URIs).
The path length check was rejecting these before they could be recognized as
data URLs. This fix excludes data URLs from the path length check.

Fixes #24161

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 22:10:57 +00:00
5 changed files with 88 additions and 181 deletions

View File

@@ -1743,7 +1743,9 @@ pub fn resolveMaybeNeedsTrailingSlash(
comptime is_a_file_path: bool,
is_user_require_resolve: bool,
) bun.JSError!void {
if (is_a_file_path and specifier.length() > comptime @as(u32, @intFromFloat(@trunc(@as(f64, @floatFromInt(bun.MAX_PATH_BYTES)) * 1.5)))) {
// Data URLs can be very long (e.g. zip.js embeds ~25KB worker code in data URIs),
// so don't apply the path length check to them.
if (is_a_file_path and (specifier.length() > comptime @as(u32, @intFromFloat(@trunc(@as(f64, @floatFromInt(bun.MAX_PATH_BYTES)) * 1.5)))) and !specifier.hasPrefixComptime("data:")) {
const specifier_utf8 = specifier.toUTF8(bun.default_allocator);
defer specifier_utf8.deinit();
const source_utf8 = source.toUTF8(bun.default_allocator);

View File

@@ -1988,10 +1988,6 @@ pub const BundleV2 = struct {
if (transpiler.options.compile) {
// Emitting DCE annotations is nonsensical in --compile.
transpiler.options.emit_dce_annotations = false;
// Default to production mode for --compile builds to enable dead code elimination
// for conditional requires like React's process.env.NODE_ENV checks.
// Users can override this with define: { 'process.env.NODE_ENV': '"development"' }
try transpiler.env.map.put("NODE_ENV", "production");
}
transpiler.configureLinker();
@@ -2000,12 +1996,6 @@ pub const BundleV2 = struct {
if (!transpiler.options.production) {
try transpiler.options.conditions.appendSlice(&.{"development"});
}
// Allow tsconfig.json overriding, but always set it to false for --compile builds.
if (transpiler.options.compile) {
transpiler.options.jsx.development = false;
}
transpiler.resolver.env_loader = transpiler.env;
transpiler.resolver.opts = transpiler.options;
}

View File

@@ -196,10 +196,7 @@ pub const BuildCommand = struct {
this_transpiler.options.env.behavior = ctx.bundler_options.env_behavior;
this_transpiler.options.env.prefix = ctx.bundler_options.env_prefix;
// Default to production mode for --compile builds to enable dead code elimination
// for conditional requires like React's process.env.NODE_ENV checks.
// Users can override this with --define 'process.env.NODE_ENV="development"'
if (ctx.bundler_options.production or ctx.bundler_options.compile) {
if (ctx.bundler_options.production) {
try this_transpiler.env.map.put("NODE_ENV", "production");
}
@@ -213,8 +210,8 @@ pub const BuildCommand = struct {
this_transpiler.resolver.opts = this_transpiler.options;
this_transpiler.resolver.env_loader = this_transpiler.env;
// Allow tsconfig.json overriding, but always set it to false if --production or --compile is passed.
if (ctx.bundler_options.production or ctx.bundler_options.compile) {
// Allow tsconfig.json overriding, but always set it to false if --production is passed.
if (ctx.bundler_options.production) {
this_transpiler.options.jsx.development = false;
this_transpiler.resolver.opts.jsx.development = false;
}

View File

@@ -0,0 +1,82 @@
import { expect, test } from "bun:test";
// Test for issue #24161: Long data URLs in Web Workers should work
// zip.js embeds ~25KB worker code in data URIs, which was being rejected
// by the path length limit check before being recognized as data URLs.
test("Worker from a long data URL (>6KB)", async () => {
// Create a data URL that's longer than the MAX_PATH_BYTES * 1.5 (~6KB) limit
// by padding the worker code with a large comment
const padding = "a".repeat(10000); // 10KB of padding
const workerCode = `
/* ${padding} */
self.onmessage = e => {
self.postMessage(e.data + " processed");
};
`;
const dataUrl = `data:text/javascript,${encodeURIComponent(workerCode)}`;
// Verify our data URL is actually longer than the threshold
expect(dataUrl.length).toBeGreaterThan(6144);
const worker = new Worker(dataUrl);
const result = await new Promise((resolve, reject) => {
worker.onerror = e => reject(e.message);
worker.onmessage = e => {
worker.terminate();
resolve(e.data);
};
worker.postMessage("test");
});
expect(result).toBe("test processed");
});
test("Worker from a long base64 data URL", async () => {
// Create worker code and encode as base64
const padding = "a".repeat(10000);
const workerCode = `
/* ${padding} */
self.onmessage = e => {
self.postMessage(e.data + " base64");
};
`;
const base64Code = Buffer.from(workerCode).toString("base64");
const dataUrl = `data:text/javascript;base64,${base64Code}`;
// Verify our data URL is actually longer than the threshold
expect(dataUrl.length).toBeGreaterThan(6144);
const worker = new Worker(dataUrl);
const result = await new Promise((resolve, reject) => {
worker.onerror = e => reject(e.message);
worker.onmessage = e => {
worker.terminate();
resolve(e.data);
};
worker.postMessage("test");
});
expect(result).toBe("test base64");
});
test("Dynamic import of long data URL", async () => {
// Test that dynamic import of long data URLs also works
const padding = "a".repeat(10000);
const moduleCode = `
/* ${padding} */
export const value = "imported successfully";
`;
const dataUrl = `data:text/javascript,${encodeURIComponent(moduleCode)}`;
// Verify our data URL is actually longer than the threshold
expect(dataUrl.length).toBeGreaterThan(6144);
const module = await import(dataUrl);
expect(module.value).toBe("imported successfully");
});

View File

@@ -1,164 +0,0 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
import { join } from "path";
// https://github.com/oven-sh/bun/issues/26244
// bun build --compile should default NODE_ENV to production for dead code elimination
describe("Issue #26244", () => {
test("--compile defaults NODE_ENV to production (CLI)", async () => {
using dir = tempDir("compile-node-env-cli", {
// This simulates React's conditional require pattern
"index.js": `
if (process.env.NODE_ENV === 'production') {
module.exports = require('./prod.js');
} else {
module.exports = require('./dev.js');
}
`,
"prod.js": `module.exports = { mode: "production" };`,
// Note: dev.js intentionally not created to simulate Next.js standalone output
// where development files are stripped
});
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
// This should succeed because NODE_ENV defaults to production,
// enabling dead code elimination of the dev.js branch
const buildProc = Bun.spawn({
cmd: [bunExe(), "build", "--compile", join(dir + "", "index.js"), "--outfile", outfile],
cwd: dir + "",
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
new Response(buildProc.stdout).text(),
new Response(buildProc.stderr).text(),
buildProc.exited,
]);
// Build should succeed - the dead branch with dev.js should be eliminated
expect(buildStderr).not.toContain("Could not resolve");
expect(buildExitCode).toBe(0);
});
test("--compile defaults NODE_ENV to production (API)", async () => {
using dir = tempDir("compile-node-env-api", {
// This simulates React's conditional require pattern
"index.js": `
if (process.env.NODE_ENV === 'production') {
module.exports = require('./prod.js');
} else {
module.exports = require('./dev.js');
}
`,
"prod.js": `module.exports = { mode: "production" };`,
// Note: dev.js intentionally not created to simulate Next.js standalone output
// where development files are stripped
});
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
// This should succeed because NODE_ENV defaults to production,
// enabling dead code elimination of the dev.js branch
const result = await Bun.build({
entrypoints: [join(dir + "", "index.js")],
compile: {
outfile,
},
});
// Build should succeed - the dead branch with dev.js should be eliminated
expect(result.success).toBe(true);
expect(result.outputs.length).toBe(1);
});
test("--compile with conditional require eliminates dead branch (CLI)", async () => {
using dir = tempDir("compile-dead-code-cli", {
"entry.js": `
// This is the pattern used by React
if (process.env.NODE_ENV === 'production') {
console.log("Using production build");
} else {
// This branch references a non-existent file
// and should be eliminated by dead code elimination
require('./non-existent-dev-file.js');
}
`,
});
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
// Should succeed - the require('./non-existent-dev-file.js') should be
// eliminated because NODE_ENV defaults to 'production'
const buildProc = Bun.spawn({
cmd: [bunExe(), "build", "--compile", join(dir + "", "entry.js"), "--outfile", outfile],
cwd: dir + "",
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
new Response(buildProc.stdout).text(),
new Response(buildProc.stderr).text(),
buildProc.exited,
]);
expect(buildStderr).not.toContain("Could not resolve");
expect(buildExitCode).toBe(0);
});
test("--compile can override NODE_ENV with --define", async () => {
using dir = tempDir("compile-define-override", {
"entry.js": `console.log(process.env.NODE_ENV);`,
});
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
// Use CLI to test --define override
const buildProc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(dir + "", "entry.js"),
"--outfile",
outfile,
"--define",
'process.env.NODE_ENV="development"',
],
cwd: dir + "",
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
new Response(buildProc.stdout).text(),
new Response(buildProc.stderr).text(),
buildProc.exited,
]);
expect(buildExitCode).toBe(0);
// Run the compiled binary
const runProc = Bun.spawn({
cmd: [outfile],
cwd: dir + "",
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(runProc.stdout).text(),
new Response(runProc.stderr).text(),
runProc.exited,
]);
expect(stdout.trim()).toBe("development");
expect(exitCode).toBe(0);
});
});