aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/api/JSBundler.zig42
-rw-r--r--src/bun.js/event_loop.zig16
-rw-r--r--src/bun.js/webcore/blob.zig8
-rw-r--r--src/bun.zig89
-rw-r--r--src/bundler/bundle_v2.zig240
-rw-r--r--src/options.zig51
6 files changed, 427 insertions, 19 deletions
diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig
index c16cc8897..a89001868 100644
--- a/src/bun.js/api/JSBundler.zig
+++ b/src/bun.js/api/JSBundler.zig
@@ -52,9 +52,9 @@ pub const JSBundler = struct {
pub const Config = struct {
target: options.Platform = options.Platform.browser,
- entry_points: std.BufSet = std.BufSet.init(bun.default_allocator),
+ entry_points: bun.StringSet = bun.StringSet.init(bun.default_allocator),
hot: bool = false,
- define: std.BufMap = std.BufMap.init(bun.default_allocator),
+ define: bun.StringMap = bun.StringMap.init(bun.default_allocator),
dir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
outdir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
serve: Serve = .{},
@@ -66,6 +66,8 @@ pub const JSBundler = struct {
names: Names = .{},
label: OwnedString = OwnedString.initEmpty(bun.default_allocator),
+ external: bun.StringSet = bun.StringSet.init(bun.default_allocator),
+ sourcemap: options.SourceMapOption = .none,
pub const List = bun.StringArrayHashMapUnmanaged(Config);
@@ -85,8 +87,9 @@ pub const JSBundler = struct {
pub fn fromJS(globalThis: *JSC.JSGlobalObject, config: JSC.JSValue, allocator: std.mem.Allocator) !Config {
var this = Config{
- .entry_points = std.BufSet.init(allocator),
- .define = std.BufMap.init(allocator),
+ .entry_points = bun.StringSet.init(allocator),
+ .external = bun.StringSet.init(allocator),
+ .define = bun.StringMap.init(allocator),
.dir = OwnedString.initEmpty(allocator),
.label = OwnedString.initEmpty(allocator),
.outdir = OwnedString.initEmpty(allocator),
@@ -118,6 +121,7 @@ pub const JSBundler = struct {
if (hot.isBoolean()) {
this.minify.whitespace = hot.coerce(bool, globalThis);
this.minify.syntax = this.minify.whitespace;
+ this.minify.identifiers = this.minify.identifiers;
} else if (hot.isObject()) {
if (try hot.getOptional(globalThis, "whitespace", bool)) |whitespace| {
this.minify.whitespace = whitespace;
@@ -125,6 +129,9 @@ pub const JSBundler = struct {
if (try hot.getOptional(globalThis, "syntax", bool)) |syntax| {
this.minify.syntax = syntax;
}
+ if (try hot.getOptional(globalThis, "identifiers", bool)) |syntax| {
+ this.minify.identifiers = syntax;
+ }
} else {
globalThis.throwInvalidArguments("Expected minify to be a boolean or an object", .{});
return error.JSException;
@@ -146,6 +153,18 @@ pub const JSBundler = struct {
return error.JSException;
}
+ if (try config.getArray(globalThis, "external")) |externals| {
+ var iter = externals.arrayIterator(globalThis);
+ while (iter.next()) |entry_point| {
+ var slice = entry_point.toSliceOrNull(globalThis) orelse {
+ globalThis.throwInvalidArguments("Expected external to be an array of strings", .{});
+ return error.JSException;
+ };
+ defer slice.deinit();
+ try this.external.insert(slice.slice());
+ }
+ }
+
if (try config.getOptional(globalThis, "label", ZigString.Slice)) |slice| {
defer slice.deinit();
this.label.appendSliceExact(slice.slice()) catch unreachable;
@@ -272,6 +291,7 @@ pub const JSBundler = struct {
pub const Minify = struct {
whitespace: bool = false,
+ identifiers: bool = false,
syntax: bool = false,
};
@@ -288,6 +308,7 @@ pub const JSBundler = struct {
pub fn deinit(self: *Config, allocator: std.mem.Allocator) void {
self.entry_points.deinit();
+ self.external.deinit();
self.define.deinit();
self.dir.deinit();
self.serve.deinit(allocator);
@@ -303,11 +324,18 @@ pub const JSBundler = struct {
globalThis: *JSC.JSGlobalObject,
arguments: []const JSC.JSValue,
) JSC.JSValue {
- _ = Config.fromJS(globalThis, arguments[0], globalThis.allocator()) catch {
+ const config = Config.fromJS(globalThis, arguments[0], globalThis.allocator()) catch {
+ return JSC.JSValue.jsUndefined();
+ };
+
+ return bun.BundleV2.generateFromJavaScript(
+ config,
+ globalThis,
+ globalThis.bunVM().eventLoop(),
+ bun.default_allocator,
+ ) catch {
return JSC.JSValue.jsUndefined();
};
- globalThis.throw("Not implemented", .{});
- return JSC.JSValue.jsUndefined();
}
pub fn buildFn(
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index 83f36922a..ea7551b7f 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -152,6 +152,10 @@ pub const AnyTask = struct {
ctx: ?*anyopaque,
callback: *const (fn (*anyopaque) void),
+ pub fn task(this: *AnyTask) Task {
+ return Task.init(this);
+ }
+
pub fn run(this: *AnyTask) void {
@setRuntimeSafety(false);
var callback = this.callback;
@@ -827,18 +831,6 @@ pub const AnyEventLoop = union(enum) {
return .{ .mini = MiniEventLoop.init(allocator) };
}
- // pub fn enqueueTask(
- // this: *AnyEventLoop,
- // comptime Context: type,
- // ctx: *Context,
- // comptime Callback: fn (*Context) void,
- // comptime field: std.meta.FieldEnum(Context),
- // ) void {
- // const TaskType = MiniEventLoop.Task.New(Context, Callback);
- // @field(ctx, field) = TaskType.init(ctx);
- // this.enqueueTaskConcurrent(&@field(ctx, field));
- // }
-
pub fn tick(
this: *AnyEventLoop,
context: *anyopaque,
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index a3650755f..4ac48f25f 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -2518,6 +2518,14 @@ pub const Blob = struct {
return blob_.toJS(globalThis);
}
+ pub fn getMimeType(this: *const Blob) ?bun.HTTP.MimeType {
+ if (this.store) |store| {
+ return store.mime_type;
+ }
+
+ return null;
+ }
+
pub fn getType(
this: *Blob,
globalThis: *JSC.JSGlobalObject,
diff --git a/src/bun.zig b/src/bun.zig
index 1999e43d5..c6fce74de 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -1245,3 +1245,92 @@ pub fn reloadProcess(
pub var auto_reload_on_crash = false;
pub const options = @import("./options.zig");
+pub const StringSet = struct {
+ map: Map,
+
+ pub const Map = StringArrayHashMap(void);
+
+ pub fn init(allocator: std.mem.Allocator) StringSet {
+ return StringSet{
+ .map = Map.init(allocator),
+ };
+ }
+
+ pub fn keys(self: StringSet) []const string {
+ return self.map.keys();
+ }
+
+ pub fn insert(self: *StringSet, key: []const u8) !void {
+ var entry = try self.map.getOrPut(key);
+ if (!entry.found_existing) {
+ entry.key_ptr.* = try self.map.allocator.dupe(u8, key);
+ }
+ }
+
+ pub fn deinit(self: *StringSet) void {
+ for (self.map.keys()) |key| {
+ self.map.allocator.free(key);
+ }
+
+ self.map.deinit();
+ }
+};
+
+pub const Schema = @import("./api/schema.zig");
+
+pub const StringMap = struct {
+ map: Map,
+
+ pub const Map = StringArrayHashMap(string);
+
+ pub fn init(allocator: std.mem.Allocator) StringMap {
+ return StringMap{
+ .map = Map.init(allocator),
+ };
+ }
+
+ pub fn keys(self: StringMap) []const string {
+ return self.map.keys();
+ }
+
+ pub fn values(self: StringMap) []const string {
+ return self.map.values();
+ }
+
+ pub fn count(self: StringMap) usize {
+ return self.map.count();
+ }
+
+ pub fn toAPI(self: StringMap) Schema.Api.StringMap {
+ return Schema.Api.StringMap{
+ .keys = self.keys(),
+ .values = self.values(),
+ };
+ }
+
+ pub fn insert(self: *StringMap, key: []const u8, value: []const u8) !void {
+ var entry = try self.map.getOrPut(key);
+ if (!entry.found_existing) {
+ entry.key_ptr.* = try self.map.allocator.dupe(u8, key);
+ } else {
+ self.map.allocator.free(entry.value_ptr.*);
+ }
+
+ entry.value_ptr.* = try self.map.allocator.dupe(u8, value);
+ }
+
+ pub fn deinit(self: *StringMap) void {
+ for (self.map.values()) |value| {
+ self.map.allocator.free(value);
+ }
+
+ for (self.map.keys()) |key| {
+ self.map.allocator.free(key);
+ }
+
+ self.map.deinit();
+ }
+};
+
+pub const DotEnv = @import("./env_loader.zig");
+pub const BundleV2 = @import("./bundler/bundle_v2.zig").BundleV2;
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index dda7d9659..140229dc1 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -146,6 +146,11 @@ pub const ThreadPool = struct {
has_notify_started: bool = false,
has_created: bool = false,
+ pub fn deinit(this: *Worker) void {
+ this.data.deinit(this.allocator);
+ this.heap.deinit();
+ }
+
pub fn get() *Worker {
var worker = @ptrCast(
*ThreadPool.Worker,
@@ -545,6 +550,241 @@ pub const BundleV2 = struct {
return try this.linker.generateChunksInParallel(chunks);
}
+ pub fn generateFromJavaScript(
+ config: bun.JSC.API.JSBundler.Config,
+ globalThis: *JSC.JSGlobalObject,
+ event_loop: *bun.JSC.EventLoop,
+ allocator: std.mem.Allocator,
+ ) !bun.JSC.JSValue {
+ var completion = try allocator.create(JSBundleCompletionTask);
+ completion.* = JSBundleCompletionTask{
+ .config = config,
+ .jsc_event_loop = event_loop,
+ .promise = JSC.JSPromise.Strong.init(globalThis),
+ .globalThis = globalThis,
+ .ref = JSC.Ref.init(),
+ .env = globalThis.bunVM().bundler.env,
+ .log = Logger.Log.init(bun.default_allocator),
+ .task = JSBundleCompletionTask.TaskCompletion.init(completion),
+ };
+
+ // Ensure this exists before we spawn the thread to prevent any race
+ // conditions from creating two
+ _ = JSC.WorkPool.get();
+
+ var thread = try std.Thread.spawn(.{}, generateInNewThreadWrap, .{completion});
+ thread.detach();
+
+ completion.ref.ref(globalThis.bunVM());
+
+ return completion.promise.value();
+ }
+
+ pub const BuildResult = struct {
+ output_files: std.ArrayList(options.OutputFile),
+ };
+
+ pub const JSBundleCompletionTask = struct {
+ config: bun.JSC.API.JSBundler.Config,
+ jsc_event_loop: *bun.JSC.EventLoop,
+ task: bun.JSC.AnyTask,
+ globalThis: *JSC.JSGlobalObject,
+ promise: JSC.JSPromise.Strong,
+ ref: JSC.Ref = JSC.Ref.init(),
+ env: *bun.DotEnv.Loader,
+ log: Logger.Log,
+
+ result: Result = .{ .pending = {} },
+
+ pub const Result = union(enum) {
+ pending: void,
+ err: anyerror,
+ value: BuildResult,
+ };
+
+ pub const TaskCompletion = bun.JSC.AnyTask.New(JSBundleCompletionTask, onComplete);
+
+ pub fn onComplete(this: *JSBundleCompletionTask) void {
+ var globalThis = this.globalThis;
+
+ defer {
+ this.config.deinit(bun.default_allocator);
+ }
+
+ this.ref.unref(globalThis.bunVM());
+ const promise = this.promise.swap();
+ const root_obj = JSC.JSValue.createEmptyObject(globalThis, 2);
+
+ switch (this.result) {
+ .pending => unreachable,
+ .err => {
+ root_obj.put(
+ globalThis,
+ JSC.ZigString.static("outputs"),
+ JSC.JSValue.createEmptyArray(globalThis, 0),
+ );
+
+ root_obj.put(
+ globalThis,
+ JSC.ZigString.static("logs"),
+ this.log.toJS(globalThis, bun.default_allocator, "Errors while building"),
+ );
+ },
+ .value => |*build| {
+ var output_files: []options.OutputFile = build.output_files.items;
+ const output_files_js = JSC.JSValue.createEmptyArray(globalThis, output_files.len);
+ defer build.output_files.deinit();
+ for (output_files, 0..) |*output_file, i| {
+ var obj = JSC.JSValue.createEmptyObject(globalThis, 2);
+ obj.put(
+ globalThis,
+ JSC.ZigString.static("path"),
+ JSC.ZigString.fromUTF8(output_file.input.text).toValueGC(globalThis),
+ );
+
+ obj.put(
+ globalThis,
+ JSC.ZigString.static("result"),
+ output_file.toJS(globalThis),
+ );
+ output_files_js.putIndex(globalThis, @intCast(u32, i), obj);
+ }
+
+ root_obj.put(
+ globalThis,
+ JSC.ZigString.static("outputs"),
+ output_files_js,
+ );
+
+ root_obj.put(
+ globalThis,
+ JSC.ZigString.static("logs"),
+ this.log.toJS(globalThis, bun.default_allocator, "Errors while building"),
+ );
+ },
+ }
+
+ promise.resolve(globalThis, root_obj);
+ }
+ };
+
+ pub fn generateInNewThreadWrap(
+ completion: *JSBundleCompletionTask,
+ ) void {
+ Output.Source.configureNamedThread("Bundler");
+ generateInNewThread(completion) catch |err| {
+ completion.result = .{ .err = err };
+ var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch unreachable;
+ concurrent_task.* = JSC.ConcurrentTask{
+ .auto_delete = true,
+ .task = completion.task.task(),
+ .next = null,
+ };
+ completion.jsc_event_loop.enqueueTaskConcurrent(concurrent_task);
+ };
+ }
+
+ fn generateInNewThread(
+ completion: *JSBundleCompletionTask,
+ ) !void {
+ const allocator = bun.default_allocator;
+
+ const config = &completion.config;
+ var bundler = try allocator.create(bun.Bundler);
+ errdefer allocator.destroy(bundler);
+
+ bundler.* = try bun.Bundler.init(
+ allocator,
+ &completion.log,
+ Api.TransformOptions{
+ .define = if (config.define.count() > 0) config.define.toAPI() else null,
+ .entry_points = config.entry_points.keys(),
+ .platform = config.target.toAPI(),
+ .absolute_working_dir = if (config.dir.list.items.len > 0) config.dir.toOwnedSliceLeaky() else null,
+ .inject = &.{},
+ .external = config.external.keys(),
+ .main_fields = &.{},
+ .extension_order = &.{},
+ },
+ null,
+ completion.env,
+ );
+ bundler.options.jsx = config.jsx;
+ bundler.options.entry_names = config.names.entry_point.data;
+ bundler.options.output_dir = config.outdir.toOwnedSliceLeaky();
+ bundler.options.minify_syntax = config.minify.syntax;
+ bundler.options.minify_whitespace = config.minify.whitespace;
+ bundler.options.minify_identifiers = config.minify.identifiers;
+ bundler.options.inlining = config.minify.syntax;
+ bundler.options.sourcemap = config.sourcemap;
+
+ try bundler.configureDefines();
+ bundler.configureLinker();
+
+ bundler.resolver.opts = bundler.options;
+
+ var event_loop = try allocator.create(JSC.AnyEventLoop);
+ defer allocator.destroy(event_loop);
+
+ // Ensure uWS::Loop is initialized
+ _ = bun.uws.Loop.get().?;
+
+ var this = try BundleV2.init(bundler, allocator, JSC.AnyEventLoop.init(allocator), false, JSC.WorkPool.get());
+ defer this.deinit();
+
+ completion.result = .{
+ .value = .{
+ .output_files = try this.runFromJSInNewThread(config),
+ },
+ };
+
+ var concurrent_task = try bun.default_allocator.create(JSC.ConcurrentTask);
+ concurrent_task.* = JSC.ConcurrentTask{
+ .auto_delete = true,
+ .task = completion.task.task(),
+ .next = null,
+ };
+ completion.jsc_event_loop.enqueueTaskConcurrent(concurrent_task);
+ }
+
+ pub fn deinit(this: *BundleV2) void {
+ for (this.graph.pool.workers[0..this.graph.pool.workers_used.loadUnchecked()]) |*worker| {
+ worker.deinit();
+ }
+ this.graph.heap.deinit();
+ }
+
+ pub fn runFromJSInNewThread(this: *BundleV2, config: *const bun.JSC.API.JSBundler.Config) !std.ArrayList(options.OutputFile) {
+ if (this.bundler.log.errors > 0) {
+ return error.BuildFailed;
+ }
+
+ this.graph.pool.pool.schedule(try this.enqueueEntryPoints(config.entry_points.keys()));
+
+ // We must wait for all the parse tasks to complete, even if there are errors.
+ this.waitForParse();
+
+ if (this.bundler.log.errors > 0) {
+ return error.BuildFailed;
+ }
+
+ try this.cloneAST();
+
+ var chunks = try this.linker.link(
+ this,
+ this.graph.entry_points.items,
+ this.graph.use_directive_entry_points,
+ try this.findReachableFiles(),
+ std.crypto.random.int(u64),
+ );
+
+ if (this.bundler.log.errors > 0) {
+ return error.BuildFailed;
+ }
+
+ return try this.linker.generateChunksInParallel(chunks);
+ }
+
pub fn onParseTaskComplete(parse_result: *ParseTask.Result, this: *BundleV2) void {
var graph = &this.graph;
var batch = ThreadPoolLib.Batch{};
diff --git a/src/options.zig b/src/options.zig
index 48b580746..59b814894 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -661,6 +661,16 @@ pub const Loader = enum {
dataurl,
text,
+ pub fn toMimeType(this: Loader) bun.HTTP.MimeType {
+ return switch (this) {
+ .jsx, .js, .ts, .tsx => bun.HTTP.MimeType.javascript,
+ .css => bun.HTTP.MimeType.css,
+ .toml, .json => bun.HTTP.MimeType.json,
+ .wasm => bun.HTTP.MimeType.wasm,
+ else => bun.HTTP.MimeType.other,
+ };
+ }
+
pub const HashTable = bun.StringArrayHashMap(Loader);
pub fn canHaveSourceMap(this: Loader) bool {
@@ -1948,6 +1958,27 @@ pub const OutputFile = struct {
close_handle_on_complete: bool = false,
autowatch: bool = true,
+ pub fn toJS(this: FileOperation, globalObject: *JSC.JSGlobalObject, loader: Loader) JSC.JSValue {
+ var file_blob = JSC.WebCore.Blob.Store.initFile(
+ if (this.fd != 0) JSC.Node.PathOrFileDescriptor{
+ .fd = this.fd,
+ } else JSC.Node.PathOrFileDescriptor{
+ .path = JSC.Node.PathLike{ .string = bun.PathString.init(globalObject.allocator().dupe(u8, this.pathname) catch unreachable) },
+ },
+ loader.toMimeType(),
+ globalObject.allocator(),
+ ) catch |err| {
+ Output.panic("error: Unable to create file blob: \"{s}\"", .{@errorName(err)});
+ };
+
+ var blob = globalObject.allocator().create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(file_blob, globalObject);
+ blob.allocator = globalObject.allocator();
+ blob.content_type = loader.toMimeType().value;
+
+ return blob.toJS(globalObject);
+ }
+
pub fn fromFile(fd: FileDescriptorType, pathname: string) FileOperation {
return .{
.pathname = pathname,
@@ -2035,6 +2066,26 @@ pub const OutputFile = struct {
try bun.copyFile(fd_in, fd_out);
}
+
+ pub fn toJS(
+ this: *OutputFile,
+ globalObject: *JSC.JSGlobalObject,
+ ) bun.JSC.JSValue {
+ return switch (this.value) {
+ .pending => @panic("Unexpected pending output file"),
+ .noop => JSC.JSValue.undefined,
+ .move => this.value.move.toJS(globalObject, this.loader),
+ .copy => this.value.copy.toJS(globalObject, this.loader),
+ .buffer => |buffer| brk: {
+ var blob = globalObject.allocator().create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.init(@constCast(buffer), bun.default_allocator, globalObject);
+ blob.store.?.mime_type = this.loader.toMimeType();
+ blob.content_type = blob.store.?.mime_type.value;
+ blob.allocator = globalObject.allocator();
+ break :brk blob.toJS(globalObject);
+ },
+ };
+ }
};
pub const TransformResult = struct {