mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Compare commits
6 Commits
bun-v1.3.5
...
claude/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
715dd5bfc3 | ||
|
|
7260d5fe22 | ||
|
|
53d49a1dc2 | ||
|
|
0de4fcbc05 | ||
|
|
db66f28fae | ||
|
|
3f76ceed97 |
@@ -1714,6 +1714,9 @@ pub const api = struct {
|
||||
/// ignore_dce_annotations
|
||||
ignore_dce_annotations: bool,
|
||||
|
||||
/// nativefill
|
||||
nativefill: bool = false,
|
||||
|
||||
/// e.g.:
|
||||
/// [serve.static]
|
||||
/// plugins = ["tailwindcss"]
|
||||
|
||||
@@ -38,6 +38,7 @@ pub const JSBundler = struct {
|
||||
env_prefix: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
tsconfig_override: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
compile: ?CompileOptions = null,
|
||||
nativefill: bool = false,
|
||||
|
||||
pub const CompileOptions = struct {
|
||||
compile_target: CompileTarget = .{},
|
||||
@@ -301,6 +302,10 @@ pub const JSBundler = struct {
|
||||
this.no_macros = !macros_flag;
|
||||
}
|
||||
|
||||
if (try config.getBooleanLoose(globalThis, "nativefill")) |nativefill| {
|
||||
this.nativefill = nativefill;
|
||||
}
|
||||
|
||||
if (try config.getBooleanLoose(globalThis, "bytecode")) |bytecode| {
|
||||
this.bytecode = bytecode;
|
||||
|
||||
|
||||
@@ -636,6 +636,22 @@ fn getCodeForParseTaskWithoutPlugins(
|
||||
const trace = bun.perf.trace("Bundler.readFile");
|
||||
defer trace.end();
|
||||
|
||||
// Check if this is a nativefill virtual path
|
||||
if (strings.hasPrefixComptime(file_path.text, "/bun-nativefill/")) {
|
||||
const NativefillModules = @import("../nativefill.zig");
|
||||
// Extract module name from path like "/bun-nativefill/strip-ansi.js"
|
||||
const module_name = file_path.text["/bun-nativefill/".len..];
|
||||
const dot_index = std.mem.indexOfScalar(u8, module_name, '.') orelse module_name.len;
|
||||
const base_name = module_name[0..dot_index];
|
||||
|
||||
if (NativefillModules.Map.get(base_name)) |source| {
|
||||
break :brk .{
|
||||
.contents = source.contents,
|
||||
.fd = bun.invalid_fd,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(file_path.namespace, "node")) lookup_builtin: {
|
||||
if (task.ctx.framework) |f| {
|
||||
if (f.built_in_modules.get(file_path.text)) |file| {
|
||||
|
||||
@@ -1861,6 +1861,7 @@ pub const BundleV2 = struct {
|
||||
transpiler.options.css_chunking = config.css_chunking;
|
||||
transpiler.options.banner = config.banner.slice();
|
||||
transpiler.options.footer = config.footer.slice();
|
||||
transpiler.options.nativefill = config.nativefill;
|
||||
|
||||
if (transpiler.options.compile) {
|
||||
// Emitting DCE annotations is nonsensical in --compile.
|
||||
|
||||
@@ -421,6 +421,7 @@ pub const Command = struct {
|
||||
emit_dce_annotations: bool = true,
|
||||
output_format: options.Format = .esm,
|
||||
bytecode: bool = false,
|
||||
nativefill: bool = false,
|
||||
banner: []const u8 = "",
|
||||
footer: []const u8 = "",
|
||||
css_chunking: bool = false,
|
||||
|
||||
@@ -67,6 +67,7 @@ pub const transpiler_params_ = [_]ParamType{
|
||||
clap.parseParam("--drop <STR>... Remove function calls, e.g. --drop=console removes all console.* calls.") catch unreachable,
|
||||
clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi") catch unreachable,
|
||||
clap.parseParam("--no-macros Disable macros from being executed in the bundler, transpiler and runtime") catch unreachable,
|
||||
clap.parseParam("--nativefill Replace imports of packages with Bun's native implementations when available (requires target to be 'bun')") catch unreachable,
|
||||
clap.parseParam("--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime") catch unreachable,
|
||||
clap.parseParam("--jsx-fragment <STR> Changes the function called when compiling JSX fragments") catch unreachable,
|
||||
clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable,
|
||||
@@ -1224,6 +1225,10 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
ctx.debug.macros = .{ .disable = {} };
|
||||
}
|
||||
|
||||
if (args.flag("--nativefill")) {
|
||||
ctx.bundler_options.nativefill = true;
|
||||
}
|
||||
|
||||
opts.output_dir = output_dir;
|
||||
if (output_file != null)
|
||||
ctx.debug.output_file = output_file.?;
|
||||
|
||||
@@ -77,6 +77,7 @@ pub const BuildCommand = struct {
|
||||
this_transpiler.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
this_transpiler.options.emit_dce_annotations = ctx.bundler_options.emit_dce_annotations;
|
||||
this_transpiler.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
|
||||
this_transpiler.options.nativefill = ctx.bundler_options.nativefill;
|
||||
|
||||
this_transpiler.options.banner = ctx.bundler_options.banner;
|
||||
this_transpiler.options.footer = ctx.bundler_options.footer;
|
||||
|
||||
12
src/nativefill.zig
Normal file
12
src/nativefill.zig
Normal file
@@ -0,0 +1,12 @@
|
||||
const strip_ansi_source = logger.Source.initPathString("/bun-nativefill/strip-ansi.js", "export default Bun.stripANSI;");
|
||||
const string_width_source = logger.Source.initPathString("/bun-nativefill/string-width.js", "export default Bun.stringWidth;");
|
||||
const better_sqlite3_source = logger.Source.initPathString("/bun-nativefill/better-sqlite3.js", "export { Database as default } from 'bun:sqlite';");
|
||||
|
||||
pub const Map = bun.ComptimeStringMap(*const logger.Source, .{
|
||||
.{ "strip-ansi", &strip_ansi_source },
|
||||
.{ "string-width", &string_width_source },
|
||||
.{ "better-sqlite3", &better_sqlite3_source },
|
||||
});
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
@@ -1770,6 +1770,7 @@ pub const BundleOptions = struct {
|
||||
|
||||
macro_remap: MacroRemap = MacroRemap{},
|
||||
no_macros: bool = false,
|
||||
nativefill: bool = false,
|
||||
|
||||
conditions: ESMConditions = undefined,
|
||||
tree_shaking: bool = false,
|
||||
|
||||
@@ -1217,6 +1217,22 @@ pub const Resolver = struct {
|
||||
}
|
||||
|
||||
if (check_package) {
|
||||
// Check for nativefill replacements when enabled and target is bun
|
||||
if (r.opts.nativefill and r.opts.target.isBun()) {
|
||||
if (NativefillModules.Map.get(import_path)) |source| {
|
||||
// Return a virtual module with the nativefill source
|
||||
result.path_pair.primary = Fs.Path.init(source.path.text);
|
||||
result.is_from_node_modules = true;
|
||||
result.diff_case = null;
|
||||
result.file_fd = .invalid;
|
||||
result.dirname_fd = .invalid;
|
||||
result.module_type = .esm;
|
||||
result.primary_side_effects_data = .no_side_effects__pure_data;
|
||||
result.is_external = false;
|
||||
return .{ .success = result };
|
||||
}
|
||||
}
|
||||
|
||||
if (r.opts.polyfill_node_globals) {
|
||||
const had_node_prefix = strings.hasPrefixComptime(import_path, "node:");
|
||||
const import_path_without_node_prefix = if (had_node_prefix) import_path["node:".len..] else import_path;
|
||||
@@ -4345,6 +4361,7 @@ const string = []const u8;
|
||||
|
||||
const Dependency = @import("../install/dependency.zig");
|
||||
const DotEnv = @import("../env_loader.zig");
|
||||
const NativefillModules = @import("../nativefill.zig");
|
||||
const NodeFallbackModules = @import("../node_fallbacks.zig");
|
||||
const ResolvePath = @import("./resolve_path.zig");
|
||||
const ast = @import("../import_record.zig");
|
||||
|
||||
363
test/bundler/nativefill.test.ts
Normal file
363
test/bundler/nativefill.test.ts
Normal file
@@ -0,0 +1,363 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
import path from "path";
|
||||
|
||||
test("nativefill replaces strip-ansi with Bun.stripANSI when target is bun", async () => {
|
||||
using dir = tempDir("nativefill-strip-ansi", {
|
||||
"index.js": `
|
||||
import stripAnsi from 'strip-ansi';
|
||||
console.log(stripAnsi('\\x1b[31mHello\\x1b[0m'));
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"strip-ansi": "7.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./index.js", "--outdir", outdir, "--target", "bun", "--nativefill"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(outdir, "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should use Bun.stripANSI, not the npm package
|
||||
expect(output).toContain("Bun.stripANSI");
|
||||
expect(output).not.toContain("node_modules");
|
||||
});
|
||||
|
||||
test("nativefill replaces string-width with Bun.stringWidth when target is bun", async () => {
|
||||
using dir = tempDir("nativefill-string-width", {
|
||||
"index.js": `
|
||||
import stringWidth from 'string-width';
|
||||
console.log(stringWidth('hello'));
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"string-width": "5.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./index.js", "--outdir", outdir, "--target", "bun", "--nativefill"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(outdir, "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should use Bun.stringWidth, not the npm package
|
||||
expect(output).toContain("Bun.stringWidth");
|
||||
expect(output).not.toContain("node_modules");
|
||||
});
|
||||
|
||||
test("nativefill replaces better-sqlite3 with bun:sqlite when target is bun", async () => {
|
||||
using dir = tempDir("nativefill-better-sqlite3", {
|
||||
"index.js": `
|
||||
import Database from 'better-sqlite3';
|
||||
const db = new Database(':memory:');
|
||||
console.log(db);
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"better-sqlite3": "9.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./index.js", "--outdir", outdir, "--target", "bun", "--nativefill"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(outdir, "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should use bun:sqlite, not the npm package
|
||||
expect(output).toContain("bun:sqlite");
|
||||
expect(output).not.toContain("node_modules");
|
||||
});
|
||||
|
||||
test("nativefill is disabled by default", async () => {
|
||||
using dir = tempDir("nativefill-disabled", {
|
||||
"index.js": `
|
||||
import stripAnsi from 'strip-ansi';
|
||||
console.log(stripAnsi('\\x1b[31mHello\\x1b[0m'));
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"strip-ansi": "7.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// First install the dependency
|
||||
await using installProc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
await installProc.exited;
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
// Build without --nativefill flag
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./index.js", "--outdir", outdir, "--target", "bun"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(outdir, "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should NOT use Bun.stripANSI since nativefill is disabled
|
||||
expect(output).not.toContain("Bun.stripANSI");
|
||||
// Should contain the actual module import
|
||||
expect(output).toContain("strip-ansi");
|
||||
});
|
||||
|
||||
test("nativefill fails when target is not bun", async () => {
|
||||
using dir = tempDir("nativefill-wrong-target", {
|
||||
"index.js": `
|
||||
import stripAnsi from 'strip-ansi';
|
||||
console.log(stripAnsi('\\x1b[31mHello\\x1b[0m'));
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"strip-ansi": "7.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// First install the dependency
|
||||
await using installProc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
await installProc.exited;
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
// Try building with --nativefill but target is browser
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./index.js", "--outdir", outdir, "--target", "browser", "--nativefill"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(outdir, "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should NOT use Bun.stripANSI when target is not bun
|
||||
expect(output).not.toContain("Bun.stripANSI");
|
||||
// Should contain the actual module import
|
||||
expect(output).toContain("strip-ansi");
|
||||
});
|
||||
|
||||
test("nativefill works with multiple imports", async () => {
|
||||
using dir = tempDir("nativefill-multiple", {
|
||||
"index.js": `
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import stringWidth from 'string-width';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
console.log(stripAnsi('\\x1b[31mHello\\x1b[0m'));
|
||||
console.log(stringWidth('hello'));
|
||||
const db = new Database(':memory:');
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"strip-ansi": "7.0.0",
|
||||
"string-width": "5.0.0",
|
||||
"better-sqlite3": "9.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./index.js", "--outdir", outdir, "--target", "bun", "--nativefill"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(outdir, "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should use all Bun native implementations
|
||||
expect(output).toContain("Bun.stripANSI");
|
||||
expect(output).toContain("Bun.stringWidth");
|
||||
expect(output).toContain("bun:sqlite");
|
||||
expect(output).not.toContain("node_modules");
|
||||
});
|
||||
|
||||
test("nativefill works with JS API", async () => {
|
||||
using dir = tempDir("nativefill-js-api", {
|
||||
"build.js": `
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.js"],
|
||||
outdir: "./out",
|
||||
target: "bun",
|
||||
nativefill: true,
|
||||
});
|
||||
`,
|
||||
"index.js": `
|
||||
import stripAnsi from 'strip-ansi';
|
||||
console.log(stripAnsi('test'));
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"strip-ansi": "7.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Run the build script
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Read the output file
|
||||
const outputFile = path.join(String(dir), "out", "index.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Should use Bun.stripANSI
|
||||
expect(output).toContain("Bun.stripANSI");
|
||||
expect(output).not.toContain("node_modules");
|
||||
});
|
||||
|
||||
test("nativefill correctly replaces imports in output", async () => {
|
||||
using dir = tempDir("nativefill-output-check", {
|
||||
"test.js": `
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import stringWidth from 'string-width';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
// Use the imports
|
||||
const result = stripAnsi('test');
|
||||
const width = stringWidth('hello');
|
||||
const db = new Database(':memory:');
|
||||
`,
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"strip-ansi": "7.0.0",
|
||||
"string-width": "5.0.0",
|
||||
"better-sqlite3": "9.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const outdir = path.join(String(dir), "out");
|
||||
|
||||
// Build with nativefill
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "./test.js", "--outdir", outdir, "--target", "bun", "--nativefill"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
buildProc.stdout.text(),
|
||||
buildProc.stderr.text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildExitCode).toBe(0);
|
||||
|
||||
// Check the output file contains the replacements
|
||||
const outputFile = path.join(outdir, "test.js");
|
||||
const output = await Bun.file(outputFile).text();
|
||||
|
||||
// Verify all replacements are in the output
|
||||
expect(output).toContain("Bun.stripANSI");
|
||||
expect(output).toContain("Bun.stringWidth");
|
||||
expect(output).toContain("bun:sqlite");
|
||||
|
||||
// Verify the npm packages are not referenced as imports
|
||||
expect(output).not.toContain("node_modules");
|
||||
// The names might appear in comments, but shouldn't be imported from npm packages
|
||||
expect(output).not.toMatch(/from ["']strip-ansi["']/);
|
||||
expect(output).not.toMatch(/from ["']string-width["']/);
|
||||
expect(output).not.toMatch(/from ["']better-sqlite3["']/);
|
||||
});
|
||||
Reference in New Issue
Block a user