Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reading embedded files in compiled executables #3405

Merged
merged 2 commits into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/bun.js/api/bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,9 @@ pub fn createNodeFS(
) js.JSValueRef {
var module = ctx.allocator().create(JSC.Node.NodeJSFS) catch unreachable;
module.* = .{};
var vm = ctx.bunVM();
if (vm.standalone_module_graph != null)
module.node_fs.vm = vm;

return module.toJS(ctx).asObjectRef();
}
Expand Down
22 changes: 9 additions & 13 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2744,19 +2744,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
// 1. Bun.file("foo")
// 2. The content-disposition header is not present
if (!has_content_disposition and content_type.category.autosetFilename()) {
if (this.blob.store()) |store| {
if (store.data == .file) {
if (store.data.file.pathlike == .path) {
const basename = std.fs.path.basename(store.data.file.pathlike.path.slice());
if (basename.len > 0) {
var filename_buf: [1024]u8 = undefined;

resp.writeHeader(
"content-disposition",
std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@min(basename.len, 1024 - 32)]}) catch "",
);
}
}
if (this.blob.getFileName()) |filename| {
const basename = std.fs.path.basename(filename);
if (basename.len > 0) {
var filename_buf: [1024]u8 = undefined;

resp.writeHeader(
"content-disposition",
std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@min(basename.len, 1024 - 32)]}) catch "",
);
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/bun.js/javascript.zig
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,10 @@ pub const VirtualMachine = struct {
pub inline fn nodeFS(this: *VirtualMachine) *Node.NodeFS {
return this.node_fs orelse brk: {
this.node_fs = bun.default_allocator.create(Node.NodeFS) catch unreachable;
this.node_fs.?.* = Node.NodeFS{};
this.node_fs.?.* = Node.NodeFS{
// only used when standalone module graph is enabled
.vm = if (this.standalone_module_graph != null) this else null,
};
break :brk this.node_fs.?;
};
}
Expand Down
30 changes: 30 additions & 0 deletions src/bun.js/node/node_fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2492,6 +2492,7 @@ pub const NodeFS = struct {
/// That means a stack-allocated buffer won't suffice. Instead, we re-use
/// the heap allocated buffer on the NodefS struct
sync_error_buf: [bun.MAX_PATH_BYTES]u8 = undefined,
vm: ?*JSC.VirtualMachine = null,

pub const ReturnType = Return;

Expand Down Expand Up @@ -3442,6 +3443,35 @@ pub const NodeFS = struct {
const fd = 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 @panic("out of memory"),
bun.default_allocator,
.Uint8Array,
),
},
};
} else if (comptime string_type == .default)
.{
.result = .{
.string = bun.default_allocator.dupe(u8, file.contents) catch @panic("out of memory"),
},
}
else
.{
.result = .{
.null_terminated = bun.default_allocator.dupeZ(u8, file.contents) catch @panic("out of memory"),
},
};
}
}
}

break :brk switch (Syscall.open(
path,
os.O.RDONLY | os.O.NOCTTY,
Expand Down
36 changes: 34 additions & 2 deletions src/bun.js/webcore/blob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,13 @@ pub const Blob = struct {
switch (path_) {
.path => {
const slice = path_.path.slice();

if (vm.standalone_module_graph) |graph| {
if (graph.find(slice)) |file| {
return file.blob(globalThis).dupe();
}
}

var cloned = (allocator.dupeZ(u8, slice) catch unreachable)[0..slice.len];

break :brk .{
Expand Down Expand Up @@ -2195,6 +2202,9 @@ pub const Blob = struct {
cap: SizeType = 0,
allocator: std.mem.Allocator,

/// Used by standalone module graph
stored_name: bun.PathString = bun.PathString.empty,

pub fn init(bytes: []u8, allocator: std.mem.Allocator) ByteStore {
return .{
.ptr = bytes.ptr,
Expand Down Expand Up @@ -2528,17 +2538,31 @@ pub const Blob = struct {
this: *Blob,
globalThis: *JSC.JSGlobalObject,
) callconv(.C) JSValue {
if (this.getFileName()) |path| {
var str = bun.String.create(path);
return str.toJS(globalThis);
}

return JSValue.undefined;
}

pub fn getFileName(
this: *const Blob,
) ?[]const u8 {
if (this.store) |store| {
if (store.data == .file) {
if (store.data.file.pathlike == .path) {
return ZigString.fromUTF8(store.data.file.pathlike.path.slice()).toValueGC(globalThis);
return store.data.file.pathlike.path.slice();
}

// we shouldn't return Number here.
} else if (store.data == .bytes) {
if (store.data.bytes.stored_name.slice().len > 0)
return store.data.bytes.stored_name.slice();
}
}

return JSC.JSValue.jsUndefined();
return null;
}

// TODO: Move this to a separate `File` object or BunFile
Expand Down Expand Up @@ -3469,6 +3493,14 @@ pub const AnyBlob = union(enum) {
InternalBlob: InternalBlob,
WTFStringImpl: bun.WTF.StringImpl,

pub fn getFileName(this: *const AnyBlob) ?[]const u8 {
return switch (this.*) {
.Blob => this.Blob.getFileName(),
.WTFStringImpl => null,
.InternalBlob => null,
};
}

pub inline fn fastSize(this: *const AnyBlob) Blob.SizeType {
return switch (this.*) {
.Blob => this.Blob.size,
Expand Down
1 change: 1 addition & 0 deletions src/cli/build_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub const BuildCommand = struct {
// We never want to hit the filesystem for these files
// This "compiled" protocol is specially handled by the module resolver.
this_bundler.options.public_path = "compiled://root/";
this_bundler.resolver.opts.public_path = "compiled://root/";

if (outfile.len == 0) {
outfile = std.fs.path.basename(this_bundler.options.entry_points[0]);
Expand Down
44 changes: 43 additions & 1 deletion src/standalone_bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ pub const StandaloneModuleGraph = struct {
return &this.files.values()[this.entry_point_id];
}

pub fn find(this: *const StandaloneModuleGraph, name: []const u8) ?*File {
if (!bun.strings.hasPrefixComptime(name, "compiled://root/")) {
return null;
}

return this.files.getPtr(name);
}

pub const CompiledModuleGraphFile = struct {
name: Schema.StringPointer = .{},
loader: bun.options.Loader = .file,
Expand All @@ -30,6 +38,32 @@ pub const StandaloneModuleGraph = struct {
loader: bun.options.Loader,
contents: []const u8 = "",
sourcemap: LazySourceMap,
blob_: ?*bun.JSC.WebCore.Blob = null,

pub fn blob(this: *File, globalObject: *bun.JSC.JSGlobalObject) *bun.JSC.WebCore.Blob {
if (this.blob_ == null) {
var store = bun.JSC.WebCore.Blob.Store.init(@constCast(this.contents), bun.default_allocator) catch @panic("out of memory");
// make it never free
store.ref();

var blob_ = bun.default_allocator.create(bun.JSC.WebCore.Blob) catch @panic("out of memory");
blob_.* = bun.JSC.WebCore.Blob.initWithStore(store, globalObject);
blob_.allocator = bun.default_allocator;

if (bun.HTTP.MimeType.byExtensionNoDefault(bun.strings.trimLeadingChar(std.fs.path.extension(this.name), '.'))) |mime| {
store.mime_type = mime;
blob_.content_type = mime.value;
blob_.content_type_was_set = true;
blob_.content_type_allocated = false;
}

store.data.bytes.stored_name = bun.PathString.init(this.name);

this.blob_ = blob_;
}

return this.blob_.?;
}
};

pub const LazySourceMap = union(enum) {
Expand Down Expand Up @@ -152,8 +186,16 @@ pub const StandaloneModuleGraph = struct {
continue;
}

var dest_path = output_file.dest_path;
if (bun.strings.hasPrefixComptime(dest_path, "./")) {
dest_path = dest_path[2..];
}

var module = CompiledModuleGraphFile{
.name = string_builder.fmtAppendCount("{s}{s}", .{ prefix, output_file.dest_path }),
.name = string_builder.fmtAppendCount("{s}{s}", .{
prefix,
dest_path,
}),
.loader = output_file.loader,
.contents = string_builder.appendCount(output_file.value.buffer.bytes),
};
Expand Down