Compare commits

..

1 Commits

Author SHA1 Message Date
Claude Bot
5315ff6cee Fix Bun.serve to respect development flag for sourcemap generation
When development: false is set in Bun.serve, sourcemaps should not be
generated or included in the bundled output. Previously, sourcemaps were
always generated with config.source_map = .linked regardless of the
development mode.

This change makes sourcemap generation conditional:
- development: false -> config.source_map = .none
- development: true -> config.source_map = .linked

Added comprehensive tests to verify:
- Sourcemaps are not included when development: false
- Sourcemaps are included when development: true
- Sourcemaps are included when development: { hmr: false }

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 22:04:40 +00:00
7 changed files with 337 additions and 63 deletions

View File

@@ -186,14 +186,12 @@ pub const HTMLRewriter = struct {
return out;
}
const ResponseKind = enum { string, array_buffer, blob, other };
const ResponseKind = enum { string, array_buffer, other };
const kind: ResponseKind = brk: {
if (response_value.isString())
break :brk .string
else if (response_value.jsType().isTypedArrayOrArrayBuffer())
break :brk .array_buffer
else if (response_value.as(jsc.WebCore.Blob)) |_|
break :brk .blob
else
break :brk .other;
};
@@ -216,12 +214,6 @@ pub const HTMLRewriter = struct {
return global.throwValue(err);
}
out_response_value.ensureStillAlive();
// For blob input, return the Response directly
if (kind == .blob) {
return out_response_value;
}
var out_response = out_response_value.as(Response) orelse return out_response_value;
var blob = out_response.getBodyValue().useAsAnyBlobAllowNonUTF8String();
@@ -238,7 +230,6 @@ pub const HTMLRewriter = struct {
.array_buffer => brk: {
break :brk blob.toArrayBuffer(global, .transfer);
},
.blob => unreachable, // handled above
.other => unreachable,
};
}

View File

@@ -282,11 +282,12 @@ pub const Route = struct {
if (!is_development) {
bun.handleOom(config.define.put("process.env.NODE_ENV", "\"production\""));
config.jsx.development = false;
config.source_map = .none;
} else {
config.force_node_env = .development;
config.jsx.development = true;
config.source_map = .linked;
}
config.source_map = .linked;
const completion_task = try bun.BundleV2.createAndScheduleCompletionTask(
config,

View File

@@ -0,0 +1,249 @@
import type { Subprocess } from "bun";
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { join } from "path";
describe("Bun.serve sourcemap generation", () => {
test("development: false should not include sourcemap comments in JS", async () => {
const dir = tempDirWithFiles("html-no-sourcemaps", {
"index.html": /*html*/ `
<!DOCTYPE html>
<html>
<head>
<title>Production Build</title>
<script type="module" src="script.js"></script>
</head>
<body>
<h1>Production</h1>
</body>
</html>
`,
"script.js": /*js*/ `
console.log("Hello from production");
const foo = () => {
return "bar";
};
foo();
`,
});
const { subprocess, port, hostname } = await waitForProductionServer(dir, {
"/": join(dir, "index.html"),
});
await using server = subprocess;
const html = await (await fetch(`http://${hostname}:${port}/`)).text();
const jsSrc = html.match(/<script type="module" crossorigin src="([^"]+)"/)?.[1];
if (!jsSrc) {
throw new Error("No script src found in HTML");
}
const response = await fetch(new URL(jsSrc, `http://${hostname}:${port}`));
const js = await response.text();
const headers = response.headers;
// Should NOT contain sourceMappingURL comment
expect(js).not.toContain("sourceMappingURL");
// Should NOT have SourceMap header
expect(headers.get("SourceMap")).toBeNull();
});
test("development: true should include sourcemap comments in JS", async () => {
const dir = tempDirWithFiles("html-with-sourcemaps", {
"index.html": /*html*/ `
<!DOCTYPE html>
<html>
<head>
<title>Development Build</title>
<script type="module" src="script.js"></script>
</head>
<body>
<h1>Development</h1>
</body>
</html>
`,
"script.js": /*js*/ `
console.log("Hello from development");
const foo = () => {
return "bar";
};
foo();
`,
});
const { subprocess, port, hostname } = await waitForDevelopmentServer(dir, {
"/": join(dir, "index.html"),
});
await using server = subprocess;
const html = await (await fetch(`http://${hostname}:${port}/`)).text();
const jsSrc = html.match(/<script type="module" crossorigin src="([^"]+)"/)?.[1];
if (!jsSrc) {
throw new Error("No script src found in HTML");
}
const response = await fetch(new URL(jsSrc, `http://${hostname}:${port}`));
const js = await response.text();
const headers = response.headers;
// SHOULD contain sourceMappingURL comment
expect(js).toContain("sourceMappingURL");
// SHOULD have SourceMap header
expect(headers.get("SourceMap")).toBeTruthy();
});
test("development: { hmr: false } should not include sourcemap comments", async () => {
const dir = tempDirWithFiles("html-dev-no-hmr", {
"index.html": /*html*/ `
<!DOCTYPE html>
<html>
<head>
<title>Development without HMR</title>
<script type="module" src="script.js"></script>
</head>
<body>
<h1>Development without HMR</h1>
</body>
</html>
`,
"script.js": /*js*/ `
console.log("Hello from dev no hmr");
`,
});
const { subprocess, port, hostname } = await waitForDevNoHMRServer(dir, {
"/": join(dir, "index.html"),
});
await using server = subprocess;
const html = await (await fetch(`http://${hostname}:${port}/`)).text();
const jsSrc = html.match(/<script type="module" crossorigin src="([^"]+)"/)?.[1];
if (!jsSrc) {
throw new Error("No script src found in HTML");
}
const response = await fetch(new URL(jsSrc, `http://${hostname}:${port}`));
const js = await response.text();
const headers = response.headers;
// In development mode (even without HMR), sourcemaps SHOULD be included
expect(js).toContain("sourceMappingURL");
expect(headers.get("SourceMap")).toBeTruthy();
});
});
async function waitForProductionServer(
dir: string,
entryPoints: Record<string, string>,
): Promise<{
subprocess: Subprocess;
port: number;
hostname: string;
}> {
let defer = Promise.withResolvers<{
subprocess: Subprocess;
port: number;
hostname: string;
}>();
const process = Bun.spawn({
cmd: [bunExe(), join(import.meta.dir, "fixtures", "serve-production.js")],
env: {
...bunEnv,
NODE_ENV: undefined,
},
cwd: dir,
stdio: ["inherit", "inherit", "inherit"],
ipc(message, subprocess) {
subprocess.send({
files: entryPoints,
});
defer.resolve({
subprocess,
port: message.port,
hostname: message.hostname,
});
},
});
return defer.promise;
}
async function waitForDevelopmentServer(
dir: string,
entryPoints: Record<string, string>,
): Promise<{
subprocess: Subprocess;
port: number;
hostname: string;
}> {
let defer = Promise.withResolvers<{
subprocess: Subprocess;
port: number;
hostname: string;
}>();
const process = Bun.spawn({
cmd: [bunExe(), join(import.meta.dir, "fixtures", "serve-development.js")],
env: {
...bunEnv,
NODE_ENV: undefined,
},
cwd: dir,
stdio: ["inherit", "inherit", "inherit"],
ipc(message, subprocess) {
subprocess.send({
files: entryPoints,
});
defer.resolve({
subprocess,
port: message.port,
hostname: message.hostname,
});
},
});
return defer.promise;
}
async function waitForDevNoHMRServer(
dir: string,
entryPoints: Record<string, string>,
): Promise<{
subprocess: Subprocess;
port: number;
hostname: string;
}> {
let defer = Promise.withResolvers<{
subprocess: Subprocess;
port: number;
hostname: string;
}>();
const process = Bun.spawn({
cmd: [bunExe(), join(import.meta.dir, "fixtures", "serve-dev-no-hmr.js")],
env: {
...bunEnv,
NODE_ENV: undefined,
},
cwd: dir,
stdio: ["inherit", "inherit", "inherit"],
ipc(message, subprocess) {
subprocess.send({
files: entryPoints,
});
defer.resolve({
subprocess,
port: message.port,
hostname: message.hostname,
});
},
});
return defer.promise;
}

View File

@@ -0,0 +1,31 @@
let server = Bun.serve({
port: 0,
development: {
hmr: false,
},
async fetch(req) {
return new Response("Hello World", {
status: 404,
});
},
});
process.on("message", async message => {
const files = message.files || {};
const routes = {};
for (const [key, value] of Object.entries(files)) {
routes[key] = (await import(value)).default;
}
server.reload({
static: routes,
development: {
hmr: false,
},
});
});
process.send({
port: server.port,
hostname: server.hostname,
});

View File

@@ -0,0 +1,27 @@
let server = Bun.serve({
port: 0,
development: true,
async fetch(req) {
return new Response("Hello World", {
status: 404,
});
},
});
process.on("message", async message => {
const files = message.files || {};
const routes = {};
for (const [key, value] of Object.entries(files)) {
routes[key] = (await import(value)).default;
}
server.reload({
static: routes,
development: true,
});
});
process.send({
port: server.port,
hostname: server.hostname,
});

View File

@@ -0,0 +1,27 @@
let server = Bun.serve({
port: 0,
development: false,
async fetch(req) {
return new Response("Hello World", {
status: 404,
});
},
});
process.on("message", async message => {
const files = message.files || {};
const routes = {};
for (const [key, value] of Object.entries(files)) {
routes[key] = (await import(value)).default;
}
server.reload({
static: routes,
development: false,
});
});
process.send({
port: server.port,
hostname: server.hostname,
});

View File

@@ -685,55 +685,3 @@ payloads.forEach(type => {
expect(calls).toBeGreaterThan(0);
});
});
// Test Blob input directly (without Response wrapper)
it("works with Blob input directly", async () => {
const htmlContent = '<script src="/main.js"></script>';
const blob = new Blob([htmlContent]);
let srcValue = null;
const result = new HTMLRewriter()
.on("script", {
element(element) {
srcValue = element.getAttribute("src");
},
})
.transform(blob);
expect(result).toBeInstanceOf(Response);
const text = await result.text();
expect(text).toBe(htmlContent);
expect(srcValue).toBe("/main.js");
});
// Test BunFile input
it("works with BunFile input", async () => {
const { mkdtemp, writeFile, rm } = await import("fs/promises");
const { tmpdir } = await import("os");
const { join } = await import("path");
const tempDir = await mkdtemp(join(tmpdir(), "htmlrewriter-test-"));
const tempFile = join(tempDir, "index.html");
const htmlContent = '<div id="test">Hello World</div>';
try {
await writeFile(tempFile, htmlContent);
const file = Bun.file(tempFile);
let divId = null;
const result = new HTMLRewriter()
.on("div", {
element(element) {
divId = element.getAttribute("id");
},
})
.transform(file);
expect(result).toBeInstanceOf(Response);
const text = await result.text();
expect(text).toBe(htmlContent);
expect(divId).toBe("test");
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});