From fad856c03c6be18e9657ea56ac8781d32be71165 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 6 Feb 2025 00:06:52 -0800 Subject: [PATCH] Support fs.stat, fs.existsSync, fs.readFile, fs.promises.stat, fs.promises.readFile in `bun build --compile` (#17102) Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> --- src/StandaloneModuleGraph.zig | 26 ++++++++++- src/bun.js/node/node_fs.zig | 65 +++++++++++++++++----------- src/bun_js.zig | 1 + test/bundler/bundler_compile.test.ts | 6 ++- 4 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index ce85cb24e7..649fd5e7ed 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -36,6 +36,17 @@ pub const StandaloneModuleGraph = struct { pub const base_public_path = targetBasePublicPath(Environment.os, ""); pub const base_public_path_with_default_suffix = targetBasePublicPath(Environment.os, "root/"); + const Instance = struct { + pub var instance: ?*StandaloneModuleGraph = null; + }; + + pub fn get() ?*StandaloneModuleGraph { + return Instance.instance; + } + + pub fn set(instance: *StandaloneModuleGraph) void { + Instance.instance = instance; + } pub fn targetBasePublicPath(target: Environment.OperatingSystem, comptime suffix: [:0]const u8) [:0]const u8 { return switch (target) { @@ -62,10 +73,16 @@ pub const StandaloneModuleGraph = struct { return this.findAssumeStandalonePath(name); } + pub fn stat(this: *const StandaloneModuleGraph, name: []const u8) ?bun.Stat { + const file = this.find(name) orelse return null; + return file.stat(); + } + pub fn findAssumeStandalonePath(this: *const StandaloneModuleGraph, name: []const u8) ?*File { if (Environment.isWindows) { var normalized_buf: bun.PathBuffer = undefined; - const normalized = bun.path.platformToPosixBuf(u8, name, &normalized_buf); + const input = strings.withoutNTPrefix(u8, name); + const normalized = bun.path.platformToPosixBuf(u8, input, &normalized_buf); return this.files.getPtr(normalized); } return this.files.getPtr(name); @@ -107,6 +124,13 @@ pub const StandaloneModuleGraph = struct { bytecode: []u8 = "", module_format: ModuleFormat = .none, + pub fn stat(this: *const File) bun.Stat { + var result = std.mem.zeroes(bun.Stat); + result.size = @intCast(this.contents.len); + result.mode = bun.S.IFREG | 0o644; + return result; + } + pub fn lessThanByIndex(ctx: []const File, lhs_i: u32, rhs_i: u32) bool { const lhs = ctx[lhs_i]; const rhs = ctx[rhs_i]; diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index d2dd5eb623..1bc741454c 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -3789,10 +3789,18 @@ pub const NodeFS = struct { pub fn exists(this: *NodeFS, args: Arguments.Exists, _: Flavor) Maybe(Return.Exists) { // NOTE: exists cannot return an error const path: PathLike = args.path orelse return .{ .result = false }; + + if (bun.StandaloneModuleGraph.get()) |graph| { + if (graph.find(path.slice()) != null) { + return .{ .result = true }; + } + } + const slice = if (path.slice().len == 0) comptime bun.OSPathLiteral("") else path.osPathKernel32(&this.sync_error_buf); + return .{ .result = bun.sys.existsOSPath(slice, false) }; } @@ -4968,32 +4976,31 @@ pub const NodeFS = struct { const fd_maybe_windows: FileDescriptor = switch (args.path) { .path => brk: { path = args.path.path.sliceZ(&this.sync_error_buf); - if (this.vm) |vm| { - if (vm.standalone_module_graph) |graph| { - if (graph.find(path)) |file| { - if (args.encoding == .buffer) { - return .{ - .result = .{ - .buffer = Buffer.fromBytes( - bun.default_allocator.dupe(u8, file.contents) catch bun.outOfMemory(), - bun.default_allocator, - .Uint8Array, - ), - }, - }; - } else if (comptime string_type == .default) - return .{ - .result = .{ - .string = bun.default_allocator.dupe(u8, file.contents) catch bun.outOfMemory(), - }, - } - else - return .{ - .result = .{ - .null_terminated = bun.default_allocator.dupeZ(u8, file.contents) catch bun.outOfMemory(), - }, - }; - } + + if (bun.StandaloneModuleGraph.get()) |graph| { + if (graph.find(path)) |file| { + if (args.encoding == .buffer) { + return .{ + .result = .{ + .buffer = Buffer.fromBytes( + bun.default_allocator.dupe(u8, file.contents) catch bun.outOfMemory(), + bun.default_allocator, + .Uint8Array, + ), + }, + }; + } else if (comptime string_type == .default) + return .{ + .result = .{ + .string = bun.default_allocator.dupe(u8, file.contents) catch bun.outOfMemory(), + }, + } + else + return .{ + .result = .{ + .null_terminated = bun.default_allocator.dupeZ(u8, file.contents) catch bun.outOfMemory(), + }, + }; } } @@ -5748,6 +5755,12 @@ pub const NodeFS = struct { pub fn stat(this: *NodeFS, args: Arguments.Stat, _: Flavor) Maybe(Return.Stat) { const path = args.path.sliceZ(&this.sync_error_buf); + if (bun.StandaloneModuleGraph.get()) |graph| { + if (graph.stat(path)) |result| { + return .{ .result = .{ .stats = .init(result, args.big_int) } }; + } + } + return switch (Syscall.stat(path)) { .result => |result| .{ .result = .{ .stats = .init(result, args.big_int) }, diff --git a/src/bun_js.zig b/src/bun_js.zig index 0a96ee2a1b..0524626bdd 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -51,6 +51,7 @@ pub const Run = struct { const graph_ptr = try bun.default_allocator.create(bun.StandaloneModuleGraph); graph_ptr.* = graph; + graph_ptr.set(); js_ast.Expr.Data.Store.create(); js_ast.Stmt.Data.Store.create(); diff --git a/test/bundler/bundler_compile.test.ts b/test/bundler/bundler_compile.test.ts index 3949d409ea..aa5a22a2ba 100644 --- a/test/bundler/bundler_compile.test.ts +++ b/test/bundler/bundler_compile.test.ts @@ -4,7 +4,7 @@ import { rmSync } from "fs"; import { itBundled } from "./expectBundled"; import { isFlaky, isWindows } from "harness"; -describe.todoIf(isFlaky && isWindows)("bundler", () => { +describe("bundler", () => { itBundled("compile/HelloWorld", { compile: true, files: { @@ -440,6 +440,10 @@ describe.todoIf(isFlaky && isWindows)("bundler", () => { fs.readFileSync(big).toString("hex"); await Bun.file(big).arrayBuffer(); fs.readFileSync(small).toString("hex"); + if ((await fs.promises.readFile(small)).length !== 31) throw "fail readFile"; + if (fs.statSync(small).size !== 31) throw "fail statSync"; + if (fs.statSync(big).size !== (4096 + (32 - 2))) throw "fail statSync"; + if (((await fs.promises.stat(big)).size) !== (4096 + (32 - 2))) throw "fail stat"; await Bun.file(small).arrayBuffer(); console.log("PASS"); `,