Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/bun.js/api/server/HTMLBundle.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
249 changes: 249 additions & 0 deletions test/js/bun/http/bun-serve-html-sourcemaps.test.ts
Original file line number Diff line number Diff line change
@@ -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;
}
31 changes: 31 additions & 0 deletions test/js/bun/http/fixtures/serve-dev-no-hmr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
let server = Bun.serve({
port: 0,
development: {
hmr: false,
},
async fetch(req) {
return new Response("Hello World", {
status: 404,
});
},
});
Comment on lines +1 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

LGTM! Dev server with HMR disabled is configured correctly.

Using development: { hmr: false } correctly tests the scenario where development mode is enabled but HMR is disabled.

Note: This fixture file is nearly identical to serve-production.js and serve-development.js. Consider extracting a shared factory function to reduce duplication if more fixtures are added in the future.

🤖 Prompt for AI Agents
test/js/bun/http/fixtures/serve-dev-no-hmr.js lines 1-11: this fixture is nearly
identical to serve-production.js and serve-development.js; extract a shared
factory function (e.g., createServeFixture(options)) in a new helper module
under test/js/bun/http/fixtures/helpers.js that accepts the differing options
(port, development.hmr, status, etc.), update the three fixture files to import
and call that factory with their specific config, and ensure tests continue to
export the Bun.serve instance or function the tests expect; keep behavior
unchanged and only move duplicated object construction into the helper.


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,
});
27 changes: 27 additions & 0 deletions test/js/bun/http/fixtures/serve-development.js
Original file line number Diff line number Diff line change
@@ -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,
});
Comment on lines +1 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

LGTM! Development server fixture is configured correctly.

The fixture properly sets development: true for testing sourcemap generation in full development mode.

Note: This is the third nearly-identical fixture file. The three fixtures (serve-production.js, serve-development.js, serve-dev-no-hmr.js) differ only in their development configuration. Consider refactoring to a parameterized fixture factory if maintenance becomes an issue:

// fixtures/serve-factory.js
export function createServerFixture(developmentConfig) {
  let server = Bun.serve({
    port: 0,
    development: developmentConfig,
    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: developmentConfig,
    });
  });
  
  process.send({
    port: server.port,
    hostname: server.hostname,
  });
}
🤖 Prompt for AI Agents
In test/js/bun/http/fixtures/serve-development.js lines 1-27: this fixture is
nearly identical to serve-production.js and serve-dev-no-hmr.js differing only
by the development flag; refactor by extracting the shared server code into a
new factory module (e.g., fixtures/serve-factory.js) that exports a
createServerFixture(developmentConfig) function, update this file to import that
factory and invoke it with development: true, and update the other two fixtures
to call the same factory with their respective boolean values so the duplicated
code is removed and maintenance is centralized.

27 changes: 27 additions & 0 deletions test/js/bun/http/fixtures/serve-production.js
Original file line number Diff line number Diff line change
@@ -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,
});