aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-05-14 06:13:39 -0700
committerGravatar GitHub <noreply@github.com> 2023-05-14 06:13:39 -0700
commit893f70fee4a62b7729abc17257aee89a2dce0069 (patch)
tree9a1da782efdadc8ab9869293f6fd9cd64e5b4ba7
parent7f25aa9e0864e95aad72ee85d475a03aee68bfb4 (diff)
downloadbun-893f70fee4a62b7729abc17257aee89a2dce0069.tar.gz
bun-893f70fee4a62b7729abc17257aee89a2dce0069.tar.zst
bun-893f70fee4a62b7729abc17257aee89a2dce0069.zip
Single-file standalone Bun executables (#2879)
* Add LIEF * Compile LIEF * Implement support for embedding files on macOS * proof of concept * Add zstd * Implement runtime support * Move some code around * Update .gitmodules * Upgrade zig https://github.com/ziglang/zig/pull/15278 * leftover * leftover * delete dead code * Fix extname * Revert "Upgrade zig" This reverts commit dd968f30bffb6c06e34302645a3a4468c957fb4e. * Revert "leftover" This reverts commit 7664de7686276cfba431103847d35b9270433dee. * Revert "leftover" This reverts commit 498005be06a8a1747d48824310e5a020b1f90d97. * various fixes * it works! * leftover * Make `zig build` a little faster * give up on code signing support * Support Linux & macOS * Finish removing LIEF * few more * Add zstd to list of deps * make it pretty --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--.gitmodules4
-rw-r--r--.vscode/settings.json14
-rw-r--r--Dockerfile28
-rw-r--r--Makefile9
-rw-r--r--build.zig136
-rw-r--r--docs/bundler/migration.md2
-rw-r--r--docs/project/licensing.md5
-rw-r--r--src/bun.js/javascript.zig88
-rw-r--r--src/bun.js/module_loader.zig10
-rw-r--r--src/bun.zig6
-rw-r--r--src/bun_js.zig83
-rw-r--r--src/bundler/bundle_v2.zig37
-rw-r--r--src/cli.zig70
-rw-r--r--src/cli/build_command.zig197
-rw-r--r--src/darwin_c.zig6
m---------src/deps/zstd0
-rw-r--r--src/deps/zstd.zig227
-rw-r--r--src/mdx/mdx_parser.zig1835
-rw-r--r--src/options.zig3
-rw-r--r--src/output.zig16
-rw-r--r--src/resolver/resolver.zig23
-rw-r--r--src/runtime.zig17
-rw-r--r--src/sourcemap/sourcemap.zig76
-rw-r--r--src/standalone_bun.zig480
-rw-r--r--src/string_builder.zig57
-rw-r--r--src/which.zig6
-rw-r--r--test/bundler/bundler_edgecase.test.ts2
-rw-r--r--test/bundler/expectBundled.ts2
28 files changed, 1415 insertions, 2024 deletions
diff --git a/.gitmodules b/.gitmodules
index c22446cbd..3f6263101 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -65,3 +65,7 @@ fetchRecurseSubmodules = false
[submodule "src/deps/c-ares"]
path = src/deps/c-ares
url = https://github.com/c-ares/c-ares.git
+[submodule "src/deps/zstd"]
+ path = src/deps/zstd
+ url = git@github.com:facebook/zstd.git
+ ignore = dirty \ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 6e3320ca3..6c8a24381 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -77,6 +77,7 @@
"src/deps/lol-html": true,
"src/deps/c-ares": true,
"src/deps/tinycc": true,
+ "src/deps/zstd": true,
"test/snippets/package-json-exports/_node_modules_copy": true
},
"C_Cpp.files.exclude": {
@@ -204,7 +205,18 @@
"compare": "cpp",
"concepts": "cpp",
"typeindex": "cpp",
- "__verbose_abort": "cpp"
+ "__verbose_abort": "cpp",
+ "__std_stream": "cpp",
+ "any": "cpp",
+ "charconv": "cpp",
+ "csignal": "cpp",
+ "format": "cpp",
+ "forward_list": "cpp",
+ "future": "cpp",
+ "regex": "cpp",
+ "span": "cpp",
+ "valarray": "cpp",
+ "codecvt": "cpp"
},
"cmake.configureOnOpen": false,
"C_Cpp.errorSquiggles": "enabled",
diff --git a/Dockerfile b/Dockerfile
index 6479c209c..dcce1ca4e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -503,6 +503,33 @@ ENV LIB_ICU_PATH=${WEBKIT_DIR}/lib
RUN --mount=type=cache,target=/ccache cd $BUN_DIR && make sqlite
+FROM bun-base as zstd
+
+ARG DEBIAN_FRONTEND
+ARG GITHUB_WORKSPACE
+ARG ZIG_PATH
+# Directory extracts to "bun-webkit"
+ARG WEBKIT_DIR
+ARG BUN_RELEASE_DIR
+ARG BUN_DEPS_OUT_DIR
+ARG BUN_DIR
+
+ARG CPU_TARGET
+ENV CPU_TARGET=${CPU_TARGET}
+
+ENV CCACHE_DIR=/ccache
+
+COPY Makefile ${BUN_DIR}/Makefile
+COPY src/deps/zstd ${BUN_DIR}/src/deps/zstd
+COPY .prettierrc.cjs ${BUN_DIR}/.prettierrc.cjs
+
+WORKDIR $BUN_DIR
+
+ENV JSC_BASE_DIR=${WEBKIT_DIR}
+ENV LIB_ICU_PATH=${WEBKIT_DIR}/lib
+
+RUN --mount=type=cache,target=/ccache cd $BUN_DIR && make zstd
+
FROM scratch as build_release_cpp
COPY --from=compile_cpp /tmp/*.o /
@@ -535,6 +562,7 @@ COPY --from=lolhtml ${BUN_DEPS_OUT_DIR}/*.a ${BUN_DEPS_OUT_DIR}/
COPY --from=mimalloc ${BUN_DEPS_OUT_DIR}/*.o ${BUN_DEPS_OUT_DIR}/
COPY --from=picohttp ${BUN_DEPS_OUT_DIR}/*.o ${BUN_DEPS_OUT_DIR}/
COPY --from=sqlite ${BUN_DEPS_OUT_DIR}/*.o ${BUN_DEPS_OUT_DIR}/
+COPY --from=zstd ${BUN_DEPS_OUT_DIR}/*.a ${BUN_DEPS_OUT_DIR}/
COPY --from=tinycc ${BUN_DEPS_OUT_DIR}/*.a ${BUN_DEPS_OUT_DIR}/
COPY --from=uws ${BUN_DEPS_OUT_DIR}/*.a ${BUN_DEPS_OUT_DIR}/
COPY --from=uws ${BUN_DEPS_OUT_DIR}/*.o ${BUN_DEPS_OUT_DIR}/
diff --git a/Makefile b/Makefile
index d2264ca7d..3806b2be5 100644
--- a/Makefile
+++ b/Makefile
@@ -344,7 +344,7 @@ LINUX_INCLUDE_DIRS := $(ALL_JSC_INCLUDE_DIRS) \
UWS_INCLUDE_DIR := -I$(BUN_DEPS_DIR)/uws/uSockets/src -I$(BUN_DEPS_DIR)/uws/src -I$(BUN_DEPS_DIR)
-INCLUDE_DIRS := $(UWS_INCLUDE_DIR) -I$(BUN_DEPS_DIR)/mimalloc/include -Isrc/napi -I$(BUN_DEPS_DIR)/boringssl/include -I$(BUN_DEPS_DIR)/c-ares/include
+INCLUDE_DIRS := $(UWS_INCLUDE_DIR) -I$(BUN_DEPS_DIR)/mimalloc/include -I$(BUN_DEPS_DIR)/zstd/include -Isrc/napi -I$(BUN_DEPS_DIR)/boringssl/include -I$(BUN_DEPS_DIR)/c-ares/include
ifeq ($(OS_NAME),linux)
@@ -452,6 +452,7 @@ ARCHIVE_FILES_WITHOUT_LIBCRYPTO = $(MINIMUM_ARCHIVE_FILES) \
-ltcc \
-lusockets \
-lcares \
+ -lzstd \
$(BUN_DEPS_OUT_DIR)/libuwsockets.o
ARCHIVE_FILES = $(ARCHIVE_FILES_WITHOUT_LIBCRYPTO)
@@ -636,6 +637,9 @@ compile-ffi-test:
sqlite:
+.PHONY: zstd
+zstd:
+ cd $(BUN_DEPS_DIR)/zstd && rm -rf build-cmake-debug && cmake $(CMAKE_FLAGS) -DZSTD_BUILD_STATIC=ON -B build-cmake-debug -S build/cmake -G Ninja && ninja -C build-cmake-debug && cp build-cmake-debug/lib/libzstd.a $(BUN_DEPS_OUT_DIR)/libzstd.a
.PHONY: libarchive
libarchive:
@@ -908,7 +912,6 @@ bun-codesign-release-local:
bun-codesign-release-local-debug:
-
.PHONY: jsc
jsc: jsc-build jsc-copy-headers jsc-bindings
.PHONY: jsc-build
@@ -1857,7 +1860,7 @@ cold-jsc-start:
misctools/cold-jsc-start.cpp -o cold-jsc-start
.PHONY: vendor-without-npm
-vendor-without-npm: node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp zlib boringssl libarchive lolhtml sqlite usockets uws tinycc c-ares
+vendor-without-npm: node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp zlib boringssl libarchive lolhtml sqlite usockets uws tinycc c-ares zstd
.PHONY: vendor-without-check
vendor-without-check: npm-install vendor-without-npm
diff --git a/build.zig b/build.zig
index a9de202ab..72588f6f4 100644
--- a/build.zig
+++ b/build.zig
@@ -71,6 +71,24 @@ const BunBuildOptions = struct {
sizegen: bool = false,
base_path: [:0]const u8 = "",
+ runtime_js_version: u64 = 0,
+ fallback_html_version: u64 = 0,
+
+ pub fn updateRuntime(this: *BunBuildOptions) anyerror!void {
+ var runtime_out_file = try std.fs.cwd().openFile("src/runtime.out.js", .{ .mode = .read_only });
+ const runtime_hash = std.hash.Wyhash.hash(
+ 0,
+ try runtime_out_file.readToEndAlloc(std.heap.page_allocator, try runtime_out_file.getEndPos()),
+ );
+ this.runtime_js_version = runtime_hash;
+ var fallback_out_file = try std.fs.cwd().openFile("src/fallback.out.js", .{ .mode = .read_only });
+ const fallback_hash = std.hash.Wyhash.hash(
+ 0,
+ try fallback_out_file.readToEndAlloc(std.heap.page_allocator, try fallback_out_file.getEndPos()),
+ );
+ this.fallback_html_version = fallback_hash;
+ }
+
pub fn step(this: BunBuildOptions, b: anytype) *std.build.OptionsStep {
var opts = b.addOptions();
opts.addOption(@TypeOf(this.canary), "is_canary", this.canary);
@@ -79,6 +97,8 @@ const BunBuildOptions = struct {
opts.addOption(@TypeOf(this.bindgen), "bindgen", this.bindgen);
opts.addOption(@TypeOf(this.sizegen), "sizegen", this.sizegen);
opts.addOption(@TypeOf(this.base_path), "base_path", this.base_path);
+ opts.addOption(@TypeOf(this.runtime_js_version), "runtime_js_version", this.runtime_js_version);
+ opts.addOption(@TypeOf(this.fallback_html_version), "fallback_html_version", this.fallback_html_version);
return opts;
}
};
@@ -105,28 +125,6 @@ const fmt = struct {
}
};
-fn updateRuntime() anyerror!void {
- var runtime_out_file = try std.fs.cwd().openFile("src/runtime.out.js", .{ .mode = .read_only });
- const runtime_hash = std.hash.Wyhash.hash(
- 0,
- try runtime_out_file.readToEndAlloc(std.heap.page_allocator, try runtime_out_file.getEndPos()),
- );
- const runtime_version_file = std.fs.cwd().createFile("src/runtime.version", .{ .truncate = true }) catch std.debug.panic("Failed to create src/runtime.version", .{});
- defer runtime_version_file.close();
- runtime_version_file.writer().print("{any}", .{fmt.hexInt(runtime_hash)}) catch unreachable;
- var fallback_out_file = try std.fs.cwd().openFile("src/fallback.out.js", .{ .mode = .read_only });
- const fallback_hash = std.hash.Wyhash.hash(
- 0,
- try fallback_out_file.readToEndAlloc(std.heap.page_allocator, try fallback_out_file.getEndPos()),
- );
-
- const fallback_version_file = std.fs.cwd().createFile("src/fallback.version", .{ .truncate = true }) catch std.debug.panic("Failed to create src/fallback.version", .{});
-
- fallback_version_file.writer().print("{any}", .{fmt.hexInt(fallback_hash)}) catch unreachable;
-
- fallback_version_file.close();
-}
-
var x64 = "x64";
var optimize: std.builtin.OptimizeMode = undefined;
@@ -194,8 +192,6 @@ pub fn build(b: *Build) !void {
else
"root.zig";
- updateRuntime() catch {};
-
const min_version: std.builtin.Version = if (target.getOsTag() != .freestanding)
target.getOsVersionMin().semver
else
@@ -271,6 +267,8 @@ pub fn build(b: *Build) !void {
obj.target.cpu_model = .{ .explicit = &std.Target.aarch64.cpu.generic };
}
+ try default_build_options.updateRuntime();
+
// we have to dump to stderr because stdout is read by zls
std.io.getStdErr().writer().print("Build {s} v{} - v{} ({s})\n", .{
triplet,
@@ -454,38 +452,9 @@ pub fn build(b: *Build) !void {
}
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
- try linkObjectFiles(b, headers_obj, target);
headers_step.dependOn(&headers_obj.step);
headers_obj.addOptions("build_options", default_build_options.step(b));
-
- // var iter = headers_obj.modules.iterator();
- // while (iter.next()) |item| {
- // const module = @ptrCast(*Module, item.value_ptr);
- // }
- // // while (headers_obj.modules.)
- // for (headers_obj.packages.items) |pkg_| {
- // const pkg: std.build.Pkg = pkg_;
- // if (std.mem.eql(u8, pkg.name, "clap")) continue;
- // var test_ = b.addTestSource(pkg.source);
-
- // b
- // .test_.setMainPkgPath(obj.main_pkg_path.?);
- // try configureObjectStep(b, test_, @TypeOf(target), target, obj.main_pkg_path.?);
- // try linkObjectFiles(b, test_, target);
- // test_.addOptions("build_options", default_build_options.step(b));
-
- // if (pkg.dependencies) |children| {
- // test_.packages = std.ArrayList(std.build.Pkg).init(b.allocator);
- // try test_.packages.appendSlice(children);
- // }
-
- // var before = b.addLog("\x1b[" ++ color_map.get("magenta").? ++ "\x1b[" ++ color_map.get("b").? ++ "[{s} tests]" ++ "\x1b[" ++ color_map.get("d").? ++ " ----\n\n" ++ "\x1b[0m", .{pkg.name});
- // var after = b.addLog("\x1b[" ++ color_map.get("d").? ++ "–––---\n\n" ++ "\x1b[0m", .{});
- // headers_step.dependOn(&before.step);
- // headers_step.dependOn(&test_.step);
- // headers_step.dependOn(&after.step);
- // }
}
b.default_step.dependOn(obj_step);
@@ -493,67 +462,6 @@ pub fn build(b: *Build) !void {
pub var original_make_fn: ?*const fn (step: *std.build.Step) anyerror!void = null;
-// Due to limitations in std.build.Builder
-// we cannot use this with debugging
-// so I am leaving this here for now, with the eventual intent to switch to std.build.Builder
-// but it is dead code
-pub fn linkObjectFiles(b: *Build, obj: *CompileStep, target: anytype) !void {
- if (target.getOsTag() == .freestanding)
- return;
- var dirs_to_search = std.BoundedArray([]const u8, 32).init(0) catch unreachable;
- const arm_brew_prefix: []const u8 = "/opt/homebrew";
- const x86_brew_prefix: []const u8 = "/usr/local";
- try dirs_to_search.append(b.env_map.get("BUN_DEPS_OUT_DIR") orelse b.env_map.get("BUN_DEPS_DIR") orelse @as([]const u8, b.pathFromRoot("src/deps")));
- if (target.getOsTag() == .macos) {
- if (target.getCpuArch().isAARCH64()) {
- try dirs_to_search.append(comptime arm_brew_prefix ++ "/opt/icu4c/lib/");
- } else {
- try dirs_to_search.append(comptime x86_brew_prefix ++ "/opt/icu4c/lib/");
- }
- }
-
- if (b.env_map.get("JSC_LIB")) |jsc| {
- try dirs_to_search.append(jsc);
- }
-
- var added = std.AutoHashMap(u64, void).init(b.allocator);
-
- const files_we_care_about = std.ComptimeStringMap([]const u8, .{
- .{ "libmimalloc.o", "libmimalloc.o" },
- .{ "libz.a", "libz.a" },
- .{ "libarchive.a", "libarchive.a" },
- .{ "libssl.a", "libssl.a" },
- .{ "picohttpparser.o", "picohttpparser.o" },
- .{ "libcrypto.boring.a", "libcrypto.boring.a" },
- .{ "libicuuc.a", "libicuuc.a" },
- .{ "libicudata.a", "libicudata.a" },
- .{ "libicui18n.a", "libicui18n.a" },
- .{ "libJavaScriptCore.a", "libJavaScriptCore.a" },
- .{ "libWTF.a", "libWTF.a" },
- .{ "libbmalloc.a", "libbmalloc.a" },
- .{ "liblolhtml.a", "liblolhtml.a" },
- .{ "uSockets.a", "uSockets.a" },
- });
-
- for (dirs_to_search.slice()) |deps_path| {
- var deps_dir = std.fs.cwd().openIterableDir(deps_path, .{}) catch continue;
- var iterator = deps_dir.iterate();
- obj.addIncludePath(deps_path);
- obj.addLibraryPath(deps_path);
-
- while (iterator.next() catch null) |entr| {
- const entry: std.fs.IterableDir.Entry = entr;
- if (files_we_care_about.get(entry.name)) |obj_name| {
- var has_added = try added.getOrPut(std.hash.Wyhash.hash(0, obj_name));
- if (!has_added.found_existing) {
- var paths = [_][]const u8{ deps_path, obj_name };
- obj.addObjectFile(try std.fs.path.join(b.allocator, &paths));
- }
- }
- }
- }
-}
-
pub fn configureObjectStep(b: *std.build.Builder, obj: *CompileStep, comptime Target: type, target: Target, main_pkg_path: []const u8) !void {
obj.setMainPkgPath(main_pkg_path);
diff --git a/docs/bundler/migration.md b/docs/bundler/migration.md
index 7b375fda5..e76d50103 100644
--- a/docs/bundler/migration.md
+++ b/docs/bundler/migration.md
@@ -35,7 +35,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
- `--bundle`
- n/a
-- Bun always bundles, use `--transpile` to disable this behavior.
+- Bun always bundles, use `--no-bundle` to disable this behavior.
---
diff --git a/docs/project/licensing.md b/docs/project/licensing.md
index ef88d0674..ea49acb1d 100644
--- a/docs/project/licensing.md
+++ b/docs/project/licensing.md
@@ -50,6 +50,11 @@ Bun statically links these libraries:
---
+- [`zstd`](https://github.com/facebook/zstd)
+- dual-licensed under the BSD License or GPLv2 license
+
+---
+
- [`simdutf`](https://github.com/simdutf/simdutf)
- Apache 2.0
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 8bf13314b..34825c3e3 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -361,6 +361,7 @@ pub const VirtualMachine = struct {
pending_unref_counter: i32 = 0,
preload: []const string = &[_][]const u8{},
unhandled_pending_rejection_to_capture: ?*JSC.JSValue = null,
+ standalone_module_graph: ?*bun.StandaloneModuleGraph = null,
hot_reload: bun.CLI.Command.HotReload = .none,
@@ -723,6 +724,92 @@ pub const VirtualMachine = struct {
return VMHolder.vm != null;
}
+ pub fn initWithModuleGraph(
+ allocator: std.mem.Allocator,
+ log: *logger.Log,
+ graph: *bun.StandaloneModuleGraph,
+ ) !*VirtualMachine {
+ VMHolder.vm = try allocator.create(VirtualMachine);
+ var console = try allocator.create(ZigConsoleClient);
+ console.* = ZigConsoleClient.init(Output.errorWriter(), Output.writer());
+ const bundler = try Bundler.init(
+ allocator,
+ log,
+ std.mem.zeroes(Api.TransformOptions),
+ null,
+ null,
+ );
+ var vm = VMHolder.vm.?;
+
+ vm.* = VirtualMachine{
+ .global = undefined,
+ .allocator = allocator,
+ .entry_point = ServerEntryPoint{},
+ .event_listeners = EventListenerMixin.Map.init(allocator),
+ .bundler = bundler,
+ .console = console,
+ .node_modules = bundler.options.node_modules_bundle,
+ .log = log,
+ .flush_list = std.ArrayList(string).init(allocator),
+ .blobs = null,
+ .origin = bundler.options.origin,
+ .saved_source_map_table = SavedSourceMap.HashTable.init(allocator),
+ .source_mappings = undefined,
+ .macros = MacroMap.init(allocator),
+ .macro_entry_points = @TypeOf(vm.macro_entry_points).init(allocator),
+ .origin_timer = std.time.Timer.start() catch @panic("Please don't mess with timers."),
+ .origin_timestamp = getOriginTimestamp(),
+ .ref_strings = JSC.RefString.Map.init(allocator),
+ .file_blobs = JSC.WebCore.Blob.Store.Map.init(allocator),
+ .standalone_module_graph = graph,
+ };
+ vm.source_mappings = .{ .map = &vm.saved_source_map_table };
+ vm.regular_event_loop.tasks = EventLoop.Queue.init(
+ default_allocator,
+ );
+ vm.regular_event_loop.tasks.ensureUnusedCapacity(64) catch unreachable;
+ vm.regular_event_loop.concurrent_tasks = .{};
+ vm.event_loop = &vm.regular_event_loop;
+
+ vm.bundler.macro_context = null;
+ vm.bundler.resolver.store_fd = false;
+
+ vm.bundler.resolver.onWakePackageManager = .{
+ .context = &vm.modules,
+ .handler = ModuleLoader.AsyncModule.Queue.onWakeHandler,
+ .onDependencyError = JSC.ModuleLoader.AsyncModule.Queue.onDependencyError,
+ };
+
+ vm.bundler.resolver.standalone_module_graph = graph;
+
+ // Avoid reading from tsconfig.json & package.json when we're in standalone mode
+ vm.bundler.configureLinkerWithAutoJSX(false);
+ try vm.bundler.configureFramework(false);
+
+ vm.bundler.macro_context = js_ast.Macro.MacroContext.init(&vm.bundler);
+
+ var global_classes: [GlobalClasses.len]js.JSClassRef = undefined;
+ inline for (GlobalClasses, 0..) |Class, i| {
+ global_classes[i] = Class.get().*;
+ }
+ vm.global = ZigGlobalObject.create(
+ &global_classes,
+ @intCast(i32, global_classes.len),
+ vm.console,
+ );
+ vm.regular_event_loop.global = vm.global;
+ vm.regular_event_loop.virtual_machine = vm;
+
+ if (source_code_printer == null) {
+ var writer = try js_printer.BufferWriter.init(allocator);
+ source_code_printer = allocator.create(js_printer.BufferPrinter) catch unreachable;
+ source_code_printer.?.* = js_printer.BufferPrinter.init(writer);
+ source_code_printer.?.ctx.append_null_byte = false;
+ }
+
+ return vm;
+ }
+
pub fn init(
allocator: std.mem.Allocator,
_args: Api.TransformOptions,
@@ -1196,6 +1283,7 @@ pub const VirtualMachine = struct {
res.* = ErrorableZigString.ok(ZigString.init(hardcoded.path));
return;
}
+
var old_log = jsc_vm.log;
var log = logger.Log.init(jsc_vm.allocator);
defer log.deinit();
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index 131ac5b59..6524c8084 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -2077,6 +2077,16 @@ pub const ModuleLoader = struct {
.source_url = ZigString.init(specifier),
.hash = 0,
};
+ } else if (jsc_vm.standalone_module_graph) |graph| {
+ if (graph.files.get(specifier)) |file| {
+ return ResolvedSource{
+ .allocator = null,
+ .source_code = ZigString.init(file.contents),
+ .specifier = ZigString.init(specifier),
+ .source_url = ZigString.init(specifier),
+ .hash = 0,
+ };
+ }
}
return null;
diff --git a/src/bun.zig b/src/bun.zig
index 6b8dffbfa..1520a179e 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -161,7 +161,7 @@ pub const fmt = struct {
try fmt.formatFloatDecimal(new_value / 1000.0, .{ .precision = 2 }, writer);
return writer.writeAll(" KB");
} else {
- try fmt.formatFloatDecimal(new_value, .{ .precision = if (std.math.approxEqAbs(f64, new_value, @trunc(new_value), 0.100)) @as(usize, 0) else @as(usize, 2) }, writer);
+ try fmt.formatFloatDecimal(new_value, .{ .precision = if (std.math.approxEqAbs(f64, new_value, @trunc(new_value), 0.100)) @as(usize, 1) else @as(usize, 2) }, writer);
}
const buf = switch (1000) {
@@ -1509,3 +1509,7 @@ pub fn openFileForPath(path_: [:0]const u8) !std.fs.File {
}
pub const Generation = u16;
+
+pub const zstd = @import("./deps/zstd.zig");
+pub const StringPointer = Schema.Api.StringPointer;
+pub const StandaloneModuleGraph = @import("./standalone_bun.zig").StandaloneModuleGraph;
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 5b1f73386..5a4eb4f8a 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -37,14 +37,94 @@ const VirtualMachine = JSC.VirtualMachine;
var run: Run = undefined;
pub const Run = struct {
- file: std.fs.File,
ctx: Command.Context,
vm: *VirtualMachine,
entry_path: string,
arena: Arena = undefined,
any_unhandled: bool = false,
+ pub fn bootStandalone(ctx_: Command.Context, entry_path: string, graph: bun.StandaloneModuleGraph) !void {
+ var ctx = ctx_;
+ JSC.markBinding(@src());
+ bun.JSC.initialize();
+
+ var graph_ptr = try bun.default_allocator.create(bun.StandaloneModuleGraph);
+ graph_ptr.* = graph;
+
+ js_ast.Expr.Data.Store.create(default_allocator);
+ js_ast.Stmt.Data.Store.create(default_allocator);
+ var arena = try Arena.init();
+
+ if (!ctx.debug.loaded_bunfig) {
+ try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", &ctx, .RunCommand);
+ }
+
+ run = .{
+ .vm = try VirtualMachine.initWithModuleGraph(arena.allocator(), ctx.log, graph_ptr),
+ .arena = arena,
+ .ctx = ctx,
+ .entry_path = entry_path,
+ };
+
+ var vm = run.vm;
+ var b = &vm.bundler;
+ vm.preload = ctx.preloads;
+ vm.argv = ctx.passthrough;
+ vm.arena = &run.arena;
+ vm.allocator = arena.allocator();
+
+ b.options.install = ctx.install;
+ b.resolver.opts.install = ctx.install;
+ b.resolver.opts.global_cache = ctx.debug.global_cache;
+ b.resolver.opts.prefer_offline_install = (ctx.debug.offline_mode_setting orelse .online) == .offline;
+ b.resolver.opts.prefer_latest_install = (ctx.debug.offline_mode_setting orelse .online) == .latest;
+ b.options.global_cache = b.resolver.opts.global_cache;
+ b.options.prefer_offline_install = b.resolver.opts.prefer_offline_install;
+ b.options.prefer_latest_install = b.resolver.opts.prefer_latest_install;
+ b.resolver.env_loader = b.env;
+
+ b.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
+ b.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
+ b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
+ b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
+
+ // b.options.minify_syntax = ctx.bundler_options.minify_syntax;
+
+ if (ctx.debug.macros) |macros| {
+ b.options.macro_remap = macros;
+ }
+
+ b.configureRouter(false) catch {
+ if (Output.enable_ansi_colors_stderr) {
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ } else {
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ }
+ Output.prettyErrorln("\n", .{});
+ Global.exit(1);
+ };
+ b.configureDefines() catch {
+ if (Output.enable_ansi_colors_stderr) {
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ } else {
+ vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ }
+ Output.prettyErrorln("\n", .{});
+ Global.exit(1);
+ };
+
+ AsyncHTTP.loadEnv(vm.allocator, vm.log, b.env);
+
+ vm.loadExtraEnv();
+ vm.is_main_thread = true;
+ JSC.VirtualMachine.is_main_thread_vm = true;
+
+ var callback = OpaqueWrap(Run, Run.start);
+ vm.global.vm().holdAPILock(&run, callback);
+ }
+
pub fn boot(ctx_: Command.Context, file: std.fs.File, entry_path: string) !void {
+ _ = file;
var ctx = ctx_;
JSC.markBinding(@src());
bun.JSC.initialize();
@@ -66,7 +146,6 @@ pub const Run = struct {
null,
ctx.debug.hot_reload != .none,
),
- .file = file,
.arena = arena,
.ctx = ctx,
.entry_path = entry_path,
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index 57adc904a..34d2780de 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -332,6 +332,7 @@ pub const BundleV2 = struct {
bun_watcher: ?*Watcher.Watcher = null,
plugins: ?*JSC.API.JSBundler.Plugin = null,
completion: ?*JSBundleCompletionTask = null,
+ source_code_length: usize = 0,
// There is a race condition where an onResolve plugin may schedule a task on the bundle thread before it's parsing task completes
resolve_tasks_waiting_for_import_source_index: std.AutoArrayHashMapUnmanaged(Index.Int, BabyList(struct { to_source_index: Index, import_record_index: u32 })) = .{},
@@ -993,6 +994,9 @@ pub const BundleV2 = struct {
event_loop: EventLoop,
unique_key: u64,
enable_reloading: bool,
+ reachable_files_count: *usize,
+ minify_duration: *u64,
+ source_code_size: *u64,
) !std.ArrayList(options.OutputFile) {
var this = try BundleV2.init(bundler, allocator, event_loop, enable_reloading, null, null);
this.unique_key = unique_key;
@@ -1009,6 +1013,9 @@ pub const BundleV2 = struct {
this.waitForParse();
+ minify_duration.* = @intCast(u64, @divTrunc(@truncate(i64, std.time.nanoTimestamp()) - @truncate(i64, bun.CLI.start_time), @as(i64, std.time.ns_per_ms)));
+ source_code_size.* = this.source_code_length;
+
if (this.graph.use_directive_entry_points.len > 0) {
if (this.bundler.log.msgs.items.len > 0) {
return error.BuildFailed;
@@ -1023,6 +1030,7 @@ pub const BundleV2 = struct {
}
const reachable_files = try this.findReachableFiles();
+ reachable_files_count.* = reachable_files.len -| 1; // - 1 for the runtime
try this.processFilesToCopy(reachable_files);
@@ -2098,6 +2106,10 @@ pub const BundleV2 = struct {
// Warning: this array may resize in this function call
// do not reuse it.
graph.input_files.items(.source)[result.source.index.get()] = result.source;
+ this.source_code_length += if (!result.source.index.isRuntime())
+ result.source.contents.len
+ else
+ @as(usize, 0);
graph.input_files.items(.unique_key_for_additional_file)[result.source.index.get()] = result.unique_key_for_additional_file;
graph.input_files.items(.content_hash_for_additional_file)[result.source.index.get()] = result.content_hash_for_additional_file;
@@ -8834,20 +8846,25 @@ const LinkerContext = struct {
if (root_path.len > 0) {
try c.writeOutputFilesToDisk(root_path, chunks, react_client_components_manifest, &output_files);
} else {
+
// In-memory build
for (chunks) |*chunk| {
+ var display_size: usize = 0;
+
const _code_result = if (c.options.source_maps != .none) chunk.intermediate_output.codeWithSourceMapShifts(
null,
c.parse_graph,
c.resolver.opts.public_path,
chunk,
chunks,
+ &display_size,
) else chunk.intermediate_output.code(
null,
c.parse_graph,
c.resolver.opts.public_path,
chunk,
chunks,
+ &display_size,
);
var code_result = _code_result catch @panic("Failed to allocate memory for output file");
@@ -8918,6 +8935,7 @@ const LinkerContext = struct {
.hash = chunk.isolated_hash,
.loader = .js,
.input_path = input_path,
+ .display_size = @truncate(u32, display_size),
.output_kind = if (chunk.entry_point.is_entry_point)
c.graph.files.items(.entry_point_kind)[chunk.entry_point.source_index].OutputKind()
else
@@ -9014,7 +9032,7 @@ const LinkerContext = struct {
};
}
}
-
+ var display_size: usize = 0;
const _code_result = if (c.options.source_maps != .none)
chunk.intermediate_output.codeWithSourceMapShifts(
code_allocator,
@@ -9022,6 +9040,7 @@ const LinkerContext = struct {
c.resolver.opts.public_path,
chunk,
chunks,
+ &display_size,
)
else
chunk.intermediate_output.code(
@@ -9030,6 +9049,7 @@ const LinkerContext = struct {
c.resolver.opts.public_path,
chunk,
chunks,
+ &display_size,
);
var code_result = _code_result catch @panic("Failed to allocate memory for output chunk");
@@ -9169,6 +9189,7 @@ const LinkerContext = struct {
else
null,
.size = @truncate(u32, code_result.buffer.len),
+ .display_size = @truncate(u32, display_size),
.data = .{
.saved = 0,
},
@@ -10635,6 +10656,7 @@ pub const Chunk = struct {
import_prefix: []const u8,
chunk: *Chunk,
chunks: []Chunk,
+ display_size: ?*usize,
) !CodeResult {
const additional_files = graph.input_files.items(.additional_files);
const unique_key_for_additional_files = graph.input_files.items(.unique_key_for_additional_file);
@@ -10678,6 +10700,10 @@ pub const Chunk = struct {
}
}
+ if (display_size) |amt| {
+ amt.* = count;
+ }
+
const debug_id_len = if (comptime FeatureFlags.source_map_debug_id)
std.fmt.count("\n//# debugId={}\n", .{bun.sourcemap.DebugIDFormatter{ .id = chunk.isolated_hash }})
else
@@ -10767,6 +10793,10 @@ pub const Chunk = struct {
const allocator = allocator_to_use orelse allocatorForSize(joiny.len);
+ if (display_size) |amt| {
+ amt.* = joiny.len;
+ }
+
const buffer = brk: {
if (comptime FeatureFlags.source_map_debug_id) {
// This comment must go before the //# sourceMappingURL comment
@@ -10801,6 +10831,7 @@ pub const Chunk = struct {
import_prefix: []const u8,
chunk: *Chunk,
chunks: []Chunk,
+ display_size: *usize,
) !CodeResult {
const additional_files = graph.input_files.items(.additional_files);
switch (this) {
@@ -10837,6 +10868,7 @@ pub const Chunk = struct {
}
}
+ display_size.* = count;
var total_buf = try (allocator_to_use orelse allocatorForSize(count)).alloc(u8, count);
var remain = total_buf;
@@ -10890,6 +10922,9 @@ pub const Chunk = struct {
.joiner => |joiner_| {
// TODO: make this safe
var joiny = joiner_;
+
+ display_size.* = joiny.len;
+
return .{
.buffer = try joiny.done((allocator_to_use orelse allocatorForSize(joiny.len))),
.shifts = &[_]sourcemap.SourceMapShifts{},
diff --git a/src/cli.zig b/src/cli.zig
index ca8208aa2..621f54fd2 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -89,6 +89,7 @@ fn invalidTarget(diag: *clap.Diagnostic, _target: []const u8) noreturn {
diag.report(Output.errorWriter(), error.InvalidTarget) catch {};
std.process.exit(1);
}
+
pub const Arguments = struct {
pub fn loader_resolver(in: string) !Api.Loader {
const option_loader = options.Loader.fromString(in) orelse return error.InvalidLoader;
@@ -198,14 +199,14 @@ pub const Arguments = struct {
clap.parseParam("--outfile <STR> Write to a file") catch unreachable,
clap.parseParam("--root <STR> Root directory used for multiple entry points") catch unreachable,
clap.parseParam("--splitting Enable code splitting") catch unreachable,
- // clap.parseParam("--manifest <STR> Write JSON manifest") catch unreachable,
- // clap.parseParam("--public-path <STR> A prefix to be appended to any import paths in bundled code") catch unreachable,
+ clap.parseParam("--public-path <STR> A prefix to be appended to any import paths in bundled code") catch unreachable,
clap.parseParam("--sourcemap <STR>? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable,
clap.parseParam("--entry-naming <STR> Customize entry point filenames. Defaults to \"[dir]/[name].[ext]\"") catch unreachable,
clap.parseParam("--chunk-naming <STR> Customize chunk filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable,
clap.parseParam("--asset-naming <STR> Customize asset filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable,
clap.parseParam("--server-components Enable React Server Components (experimental)") catch unreachable,
- clap.parseParam("--transpile Transpile file only, do not bundle") catch unreachable,
+ clap.parseParam("--no-bundle Transpile file only, do not bundle") catch unreachable,
+ clap.parseParam("--compile Generate a standalone Bun executable containing your bundled code") catch unreachable,
};
// TODO: update test completions
@@ -478,7 +479,11 @@ pub const Arguments = struct {
ctx.bundler_options.minify_identifiers = minify_flag or args.flag("--minify-identifiers");
if (cmd == .BuildCommand) {
- ctx.bundler_options.transform_only = args.flag("--transpile");
+ ctx.bundler_options.transform_only = args.flag("--no-bundle");
+
+ if (args.flag("--compile")) {
+ ctx.bundler_options.compile = true;
+ }
if (args.option("--outdir")) |outdir| {
if (outdir.len > 0) {
@@ -555,7 +560,14 @@ pub const Arguments = struct {
entry_points[0],
"build",
) or strings.eqlComptime(entry_points[0], "bun"))) {
- entry_points = entry_points[1..];
+ var out_entry = entry_points[1..];
+ for (entry_points, 0..) |entry, i| {
+ if (entry.len > 0) {
+ out_entry = out_entry[i..];
+ break;
+ }
+ }
+ entry_points = out_entry;
}
},
.DevCommand => {
@@ -661,34 +673,6 @@ pub const Arguments = struct {
opts.resolve = Api.ResolveMode.lazy;
- switch (comptime cmd) {
- .BuildCommand => {
- // if (args.option("--resolve")) |_resolve| {
- // switch (ResolveMatcher.match(_resolve)) {
- // ResolveMatcher.case("disable") => {
- // opts.resolve = Api.ResolveMode.disable;
- // },
- // ResolveMatcher.case("bundle") => {
- // opts.resolve = Api.ResolveMode.bundle;
- // },
- // ResolveMatcher.case("dev") => {
- // opts.resolve = Api.ResolveMode.dev;
- // },
- // ResolveMatcher.case("lazy") => {
- // opts.resolve = Api.ResolveMode.lazy;
- // },
- // else => {
- // diag.name.long = "--resolve";
- // diag.arg = _resolve;
- // try diag.report(Output.errorWriter(), error.InvalidResolveOption);
- // std.process.exit(1);
- // },
- // }
- // }
- },
- else => {},
- }
-
const TargetMatcher = strings.ExactSizeMatcher(8);
if (args.option("--target")) |_target| {
@@ -941,6 +925,8 @@ pub const Command = struct {
has_loaded_global_config: bool = false,
pub const BundlerOptions = struct {
+ compile: bool = false,
+
outdir: []const u8 = "",
outfile: []const u8 = "",
root_dir: []const u8 = "",
@@ -1119,6 +1105,24 @@ pub const Command = struct {
// _ = BunxCommand;
}
+ if (try bun.StandaloneModuleGraph.fromExecutable(bun.default_allocator)) |graph| {
+ var ctx = Command.Context{
+ .args = std.mem.zeroes(Api.TransformOptions),
+ .log = log,
+ .start_time = start_time,
+ .allocator = bun.default_allocator,
+ };
+
+ ctx.args.target = Api.Target.bun;
+
+ try @import("./bun_js.zig").Run.bootStandalone(
+ ctx,
+ graph.entryPoint().name,
+ graph,
+ );
+ return;
+ }
+
const tag = which();
switch (tag) {
diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig
index 354c481cc..3d2d9948a 100644
--- a/src/cli/build_command.zig
+++ b/src/cli/build_command.zig
@@ -36,23 +36,30 @@ var estimated_input_lines_of_code_: usize = undefined;
pub const BuildCommand = struct {
pub fn exec(
- ctx: Command.Context,
+ ctx_: Command.Context,
) !void {
Global.configureAllocator(.{ .long_running = true });
+ var ctx = ctx_;
var allocator = ctx.allocator;
var log = ctx.log;
estimated_input_lines_of_code_ = 0;
+ if (ctx.bundler_options.compile) {
+ // set this early so that externals are set up correctly and define is right
+ ctx.args.target = .bun;
+ }
var this_bundler = try bundler.Bundler.init(allocator, log, ctx.args, null, null);
this_bundler.options.source_map = options.SourceMapOption.fromApi(ctx.args.source_map);
this_bundler.resolver.opts.source_map = options.SourceMapOption.fromApi(ctx.args.source_map);
- if (this_bundler.options.source_map == .external and ctx.bundler_options.outdir.len == 0) {
+ if (this_bundler.options.source_map == .external and ctx.bundler_options.outdir.len == 0 and !ctx.bundler_options.compile) {
Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use an external source map without --outdir", .{});
Global.exit(1);
return;
}
+ var outfile = ctx.bundler_options.outfile;
+
this_bundler.options.entry_naming = ctx.bundler_options.entry_naming;
this_bundler.options.chunk_naming = ctx.bundler_options.chunk_naming;
this_bundler.options.asset_naming = ctx.bundler_options.asset_naming;
@@ -75,6 +82,59 @@ pub const BuildCommand = struct {
this_bundler.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
this_bundler.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
+ if (ctx.bundler_options.compile) {
+ if (ctx.bundler_options.code_splitting) {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use --compile with --splitting", .{});
+ Global.exit(1);
+ return;
+ }
+
+ if (this_bundler.options.entry_points.len > 1) {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> multiple entry points are not supported with --compile", .{});
+ Global.exit(1);
+ return;
+ }
+
+ if (ctx.bundler_options.outdir.len > 0) {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use --compile with --outdir", .{});
+ Global.exit(1);
+ return;
+ }
+
+ // 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/";
+
+ if (outfile.len == 0) {
+ outfile = std.fs.path.basename(this_bundler.options.entry_points[0]);
+ const ext = std.fs.path.extension(outfile);
+ if (ext.len > 0) {
+ outfile = outfile[0 .. outfile.len - ext.len];
+ }
+
+ if (strings.eqlComptime(outfile, "index")) {
+ outfile = std.fs.path.basename(std.fs.path.dirname(this_bundler.options.entry_points[0]) orelse "index");
+ }
+
+ if (strings.eqlComptime(outfile, "bun")) {
+ outfile = std.fs.path.basename(std.fs.path.dirname(this_bundler.options.entry_points[0]) orelse "bun");
+ }
+ }
+
+ // If argv[0] is "bun" or "bunx", we don't check if the binary is standalone
+ if (strings.eqlComptime(outfile, "bun") or strings.eqlComptime(outfile, "bunx")) {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use --compile with an output file named 'bun' because bun won't realize it's a standalone executable. Please choose a different name for --outfile", .{});
+ Global.exit(1);
+ return;
+ }
+
+ if (ctx.bundler_options.transform_only) {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> --compile does not support --no-bundle", .{});
+ Global.exit(1);
+ return;
+ }
+ }
+
if (this_bundler.options.entry_points.len > 1 and ctx.bundler_options.outdir.len == 0) {
Output.prettyErrorln("error: to use multiple entry points, specify --outdir", .{});
Global.exit(1);
@@ -150,6 +210,10 @@ pub const BuildCommand = struct {
return;
}
+ var reachable_file_count: usize = 0;
+ var minify_duration: u64 = 0;
+ var input_code_length: u64 = 0;
+
const output_files: []options.OutputFile = brk: {
if (ctx.bundler_options.transform_only) {
this_bundler.options.import_path_format = .relative;
@@ -182,6 +246,9 @@ pub const BuildCommand = struct {
bun.JSC.AnyEventLoop.init(ctx.allocator),
std.crypto.random.int(u64),
ctx.debug.hot_reload == .watch,
+ &reachable_file_count,
+ &minify_duration,
+ &input_code_length,
) catch |err| {
if (log.msgs.items.len > 0) {
try log.printForLogLevel(Output.errorWriter());
@@ -196,21 +263,24 @@ pub const BuildCommand = struct {
};
{
+ var write_summary = false;
{
dump: {
defer Output.flush();
var writer = Output.writer();
var output_dir = this_bundler.options.output_dir;
- if (ctx.bundler_options.outfile.len > 0 and output_files.len == 1 and output_files[0].value == .buffer) {
- output_dir = std.fs.path.dirname(ctx.bundler_options.outfile) orelse ".";
- output_files[0].path = std.fs.path.basename(ctx.bundler_options.outfile);
+ if (outfile.len > 0 and output_files.len == 1 and output_files[0].value == .buffer) {
+ output_dir = std.fs.path.dirname(outfile) orelse ".";
+ output_files[0].path = std.fs.path.basename(outfile);
}
- if (ctx.bundler_options.outfile.len == 0 and output_files.len == 1 and ctx.bundler_options.outdir.len == 0) {
- // if --transpile is passed, it won't have an output dir
- if (output_files[0].value == .buffer)
- try writer.writeAll(output_files[0].value.buffer.bytes);
- break :dump;
+ if (!ctx.bundler_options.compile) {
+ if (outfile.len == 0 and output_files.len == 1 and ctx.bundler_options.outdir.len == 0) {
+ // if --no-bundle is passed, it won't have an output dir
+ if (output_files[0].value == .buffer)
+ try writer.writeAll(output_files[0].value.buffer.bytes);
+ break :dump;
+ }
}
var root_path = output_dir;
@@ -237,6 +307,105 @@ pub const BuildCommand = struct {
);
}
+ if (ctx.bundler_options.compile) {
+ const bundled_end = std.time.nanoTimestamp();
+ const minified = this_bundler.options.minify_identifiers or this_bundler.options.minify_whitespace or this_bundler.options.minify_syntax;
+ const padding_buf = [_]u8{' '} ** 16;
+
+ const bundle_until_now = @divTrunc(@truncate(i64, bundled_end - bun.CLI.start_time), @as(i64, std.time.ns_per_ms));
+
+ const bundle_elapsed = if (minified)
+ bundle_until_now - @intCast(i64, @truncate(u63, minify_duration))
+ else
+ bundle_until_now;
+
+ const minified_digit_count: usize = switch (minify_duration) {
+ 0...9 => 3,
+ 10...99 => 2,
+ 100...999 => 1,
+ 1000...9999 => 0,
+ else => 0,
+ };
+ if (minified) {
+ Output.pretty("{s}", .{padding_buf[0..@intCast(usize, minified_digit_count)]});
+ Output.printElapsedStdoutTrim(@intToFloat(f64, minify_duration));
+ const output_size = brk: {
+ var total_size: u64 = 0;
+ for (output_files) |f| {
+ if (f.loader == .js) {
+ total_size += f.size_without_sourcemap;
+ }
+ }
+
+ break :brk total_size;
+ };
+ // this isn't an exact size
+ // we may inject sourcemaps or comments or import paths
+ const delta: i64 = @truncate(i64, @intCast(i65, input_code_length) - @intCast(i65, output_size));
+ if (delta > 1024) {
+ Output.prettyln(
+ " <green>minify<r> -{} <d>(estimate)<r>",
+ .{
+ bun.fmt.size(@intCast(usize, delta)),
+ },
+ );
+ } else if (-delta > 1024) {
+ Output.prettyln(
+ " <b>minify<r> +{} <d>(estimate)<r>",
+ .{
+ bun.fmt.size(@intCast(usize, -delta)),
+ },
+ );
+ } else {
+ Output.prettyln(" <b>minify<r>", .{});
+ }
+ }
+
+ const bundle_elapsed_digit_count: usize = switch (bundle_elapsed) {
+ 0...9 => 3,
+ 10...99 => 2,
+ 100...999 => 1,
+ 1000...9999 => 0,
+ else => 0,
+ };
+
+ Output.pretty("{s}", .{padding_buf[0..@intCast(usize, bundle_elapsed_digit_count)]});
+ Output.printElapsedStdoutTrim(@intToFloat(f64, bundle_elapsed));
+ Output.prettyln(
+ " <green>bundle<r> {d} modules",
+ .{
+ reachable_file_count,
+ },
+ );
+
+ Output.flush();
+ try bun.StandaloneModuleGraph.toExecutable(
+ allocator,
+ output_files,
+ root_dir,
+ this_bundler.options.public_path,
+ outfile,
+ );
+ const compiled_elapsed = @divTrunc(@truncate(i64, std.time.nanoTimestamp() - bundled_end), @as(i64, std.time.ns_per_ms));
+ const compiled_elapsed_digit_count: isize = switch (compiled_elapsed) {
+ 0...9 => 3,
+ 10...99 => 2,
+ 100...999 => 1,
+ 1000...9999 => 0,
+ else => 0,
+ };
+
+ Output.pretty("{s}", .{padding_buf[0..@intCast(usize, compiled_elapsed_digit_count)]});
+
+ Output.printElapsedStdoutTrim(@intToFloat(f64, compiled_elapsed));
+
+ Output.prettyln(" <green>compile<r> <b><blue>{s}<r>", .{
+ outfile,
+ });
+
+ break :dump;
+ }
+
// On posix, file handles automatically close on process exit by the OS
// Closing files shows up in profiling.
// So don't do that unless we actually need to.
@@ -268,6 +437,7 @@ pub const BuildCommand = struct {
}
}
}
+
try root_dir.dir.writeFile(rel_path, value.bytes);
},
.move => |value| {
@@ -297,8 +467,15 @@ pub const BuildCommand = struct {
try std.fmt.formatFloatDecimal(size, .{ .precision = 2 }, writer);
try writer.writeAll(" KB\n");
}
+
+ write_summary = true;
+ }
+ if (write_summary) {
+ Output.printStartEndStdout(bun.CLI.start_time, std.time.nanoTimestamp());
+ Output.prettyln(" <green>Build<r>", .{});
}
}
+
try log.printForLogLevel(Output.errorWriter());
exitOrWatch(0, ctx.debug.hot_reload == .watch);
}
diff --git a/src/darwin_c.zig b/src/darwin_c.zig
index ab5fb13f7..bd6c3c9ef 100644
--- a/src/darwin_c.zig
+++ b/src/darwin_c.zig
@@ -67,11 +67,11 @@ pub const COPYFILE_SKIP = @as(c_int, 1);
pub const COPYFILE_QUIT = @as(c_int, 2);
// int clonefileat(int src_dirfd, const char * src, int dst_dirfd, const char * dst, int flags);
-pub extern "c" fn clonefileat(c_int, [*c]const u8, c_int, [*c]const u8, uint32_t: c_int) c_int;
+pub extern "c" fn clonefileat(c_int, [*:0]const u8, c_int, [*:0]const u8, uint32_t: c_int) c_int;
// int fclonefileat(int srcfd, int dst_dirfd, const char * dst, int flags);
-pub extern "c" fn fclonefileat(c_int, c_int, [*c]const u8, uint32_t: c_int) c_int;
+pub extern "c" fn fclonefileat(c_int, c_int, [*:0]const u8, uint32_t: c_int) c_int;
// int clonefile(const char * src, const char * dst, int flags);
-pub extern "c" fn clonefile([*c]const u8, [*c]const u8, uint32_t: c_int) c_int;
+pub extern "c" fn clonefile(src: [*:0]const u8, dest: [*:0]const u8, flags: c_int) c_int;
// pub fn stat_absolute(path: [:0]const u8) StatError!Stat {
// if (builtin.os.tag == .windows) {
diff --git a/src/deps/zstd b/src/deps/zstd
new file mode 160000
+Subproject 63779c798237346c2b245c546c40b72a5a5913f
diff --git a/src/deps/zstd.zig b/src/deps/zstd.zig
new file mode 100644
index 000000000..b8bb7e93b
--- /dev/null
+++ b/src/deps/zstd.zig
@@ -0,0 +1,227 @@
+pub extern fn ZSTD_versionNumber() c_uint;
+pub extern fn ZSTD_versionString() [*c]const u8;
+pub extern fn ZSTD_compress(dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, compressionLevel: c_int) usize;
+pub extern fn ZSTD_decompress(dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, compressedSize: usize) usize;
+pub extern fn ZSTD_getFrameContentSize(src: ?*const anyopaque, srcSize: usize) c_ulonglong;
+pub extern fn ZSTD_getDecompressedSize(src: ?*const anyopaque, srcSize: usize) c_ulonglong;
+pub extern fn ZSTD_findFrameCompressedSize(src: ?*const anyopaque, srcSize: usize) usize;
+pub extern fn ZSTD_compressBound(srcSize: usize) usize;
+pub extern fn ZSTD_isError(code: usize) c_uint;
+pub extern fn ZSTD_getErrorName(code: usize) [*:0]const u8;
+pub extern fn ZSTD_minCLevel() c_int;
+pub extern fn ZSTD_maxCLevel() c_int;
+pub extern fn ZSTD_defaultCLevel() c_int;
+pub const struct_ZSTD_CCtx_s = opaque {};
+pub const ZSTD_CCtx = struct_ZSTD_CCtx_s;
+pub extern fn ZSTD_createCCtx() ?*ZSTD_CCtx;
+pub extern fn ZSTD_freeCCtx(cctx: ?*ZSTD_CCtx) usize;
+pub extern fn ZSTD_compressCCtx(cctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, compressionLevel: c_int) usize;
+pub const struct_ZSTD_DCtx_s = opaque {};
+pub const ZSTD_DCtx = struct_ZSTD_DCtx_s;
+pub extern fn ZSTD_createDCtx() ?*ZSTD_DCtx;
+pub extern fn ZSTD_freeDCtx(dctx: ?*ZSTD_DCtx) usize;
+pub extern fn ZSTD_decompressDCtx(dctx: ?*ZSTD_DCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize) usize;
+pub const ZSTD_fast: c_int = 1;
+pub const ZSTD_dfast: c_int = 2;
+pub const ZSTD_greedy: c_int = 3;
+pub const ZSTD_lazy: c_int = 4;
+pub const ZSTD_lazy2: c_int = 5;
+pub const ZSTD_btlazy2: c_int = 6;
+pub const ZSTD_btopt: c_int = 7;
+pub const ZSTD_btultra: c_int = 8;
+pub const ZSTD_btultra2: c_int = 9;
+pub const ZSTD_strategy = c_uint;
+pub const ZSTD_c_compressionLevel: c_int = 100;
+pub const ZSTD_c_windowLog: c_int = 101;
+pub const ZSTD_c_hashLog: c_int = 102;
+pub const ZSTD_c_chainLog: c_int = 103;
+pub const ZSTD_c_searchLog: c_int = 104;
+pub const ZSTD_c_minMatch: c_int = 105;
+pub const ZSTD_c_targetLength: c_int = 106;
+pub const ZSTD_c_strategy: c_int = 107;
+pub const ZSTD_c_enableLongDistanceMatching: c_int = 160;
+pub const ZSTD_c_ldmHashLog: c_int = 161;
+pub const ZSTD_c_ldmMinMatch: c_int = 162;
+pub const ZSTD_c_ldmBucketSizeLog: c_int = 163;
+pub const ZSTD_c_ldmHashRateLog: c_int = 164;
+pub const ZSTD_c_contentSizeFlag: c_int = 200;
+pub const ZSTD_c_checksumFlag: c_int = 201;
+pub const ZSTD_c_dictIDFlag: c_int = 202;
+pub const ZSTD_c_nbWorkers: c_int = 400;
+pub const ZSTD_c_jobSize: c_int = 401;
+pub const ZSTD_c_overlapLog: c_int = 402;
+pub const ZSTD_c_experimentalParam1: c_int = 500;
+pub const ZSTD_c_experimentalParam2: c_int = 10;
+pub const ZSTD_c_experimentalParam3: c_int = 1000;
+pub const ZSTD_c_experimentalParam4: c_int = 1001;
+pub const ZSTD_c_experimentalParam5: c_int = 1002;
+pub const ZSTD_c_experimentalParam6: c_int = 1003;
+pub const ZSTD_c_experimentalParam7: c_int = 1004;
+pub const ZSTD_c_experimentalParam8: c_int = 1005;
+pub const ZSTD_c_experimentalParam9: c_int = 1006;
+pub const ZSTD_c_experimentalParam10: c_int = 1007;
+pub const ZSTD_c_experimentalParam11: c_int = 1008;
+pub const ZSTD_c_experimentalParam12: c_int = 1009;
+pub const ZSTD_c_experimentalParam13: c_int = 1010;
+pub const ZSTD_c_experimentalParam14: c_int = 1011;
+pub const ZSTD_c_experimentalParam15: c_int = 1012;
+pub const ZSTD_c_experimentalParam16: c_int = 1013;
+pub const ZSTD_c_experimentalParam17: c_int = 1014;
+pub const ZSTD_c_experimentalParam18: c_int = 1015;
+pub const ZSTD_c_experimentalParam19: c_int = 1016;
+pub const ZSTD_cParameter = c_uint;
+pub const ZSTD_bounds = extern struct {
+ @"error": usize,
+ lowerBound: c_int,
+ upperBound: c_int,
+};
+pub extern fn ZSTD_cParam_getBounds(cParam: ZSTD_cParameter) ZSTD_bounds;
+pub extern fn ZSTD_CCtx_setParameter(cctx: ?*ZSTD_CCtx, param: ZSTD_cParameter, value: c_int) usize;
+pub extern fn ZSTD_CCtx_setPledgedSrcSize(cctx: ?*ZSTD_CCtx, pledgedSrcSize: c_ulonglong) usize;
+pub const ZSTD_reset_session_only: c_int = 1;
+pub const ZSTD_reset_parameters: c_int = 2;
+pub const ZSTD_reset_session_and_parameters: c_int = 3;
+pub const ZSTD_ResetDirective = c_uint;
+pub extern fn ZSTD_CCtx_reset(cctx: ?*ZSTD_CCtx, reset: ZSTD_ResetDirective) usize;
+pub extern fn ZSTD_compress2(cctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize) usize;
+pub const ZSTD_d_windowLogMax: c_int = 100;
+pub const ZSTD_d_experimentalParam1: c_int = 1000;
+pub const ZSTD_d_experimentalParam2: c_int = 1001;
+pub const ZSTD_d_experimentalParam3: c_int = 1002;
+pub const ZSTD_d_experimentalParam4: c_int = 1003;
+pub const ZSTD_d_experimentalParam5: c_int = 1004;
+pub const ZSTD_dParameter = c_uint;
+pub extern fn ZSTD_dParam_getBounds(dParam: ZSTD_dParameter) ZSTD_bounds;
+pub extern fn ZSTD_DCtx_setParameter(dctx: ?*ZSTD_DCtx, param: ZSTD_dParameter, value: c_int) usize;
+pub extern fn ZSTD_DCtx_reset(dctx: ?*ZSTD_DCtx, reset: ZSTD_ResetDirective) usize;
+pub const struct_ZSTD_inBuffer_s = extern struct {
+ src: ?*const anyopaque,
+ size: usize,
+ pos: usize,
+};
+pub const ZSTD_inBuffer = struct_ZSTD_inBuffer_s;
+pub const struct_ZSTD_outBuffer_s = extern struct {
+ dst: ?*anyopaque,
+ size: usize,
+ pos: usize,
+};
+pub const ZSTD_outBuffer = struct_ZSTD_outBuffer_s;
+pub const ZSTD_CStream = ZSTD_CCtx;
+pub extern fn ZSTD_createCStream() ?*ZSTD_CStream;
+pub extern fn ZSTD_freeCStream(zcs: ?*ZSTD_CStream) usize;
+pub const ZSTD_e_continue: c_int = 0;
+pub const ZSTD_e_flush: c_int = 1;
+pub const ZSTD_e_end: c_int = 2;
+pub const ZSTD_EndDirective = c_uint;
+pub extern fn ZSTD_compressStream2(cctx: ?*ZSTD_CCtx, output: [*c]ZSTD_outBuffer, input: [*c]ZSTD_inBuffer, endOp: ZSTD_EndDirective) usize;
+pub extern fn ZSTD_CStreamInSize() usize;
+pub extern fn ZSTD_CStreamOutSize() usize;
+pub extern fn ZSTD_initCStream(zcs: ?*ZSTD_CStream, compressionLevel: c_int) usize;
+pub extern fn ZSTD_compressStream(zcs: ?*ZSTD_CStream, output: [*c]ZSTD_outBuffer, input: [*c]ZSTD_inBuffer) usize;
+pub extern fn ZSTD_flushStream(zcs: ?*ZSTD_CStream, output: [*c]ZSTD_outBuffer) usize;
+pub extern fn ZSTD_endStream(zcs: ?*ZSTD_CStream, output: [*c]ZSTD_outBuffer) usize;
+pub const ZSTD_DStream = ZSTD_DCtx;
+pub extern fn ZSTD_createDStream() ?*ZSTD_DStream;
+pub extern fn ZSTD_freeDStream(zds: ?*ZSTD_DStream) usize;
+pub extern fn ZSTD_initDStream(zds: ?*ZSTD_DStream) usize;
+pub extern fn ZSTD_decompressStream(zds: ?*ZSTD_DStream, output: [*c]ZSTD_outBuffer, input: [*c]ZSTD_inBuffer) usize;
+pub extern fn ZSTD_DStreamInSize() usize;
+pub extern fn ZSTD_DStreamOutSize() usize;
+pub extern fn ZSTD_compress_usingDict(ctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, dict: ?*const anyopaque, dictSize: usize, compressionLevel: c_int) usize;
+pub extern fn ZSTD_decompress_usingDict(dctx: ?*ZSTD_DCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, dict: ?*const anyopaque, dictSize: usize) usize;
+pub const struct_ZSTD_CDict_s = opaque {};
+pub const ZSTD_CDict = struct_ZSTD_CDict_s;
+pub extern fn ZSTD_createCDict(dictBuffer: ?*const anyopaque, dictSize: usize, compressionLevel: c_int) ?*ZSTD_CDict;
+pub extern fn ZSTD_freeCDict(CDict: ?*ZSTD_CDict) usize;
+pub extern fn ZSTD_compress_usingCDict(cctx: ?*ZSTD_CCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, cdict: ?*const ZSTD_CDict) usize;
+pub const struct_ZSTD_DDict_s = opaque {};
+pub const ZSTD_DDict = struct_ZSTD_DDict_s;
+pub extern fn ZSTD_createDDict(dictBuffer: ?*const anyopaque, dictSize: usize) ?*ZSTD_DDict;
+pub extern fn ZSTD_freeDDict(ddict: ?*ZSTD_DDict) usize;
+pub extern fn ZSTD_decompress_usingDDict(dctx: ?*ZSTD_DCtx, dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, ddict: ?*const ZSTD_DDict) usize;
+pub extern fn ZSTD_getDictID_fromDict(dict: ?*const anyopaque, dictSize: usize) c_uint;
+pub extern fn ZSTD_getDictID_fromCDict(cdict: ?*const ZSTD_CDict) c_uint;
+pub extern fn ZSTD_getDictID_fromDDict(ddict: ?*const ZSTD_DDict) c_uint;
+pub extern fn ZSTD_getDictID_fromFrame(src: ?*const anyopaque, srcSize: usize) c_uint;
+pub extern fn ZSTD_CCtx_loadDictionary(cctx: ?*ZSTD_CCtx, dict: ?*const anyopaque, dictSize: usize) usize;
+pub extern fn ZSTD_CCtx_refCDict(cctx: ?*ZSTD_CCtx, cdict: ?*const ZSTD_CDict) usize;
+pub extern fn ZSTD_CCtx_refPrefix(cctx: ?*ZSTD_CCtx, prefix: ?*const anyopaque, prefixSize: usize) usize;
+pub extern fn ZSTD_DCtx_loadDictionary(dctx: ?*ZSTD_DCtx, dict: ?*const anyopaque, dictSize: usize) usize;
+pub extern fn ZSTD_DCtx_refDDict(dctx: ?*ZSTD_DCtx, ddict: ?*const ZSTD_DDict) usize;
+pub extern fn ZSTD_DCtx_refPrefix(dctx: ?*ZSTD_DCtx, prefix: ?*const anyopaque, prefixSize: usize) usize;
+pub extern fn ZSTD_sizeof_CCtx(cctx: ?*const ZSTD_CCtx) usize;
+pub extern fn ZSTD_sizeof_DCtx(dctx: ?*const ZSTD_DCtx) usize;
+pub extern fn ZSTD_sizeof_CStream(zcs: ?*const ZSTD_CStream) usize;
+pub extern fn ZSTD_sizeof_DStream(zds: ?*const ZSTD_DStream) usize;
+pub extern fn ZSTD_sizeof_CDict(cdict: ?*const ZSTD_CDict) usize;
+pub extern fn ZSTD_sizeof_DDict(ddict: ?*const ZSTD_DDict) usize;
+pub const ZSTD_VERSION_MAJOR = @as(c_int, 1);
+pub const ZSTD_VERSION_MINOR = @as(c_int, 5);
+pub const ZSTD_VERSION_RELEASE = @as(c_int, 5);
+pub const ZSTD_VERSION_NUMBER = (((ZSTD_VERSION_MAJOR * @as(c_int, 100)) * @as(c_int, 100)) + (ZSTD_VERSION_MINOR * @as(c_int, 100))) + ZSTD_VERSION_RELEASE;
+pub const ZSTD_LIB_VERSION = ZSTD_VERSION_MAJOR.ZSTD_VERSION_MINOR.ZSTD_VERSION_RELEASE;
+pub const ZSTD_CLEVEL_DEFAULT = @as(c_int, 3);
+pub const ZSTD_MAGICNUMBER = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xFD2FB528, .hexadecimal);
+pub const ZSTD_MAGIC_DICTIONARY = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xEC30A437, .hexadecimal);
+pub const ZSTD_MAGIC_SKIPPABLE_START = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x184D2A50, .hexadecimal);
+pub const ZSTD_MAGIC_SKIPPABLE_MASK = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xFFFFFFF0, .hexadecimal);
+pub const ZSTD_BLOCKSIZELOG_MAX = @as(c_int, 17);
+pub const ZSTD_BLOCKSIZE_MAX = @as(c_int, 1) << ZSTD_BLOCKSIZELOG_MAX;
+pub const ZSTD_CONTENTSIZE_UNKNOWN = @as(c_ulonglong, 0) - @as(c_int, 1);
+pub const ZSTD_CONTENTSIZE_ERROR = @as(c_ulonglong, 0) - @as(c_int, 2);
+pub const ZSTD_MAX_INPUT_SIZE = if (@import("std").zig.c_translation.sizeof(usize) == @as(c_int, 8)) @as(c_ulonglong, 0xFF00FF00FF00FF00) else @import("std").zig.c_translation.promoteIntLiteral(c_uint, 0xFF00FF00, .hexadecimal);
+pub inline fn ZSTD_COMPRESSBOUND(srcSize: anytype) @TypeOf(if (@import("std").zig.c_translation.cast(usize, srcSize) >= ZSTD_MAX_INPUT_SIZE) @as(c_int, 0) else (srcSize + (srcSize >> @as(c_int, 8))) + (if (srcSize < (@as(c_int, 128) << @as(c_int, 10))) ((@as(c_int, 128) << @as(c_int, 10)) - srcSize) >> @as(c_int, 11) else @as(c_int, 0))) {
+ return if (@import("std").zig.c_translation.cast(usize, srcSize) >= ZSTD_MAX_INPUT_SIZE) @as(c_int, 0) else (srcSize + (srcSize >> @as(c_int, 8))) + (if (srcSize < (@as(c_int, 128) << @as(c_int, 10))) ((@as(c_int, 128) << @as(c_int, 10)) - srcSize) >> @as(c_int, 11) else @as(c_int, 0));
+}
+pub const ZSTD_CCtx_s = struct_ZSTD_CCtx_s;
+pub const ZSTD_DCtx_s = struct_ZSTD_DCtx_s;
+pub const ZSTD_inBuffer_s = struct_ZSTD_inBuffer_s;
+pub const ZSTD_outBuffer_s = struct_ZSTD_outBuffer_s;
+pub const ZSTD_CDict_s = struct_ZSTD_CDict_s;
+pub const ZSTD_DDict_s = struct_ZSTD_DDict_s;
+
+// -----------------------------------
+
+/// ZSTD_compress() :
+/// Compresses `src` content as a single zstd compressed frame into already allocated `dst`.
+/// NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have
+/// enough space to successfully compress the data.
+/// @return : compressed size written into `dst` (<= `dstCapacity),
+/// or an error code if it fails (which can be tested using ZSTD_isError()). */
+// ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity,
+// const void* src, size_t srcSize,
+// int compressionLevel);
+pub fn compress(dest: []u8, src: []const u8, level: ?i32) Result {
+ const result = ZSTD_compress(dest.ptr, dest.len, src.ptr, src.len, level orelse ZSTD_defaultCLevel());
+ if (ZSTD_isError(result) != 0) return .{ .err = bun.sliceTo(ZSTD_getErrorName(result), 0) };
+ return .{ .success = result };
+}
+
+pub fn compressBound(srcSize: usize) usize {
+ return ZSTD_compressBound(srcSize);
+}
+
+/// ZSTD_decompress() :
+/// `compressedSize` : must be the _exact_ size of some number of compressed and/or skippable frames.
+/// `dstCapacity` is an upper bound of originalSize to regenerate.
+/// If user cannot imply a maximum upper bound, it's better to use streaming mode to decompress data.
+/// @return : the number of bytes decompressed into `dst` (<= `dstCapacity`),
+/// or an errorCode if it fails (which can be tested using ZSTD_isError()). */
+// ZSTDLIB_API size_t ZSTD_decompress( void* dst, size_t dstCapacity,
+// const void* src, size_t compressedSize);
+pub fn decompress(dest: []u8, src: []const u8) Result {
+ const result = ZSTD_decompress(dest.ptr, dest.len, src.ptr, src.len);
+ if (ZSTD_isError(result) != 0) return .{ .err = bun.sliceTo(ZSTD_getErrorName(result), 0) };
+ return .{ .success = result };
+}
+
+pub fn getDecompressedSize(src: []const u8) usize {
+ return ZSTD_getDecompressedSize(src.ptr, src.len);
+}
+
+pub const Result = union(enum) {
+ success: usize,
+ err: [:0]const u8,
+};
+
+const bun = @import("root").bun;
diff --git a/src/mdx/mdx_parser.zig b/src/mdx/mdx_parser.zig
deleted file mode 100644
index b3dcdb91f..000000000
--- a/src/mdx/mdx_parser.zig
+++ /dev/null
@@ -1,1835 +0,0 @@
-const std = @import("std");
-const logger = @import("root").bun.logger;
-const mdx_lexer = @import("./mdx_lexer.zig");
-const Lexer = mdx_lexer.Lexer;
-const importRecord = @import("../import_record.zig");
-const js_ast = bun.JSAst;
-const JSParser = @import("../js_parser/js_parser.zig").MDXParser;
-const ParseStatementOptions = @import("../js_parser/js_parser.zig").ParseStatementOptions;
-
-const options = @import("../options.zig");
-
-const fs = @import("../fs.zig");
-const bun = @import("root").bun;
-const string = bun.string;
-const Output = bun.Output;
-const Global = bun.Global;
-const Environment = bun.Environment;
-const strings = bun.strings;
-const MutableString = bun.MutableString;
-const stringZ = bun.stringZ;
-const default_allocator = bun.default_allocator;
-const C = bun.C;
-const expect = std.testing.expect;
-const ImportKind = importRecord.ImportKind;
-const BindingNodeIndex = js_ast.BindingNodeIndex;
-const Define = @import("../defines.zig").Define;
-const js_lexer = bun.js_lexer;
-const StmtNodeIndex = js_ast.StmtNodeIndex;
-const ExprNodeIndex = js_ast.ExprNodeIndex;
-const ExprNodeList = js_ast.ExprNodeList;
-const StmtNodeList = js_ast.StmtNodeList;
-const BindingNodeList = js_ast.BindingNodeList;
-const ParserOptions = @import("../js_parser/js_parser.zig").Parser.Options;
-const runVisitPassAndFinish = @import("../js_parser/js_parser.zig").Parser.runVisitPassAndFinish;
-const Ref = @import("../ast/base.zig").Ref;
-const assert = std.debug.assert;
-const BabyList = js_ast.BabyList;
-
-const LocRef = js_ast.LocRef;
-const S = js_ast.S;
-const B = js_ast.B;
-const G = js_ast.G;
-const T = mdx_lexer.T;
-const E = js_ast.E;
-const Stmt = js_ast.Stmt;
-const Expr = js_ast.Expr;
-const Binding = js_ast.Binding;
-const Symbol = js_ast.Symbol;
-const Level = js_ast.Op.Level;
-const Op = js_ast.Op;
-const Scope = js_ast.Scope;
-const Range = logger.Range;
-
-pub const Container = struct {
- ch: u8 = 0,
- is_loose: bool = false,
- is_task: bool = false,
- start: u32 = 0,
- mark_indent: u32 = 0,
- contents_indent: u32 = 0,
- block_index: u32 = 0,
- task_mark_off: u32 = 0,
-};
-
-pub const Block = struct {
- tag: Tag = Tag.html,
- flags: Block.Flags.Set = Block.Flags.Set{},
- data: u32 = 0,
- /// Leaf blocks: Count of lines (MD_LINE or MD_VERBATIMLINE) on the block.
- /// LI: Task mark offset in the input doc.
- /// OL: Start item number.
- ///
- line_count: u32 = 0,
- line_offset: u32 = 0,
- detail: Block.Detail = Block.Detail{ .none = .{} },
-
- pub inline fn lines(this: Block, lines_: BabyList(Line)) []Line {
- return lines_.ptr[this.line_offset .. this.line_offset + this.line_count];
- }
-
- pub inline fn verbatimLines(this: Block, lines_: BabyList(Line.Verbatim)) []Line.Verbatim {
- return lines_.ptr[this.line_offset .. this.line_offset + this.line_count];
- }
-
- pub const Data = u32;
-
- pub const Flags = enum(u3) {
- container_opener = 0,
- container_closer = 1,
- loose_list = 2,
- setext_header = 3,
-
- pub const Set = std.enums.EnumSet(Block.Flags);
- };
-
- pub inline fn isContainer(this: Block) bool {
- return this.flags.contains(.container_opener) or this.flags.contains(.container_closer);
- }
-
- pub const Tag = enum {
- /// <body>...</body>
- doc,
-
- /// <blockquote>...</blockquote>
- quote,
-
- /// <ul>...</ul>
- ///Detail: Structure ul_detail.
- ul,
-
- /// <ol>...</ol>
- ///Detail: Structure ol_detail.
- ol,
-
- /// <li>...</li>
- ///Detail: Structure li_detail.
- li,
-
- /// <hr>
- hr,
-
- /// <h1>...</h1> (for levels up to 6)
- ///Detail: Structure h_detail.
- h,
-
- /// <pre><code>...</code></pre>
- ///Note the text lines within code blocks are terminated with '\n'
- ///instead of explicit MD_TEXT_BR.
- code,
-
- /// Raw HTML block. This itself does not correspond to any particular HTML
- ///tag. The contents of it _is_ raw HTML source intended to be put
- ///in verbatim form to the HTML output.
- html,
-
- /// <p>...</p>
- p,
-
- /// <table>...</table> and its contents.
- ///Detail: Structure table_detail (for table),
- /// structure td_detail (for th and td)
- ///Note all of these are used only if extension MD_FLAG_TABLES is enabled.
- table,
- thead,
- tbody,
- tr,
- th,
- td,
- };
-
- pub const UL = struct {
- tight: bool = false,
- mark: u8 = '*',
- };
-
- pub const OL = struct {
- start: u32 = 0,
- tight: bool = false,
- mark: u8 = '*',
- };
-
- pub const LI = struct {
- /// Can be non-zero only with MD_FLAG_TASKLISTS
- task: bool = false,
- /// is_task, then one of 'x', 'X' or ' '. Undefined otherwise.
- task_mark: u8 = 'x',
- /// If is_task, then offset in the input of the char between '[' and ']'.
- task_mark_off: u32 = 0,
- };
-
- pub const Header = u4;
-
- pub const Code = struct {
- info: Attribute = .{},
- lang: Attribute = .{},
- /// character used for fenced code block; or zero for indented code block. *
- fence: u8 = '`',
- };
-
- pub const Table = struct {
- /// Count of columns in the table.
- column_count: u32 = 0,
- /// Count of rows in the table header (currently always 1)
- head_row_count: u32 = 1,
- /// Count of rows in the table body
- body_row_count: u32 = 0,
- };
-
- pub const Detail = union {
- none: void,
- ul: UL,
- ol: OL,
- li: LI,
- };
-
- pub const TD = struct {
- alignment: Align = Align.default,
- };
-};
-pub const Span = struct {
- pub const Tag = enum {
- /// <em>...</em>
- em,
-
- /// <strong>...</strong>
- strong,
-
- /// <a href="xxx">...</a>
- /// Detail: Structure a_detail.
- a,
-
- /// <img src="xxx">...</a>
- /// Detail: Structure img_detail.
- /// Note: Image text can contain nested spans and even nested images.
- /// If rendered into ALT attribute of HTML <IMG> tag, it's responsibility
- /// of the parser to deal with it.
- img,
-
- /// <code>...</code>
- code,
-
- /// <del>...</del>
- /// Note: Recognized only when MD_FLAG_STRIKETHROUGH is enabled.
- del,
-
- /// For recognizing inline ($) and display ($$) equations
- /// Note: Recognized only when MD_FLAG_LATEXMATHSPANS is enabled.
- latexmath,
- latexmath_display,
-
- /// Wiki links
- /// Note: Recognized only when MD_FLAG_WIKILINKS is enabled.
- wikilink,
-
- /// <u>...</u>
- /// Note: Recognized only when MD_FLAG_UNDERLINE is enabled.
- u,
- };
-
- pub const Link = struct {
- src: Attribute = .{},
- title: Attribute = .{},
- };
-
- pub const Image = Link;
-
- pub const Wikilink = struct {
- target: Attribute = .{},
- };
-};
-
-pub const Text = enum {
- /// Normal text.
- normal,
- /// NULL character. CommonMark requires replacing NULL character with
- /// the replacement char U+FFFD, so this allows caller to do that easily.
- nullchar,
- /// Line breaks.
- /// Note these are not sent from blocks with verbatim output (MD_BLOCK_CODE
- /// or MD_BLOCK_HTML). In such cases, '\n' is part of the text itself.
- /// <br> (hard break)
- br,
- /// '\n' in source text where it is not semantically meaningful (soft break)
- softbr,
- /// Entity.
- /// (a) Named entity, e.g. &nbsp;
- /// (Note MD4C does not have a list of known entities.
- /// Anything matching the regexp /&[A-Za-z][A-Za-z0-9]{1,47};/ is
- /// treated as a named entity.)
- /// (b) Numerical entity, e.g. &#1234;
- /// (c) Hexadecimal entity, e.g. &#x12AB;
- ///
- /// As MD4C is mostly encoding agnostic, application gets the verbatim
- /// entity text into the MD_PARSER::text_callback().
- entity,
- /// Text in a code block (inside MD_BLOCK_CODE) or inlined code (`code`).
- /// If it is inside MD_BLOCK_CODE, it includes spaces for indentation and
- /// '\n' for new lines. br and softbr are not sent for this
- /// kind of text.
- code,
- /// Text is a raw HTML. If it is contents of a raw HTML block (i.e. not
- /// an inline raw HTML), then br and softbr are not used.
- /// The text contains verbatim '\n' for the new lines.
- html,
- /// Text is inside an equation. This is processed the same way as inlined code
- /// spans (`code`).
- latexmath,
-};
-pub const Align = enum(u3) {
- default = 0,
- left = 1,
- center = 2,
- right = 3,
-};
-
-/// String attribute.
-///
-/// This wraps strings which are outside of a normal text flow and which are
-/// propagated within various detailed structures, but which still may contain
-/// string portions of different types like e.g. entities.
-///
-/// So, for example, lets consider this image:
-///
-/// ![image alt text](http://example.org/image.png 'foo &quot; bar')
-///
-/// The image alt text is propagated as a normal text via the MD_PARSER::text()
-/// callback. However, the image title ('foo &quot; bar') is propagated as
-/// MD_ATTRIBUTE in MD_SPAN_IMG_DETAIL::title.
-///
-/// Then the attribute MD_SPAN_IMG_DETAIL::title shall provide the following:
-/// -- [0]: "foo " (substr_types[0] == MD_TEXT_NORMAL; substr_offsets[0] == 0)
-/// -- [1]: "&quot;" (substr_types[1] == MD_TEXT_ENTITY; substr_offsets[1] == 4)
-/// -- [2]: " bar" (substr_types[2] == MD_TEXT_NORMAL; substr_offsets[2] == 10)
-/// -- [3]: (n/a) (n/a ; substr_offsets[3] == 14)
-///
-/// Note that these invariants are always guaranteed:
-/// -- substr_offsets[0] == 0
-/// -- substr_offsets[LAST+1] == size
-/// -- Currently, only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR
-/// substrings can appear. This could change only of the specification
-/// changes.
-///
-pub const Attribute = struct {
- text: []const u8 = "",
- substring: Substring.List = .{},
-};
-pub const Substring = struct {
- offset: u32,
- tag: Text,
-
- pub const List = std.MultiArrayList(Substring);
- pub const ListPool = ObjectPool(List);
-};
-
-pub const Mark = struct {
- position: Ref = Ref.None,
- prev: u32 = std.math.maxInt(u32),
- next: u32 = std.math.maxInt(u32),
- ch: u8 = 0,
- flags: u16 = 0,
-
- /// Maybe closer.
- pub const potential_closer = 0x02;
- /// Maybe opener.
- pub const potential_opener = 0x01;
- /// Definitely opener.
- pub const opener = 0x04;
- /// Definitely closer.
- pub const closer = 0x08;
- /// Resolved in any definite way.
- pub const resolved = 0x10;
-
- /// Helper for the "rule of 3". */
- pub const emph_intraword = 0x20;
- pub const emph_mod3_0 = 0x40;
- pub const emph_mod3_1 = 0x80;
- pub const emph_mod3_2 = (0x40 | 0x80);
- pub const emph_mod3_mask = (0x40 | 0x80);
- /// Distinguisher for '<', '>'. */
- pub const autolink = 0x20;
- /// For permissive autolinks. */
- pub const validpermissiveautolink = 0x20;
- /// For '[' to rule out invalid link labels early */
- pub const hasnestedbrackets = 0x20;
-
- /// During analyzes of inline marks, we need to manage some "mark chains",
- /// of (yet unresolved) openers. This structure holds start/end of the chain.
- /// The chain internals are then realized through MD_MARK::prev and ::next.
- pub const Chain = struct {
- head: u32 = std.math.maxInt(u32),
- tail: u32 = std.math.maxInt(u32),
-
- pub const List = struct {
- data: [13]Chain = [13]Chain{ .{}, .{}, .{}, .{}, .{}, .{}, .{}, .{}, .{}, .{}, .{}, .{} },
- pub inline fn ptr_chain(this: *List) *Chain {
- return &this.data[0];
- }
- pub inline fn tablecellboundaries(this: *List) *Chain {
- return &this.data[1];
- }
- pub inline fn asterisk_openers_extraword_mod3_0(this: *List) *Chain {
- return &this.data[2];
- }
- pub inline fn asterisk_openers_extraword_mod3_1(this: *List) *Chain {
- return &this.data[3];
- }
- pub inline fn asterisk_openers_extraword_mod3_2(this: *List) *Chain {
- return &this.data[4];
- }
- pub inline fn asterisk_openers_intraword_mod3_0(this: *List) *Chain {
- return &this.data[5];
- }
- pub inline fn asterisk_openers_intraword_mod3_1(this: *List) *Chain {
- return &this.data[6];
- }
- pub inline fn asterisk_openers_intraword_mod3_2(this: *List) *Chain {
- return &this.data[7];
- }
- pub inline fn underscore_openers(this: *List) *Chain {
- return &this.data[8];
- }
- pub inline fn tilde_openers_1(this: *List) *Chain {
- return &this.data[9];
- }
- pub inline fn tilde_openers_2(this: *List) *Chain {
- return &this.data[10];
- }
- pub inline fn bracket_openers(this: *List) *Chain {
- return &this.data[11];
- }
- pub inline fn dollar_openers(this: *List) *Chain {
- return &this.data[12];
- }
- };
- };
-};
-
-pub const Line = struct {
- beg: u32 = 0,
- end: u32 = 0,
-
- pub const Tag = enum(u32) {
- blank,
- hr,
- atx_header,
- setext_header,
- setext_underline,
- indented_code,
- fenced_code,
- html,
- text,
- table,
- table_underline,
- };
- pub const Analysis = packed struct {
- tag: Tag = Tag.blank,
- beg: u32 = 0,
- end: u32 = 0,
- indent: u32 = 0,
- data: u32 = 0,
-
- pub const blank = Analysis{};
- pub fn eql(a: Analysis, b: Analysis) bool {
- return strings.eqlLong(std.mem.asBytes(&a), std.mem.asBytes(&b), false);
- }
- };
-
- pub const Verbatim = struct {
- line: Line = Line{},
- indent: u32 = 0,
- };
-};
-
-pub const MDParser = struct {
- marks: BabyList(Mark) = .{},
- chain: Mark.Chain.List = .{},
- source: logger.Source,
- flags: Flags.Set = Flags.commonmark,
- allocator: std.mem.Allocator,
- mdx: *MDX,
- mark_char_map: [255]u1 = undefined,
- doc_ends_with_newline: bool = false,
- size: u32 = 0,
-
- lines: BabyList(Line) = .{},
- verbatim_lines: BabyList(Line.Verbatim) = .{},
-
- containers: BabyList(Container) = .{},
- blocks: BabyList(Block) = .{},
- current_block: ?*Block = null,
- current_block_index: u32 = 0,
-
- code_fence_length: u32 = 0,
- code_indent_offset: u32 = std.math.maxInt(u32),
- last_line_has_list_loosening_effect: bool = false,
- last_list_item_starts_with_two_blank_lines: bool = false,
-
- pub const Flags = enum {
- /// In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' '
- collapse_whitespace,
- /// Do not require space in ATX headers ( ###header )
- permissive_atxheaders,
- /// Recognize URLs as autolinks even without '<', '>'
- permissive_url_autolinks,
- /// Recognize e-mails as autolinks even without '<', '>' and 'mailto:'
- permissive_email_autolinks,
- /// Disable indented code blocks. (Only fenced code works.)
- noindented_codeblocks,
- /// Disable raw HTML blocks.
- no_html_blocks,
- /// Disable raw HTML (inline).
- no_html_spans,
- /// Enable tables extension.
- tables,
- /// Enable strikethrough extension.
- strikethrough,
- /// Enable WWW autolinks (even without any scheme prefix, if they begin with 'www.')
- permissive_www_autolinks,
- /// Enable task list extension.
- tasklists,
- /// Enable $ and $$ containing LaTeX equations.
- latex_mathspans,
- /// Enable wiki links extension.
- wikilinks,
- /// Enable underline extension (and disables '_' for normal emphasis).
- underline,
-
- pub const Set = std.enums.EnumSet(Flags);
- pub const permissive_autolinks = Set.init(.{ .permissive_email_autolinks = true, .permissive_url_autolinks = true });
- pub const no_email = Set.init(.{ .no_html_blocks = true, .no_html_spans = true });
- pub const github = Set.init(.{ .tables = true, .permissive_autolinks = true, .strikethrough = true, .tasklists = true });
- pub const commonmark: i32 = Set{};
- };
-
- fn buildCharMap(this: *MDParser) void {
- @memset(&this.mark_char_map, 0, this.mark_char_map.len);
-
- this.mark_char_map['\\'] = 1;
- this.mark_char_map['*'] = 1;
- this.mark_char_map['_'] = 1;
- this.mark_char_map['`'] = 1;
- this.mark_char_map['&'] = 1;
- this.mark_char_map[';'] = 1;
- this.mark_char_map['<'] = 1;
- this.mark_char_map['>'] = 1;
- this.mark_char_map['['] = 1;
- this.mark_char_map['!'] = 1;
- this.mark_char_map[']'] = 1;
- this.mark_char_map[0] = 1;
-
- // whitespace
- this.mark_char_map[' '] = 1;
- this.mark_char_map['\t'] = 1;
- this.mark_char_map['\r'] = 1;
- this.mark_char_map['\n'] = 1;
-
- // form feed
- this.mark_char_map[0xC] = 1;
- // vertical tab
- this.mark_char_map[0xB] = 1;
-
- if (this.flags.contains(.strikethrough)) {
- this.mark_char_map['~'] = 1;
- }
-
- if (this.flags.contains(.latex_mathspans)) {
- this.mark_char_map['$'] = 1;
- }
-
- if (this.flags.contains(.permissive_email_autolinks)) {
- this.mark_char_map['@'] = 1;
- }
-
- if (this.flags.contains(.permissive_url_autolinks)) {
- this.mark_char_map[':'] = 1;
- }
-
- if (this.flags.contains(.permissive_www_autolinks)) {
- this.mark_char_map['.'] = 1;
- }
-
- if (this.flags.contains(.tables)) {
- this.mark_char_map['.'] = 1;
- }
- }
- pub fn init(allocator: std.mem.Allocator, source: logger.Source, flags: Flags.Set, mdx: *MDX) MDParser {
- var parser = MDParser{
- .allocator = allocator,
- .source = source,
- .flags = flags,
- .mdx = mdx,
- .size = @truncate(u32, source.contents.len),
- };
- parser.buildCharMap();
- parser.doc_ends_with_newline = source.contents.len.len > 0 and source.contents[source.contents.len - 1] == '\n';
- return parser;
- }
-
- fn startNewBlock(this: *MDParser, line: *const Line.Analysis) !void {
- try this.blocks.push(
- this.allocator,
- Block{
- .tag = switch (line.tag) {
- .hr => Block.Tag.hr,
- .atx_header, .setext_header => Block.Tag.h,
- .fenced_code, .indented_code => Block.Tag.code,
- .text => Block.Tag.p,
- .html => Block.Tag.html,
- else => unreachable,
- },
- .data = line.data,
- .line_count = 0,
- .line_offset = switch (line.tag) {
- .indented_code, .html, .fenced_code => this.verbatim_lines.len,
- else => this.lines.len,
- },
- },
- );
- }
-
- inline fn charAt(this: *const MDParser, index: u32) u8 {
- return this.source.contents[index];
- }
-
- inline fn isNewline(this: *const MDParser, index: u32) bool {
- return switch (this.charAt(index)) {
- '\n', '\r' => true,
- else => false,
- };
- }
-
- inline fn isAnyOf2(this: *const MDParser, index: u32, comptime first: u8, comptime second: u8) bool {
- return isAnyOf2_(this.charAt(index), first, second);
- }
-
- inline fn isAnyOf2_(char: u8, comptime first: u8, comptime second: u8) bool {
- return switch (char) {
- first, second => true,
- else => false,
- };
- }
-
- inline fn isAnyOf(this: *const MDParser, index: u32, comptime values: []const u8) bool {
- return isCharAnyOf(this.charAt(index), values);
- }
-
- inline fn isCharAnyOf(char: u8, comptime values: []const u8) bool {
- inline for (values) |val| {
- if (val == char) return true;
- }
- return false;
- }
-
- inline fn isBlank(char: u8) bool {
- return isCharAnyOf(char, &[_]u8{ ' ', '\t' });
- }
-
- inline fn isWhitespace(char: u8) bool {
- return isCharAnyOf(char, &[_]u8{ ' ', '\t', 0xC, 0xB });
- }
-
- pub fn getIndent(this: *MDParser, total_indent: u32, beg: u32, end: *u32) u32 {
- var off = beg;
- var indent = total_indent;
- while (off < this.size and isBlank(this.charAt(off))) {
- if (this.charAt(off) == '\t') {
- indent = (indent + 4) & ~3;
- } else {
- indent += 1;
- }
- off += 1;
- }
- end.* = off;
- return indent - total_indent;
- }
-
- pub fn isContainerMark(this: *MDParser, indent: u32, beg: u32, end: *u32, container: *Container) bool {
- var off = beg;
- var max_end: u32 = undefined;
-
- if (off >= this.size or indent >= this.code_indent_offset)
- return false;
-
- if (this.charAt(off) == '>') {
- off += 1;
- container.ch = '>';
- container.is_loose = false;
- container.is_task = false;
- container.mark_indent = indent;
- container.contents_indent = indent + 1;
- end.* = off;
- return true;
- }
-
- // Check for list item bullet mark.
- if (this.isAnyOf(off, "-+*") and (off + 1 >= this.size or isBlank(this.charAt(off + 1)) or this.isNewline(off + 1))) {
- container.ch = this.charAt(off);
- container.is_loose = false;
- container.is_task = false;
- container.mark_indent = indent;
- container.contents_indent = indent + 1;
- end.* = off + 1;
- return true;
- }
-
- // Check for ordered list item marks
- max_end = @min(off + 9, this.size);
- container.start = 0;
- while (off < max_end and std.ascii.isDigit(this.charAt(off))) {
- container.start = container.start * 10 + (this.charAt(off) - '0');
- off += 1;
- }
-
- if (off > beg and
- off < this.size and
- (this.isAnyOf2(off, '.', ')')) and
- (off + 1 >= this.size or
- this.isBlank(this.charAt(off + 1) or
- this.isNewline(off + 1))))
- {
- container.ch = this.charAt(off);
- container.is_loose = false;
- container.is_task = false;
- container.mark_indent = indent;
- container.contents_indent = indent + off - beg + 1;
- end.* = off + 1;
- return true;
- }
-
- return false;
- }
-
- fn analyzeLine(this: *MDParser, beg: u32, end: *u32, pivot_line: *const Line.Analysis, line: *Line.Analysis) !void {
- _ = this;
- _ = beg;
- _ = end;
- _ = pivot_line;
- _ = line;
- var off = beg;
- var hr_killer: u32 = 0;
- var prev_line_has_list_loosening_effect = this.last_line_has_list_loosening_effect;
- var container = Container{};
- _ = hr_killer;
- _ = prev_line_has_list_loosening_effect;
- _ = container;
- var total_indent: u32 = 0;
- var n_parents: u32 = 0;
- var n_brothers: u32 = 0;
- var n_children: u32 = 0;
-
- // Given the indentation and block quote marks '>', determine how many of
- // the current containers are our parents.
- while (n_parents < this.containers.len) {
- var c: *Container = this.containers.ptr + n_parents;
-
- if (c.ch == '>' and line.indent < this.code_indent_offset and off < this.size and this.charAt(off) == '>') {
- off += 1;
- total_indent += 1;
- line.indent = this.getIndent(total_indent, off, &off);
- total_indent += line.indent;
-
- // The optional 1st space after '>' is part of the block quote mark.
- line.indent -|= line.indent;
- line.beg = off;
- } else if (c.ch != '>' and line.indent >= c.contents_indent) {
- line.indent -|= c.contents_indent;
- } else {
- break;
- }
-
- n_parents += 1;
- }
-
- if (off >= this.size or this.isNewline(off)) {
- // Blank line does not need any real indentation to be nested inside a list
- if (n_brothers + n_children == 0) {
- while (n_parents < this.containers.len and this.containers.ptr[n_parents].ch == '>') {
- n_parents += 1;
- }
- }
- }
-
- while (true) {
- switch (pivot_line.tag) {
- .fencedcode => {
- // Check whether we are fenced code continuation.
- line.beg = off;
-
- // We are another MD_LINE_FENCEDCODE unless we are closing fence
- // which we transform into MD_LINE_BLANK.
- if (line.indent < this.code_indent_offset) {
- if (this.isClosingCodeFence(this.charAt(pivot_line.beg), off, &off)) {
- line.tag = .blank;
- this.last_line_has_list_loosening_effect = false;
- break;
- }
- }
-
- // Change indentation accordingly to the initial code fence.
- if (n_parents == this.containers.len) {
- line.indent -|= pivot_line.indent;
- line.tag = .fenced_code;
- break;
- }
- },
-
- .indentedcode => {},
- .text => {},
-
- .html => {},
- else => {},
- }
-
- // Check for blank line.
- if (off >= this.size or this.isNewline(off)) {
- if (pivot_line.tag == .indented_code and n_parents == this.containers.len) {
- line.tag = .indented_code;
- line.indent -|= this.code_indent_offset;
- this.last_line_has_list_loosening_effect = false;
- } else {
- line.tag = .blank;
- this.last_line_has_list_loosening_effect = n_parents > 0 and
- n_brothers + n_children == 0 and
- this.containers.ptr[n_parents - 1].ch != '>';
-
- // See https://github.com/mity/md4c/issues/6
- //
- // This ugly checking tests we are in (yet empty) list item but
- // not its very first line (i.e. not the line with the list
- // item mark).
- //
- // If we are such a blank line, then any following non-blank
- // line which would be part of the list item actually has to
- // end the list because according to the specification, "a list
- // item can begin with at most one blank line."
- //
- if (n_parents > 0 and this.containers.ptr[n_parents - 1].ch != '>' and n_brothers + n_children == 0 and this.current_block == null and this.blocks.len > 0) {
- var top_block = this.blocks.last().?;
- if (top_block.tag == .li) {
- this.last_list_item_starts_with_two_blank_lines = true;
- }
- }
- }
- break;
- } else {
- // This is the 2nd half of the hack. If the flag is set (i.e. there
- // was a 2nd blank line at the beginning of the list item) and if
- // we would otherwise still belong to the list item, we enforce
- // the end of the list.
- this.last_line_has_list_loosening_effect = false;
- if (this.last_list_item_starts_with_two_blank_lines) {
- if (n_parents > 0 and
- this.containers.ptr[n_parents - 1].ch != '>' and
- n_brothers + n_children == 0 and
- this.current_block == null and this.blocks.len > 1)
- {
- var top = this.blocks.last().?;
- if (top.tag == .li) {
- n_parents -|= 1;
- }
- }
- this.last_line_has_list_loosening_effect = true;
- }
- }
-
- // Check whether we are Setext underline.
- if (line.indent < this.code_indent_offset and
- pivot_line.tag == .text and
- off < this.size and
- this.isAnyOf2(off, '=', '-') and
- n_parents == this.containers.len)
- {
- var level: u4 = 0;
- if (this.isSetextUnderline(off, &off, &level)) {
- line.tag = .setext_underline;
- line.data = level;
- break;
- }
- }
-
- // Check for a thematic break line
- if (line.indent < this.code_indent_offset and off < this.size and off >= hr_killer and this.isAnyOf(off, "-_*")) {
- if (this.isHRLine(off, &off, &hr_killer)) {
- line.tag = .hr;
- break;
- }
- }
-
- // Check for "brother" container. I.e. whether we are another list item
- //in already started list.
- if (n_parents < this.containers.len and n_brothers + n_children == 0) {
- var tmp: u32 = undefined;
-
- if (this.isContainerMark(line.indent, off, &tmp, &container) and
- isContainerCompatible(&this.containers.ptr[n_parents], &container))
- {
- pivot_line.* = Line.Analysis.blank;
- off = tmp;
-
- total_indent += container.contents_indent - container.mark_indent;
- line.indent = this.getIndent(total_indent, off, &off);
- total_indent += line.indent;
- line.beg = off;
-
- // Some of the following whitespace actually still belongs to the mark.
- if (off >= this.size or this.isNewline(off)) {
- container.contents_indent += 1;
- } else if (line.indent <= this.code_indent_offset) {
- container.contents_indent += line.indent;
- line.indent = 0;
- } else {
- container.contents_indent += 1;
- line.indent -= 1;
- }
-
- this.containers.ptr[n_parents].mark_indent = container.mark_indent;
- this.containers.ptr[n_parents].contents_indent = container.contents_indent;
- n_brothers += 1;
- continue;
- }
- }
-
- // Check for indented code
- // Note: indented code block cannot interrupt a paragrpah
- if (line.indent >= this.code_indent_offset and
- (pivot_line.tag == .blank or
- pivot_line.tag == .indented_code))
- {
- line.tag = .indented_code;
- std.debug.assert(line.indent >= this.code_indent_offset);
- line.indent -|= this.code_indent_offset;
- line.data = 0;
- break;
- }
-
- // Check for start of a new container block
- if (line.indent < this.code_indent_offset and
- this.isContainerMark(line.indent, off, &off, &container))
- {
- if (pivot_line.tag == .text and
- n_parents == this.n_containers and
- (off >= this.size or this.isNewline(off)) and
- container.ch != '>')
- {
- // Noop. List mark followed by a blank line cannot interrupt a paragraph.
- } else if (pivot_line.tag == .text and
- n_parents == this.containers.len and
- isAnyOf2_(container.ch, '.', ')'))
- {
- // Noop. Ordered list cannot interrupt a paragraph unless the start index is 1.
- } else {
- total_indent += container.contents_indent - container.mark_indent;
- line.indent = this.getIndent(total_indent, off, &off);
- total_indent += line.indent;
-
- line.beg = off;
- line.data = container.ch;
-
- // Some of the following whitespace actually still belongs to the mark.
- if (off >= this.size or this.isNewline(off)) {
- container.contents_indent += 1;
- } else if (line.indent <= this.code_indent_offset) {
- container.contents_indent += line.indent;
- line.indent = 0;
- } else {
- container.contents_indent += 1;
- line.indent -= 1;
- }
-
- if (n_brothers + n_children == 0) {
- pivot_line.* = Line.Analysis.blank;
- }
-
- if (n_children == 0) {
- try this.leaveChildContainers(n_parents + n_brothers);
- }
-
- n_children += 1;
- try this.pushContainer(container);
- continue;
- }
- }
-
- // heck whether we are table continuation.
- if (pivot_line.tag == .table and n_parents == this.n_containers) {
- line.tag = .table;
- break;
- }
-
- // heck for ATX header.
- if (line.indent < this.code_indent_offset and off < this.size and this.isAnyOf(off, '#')) {
- var level: u4 = 0;
- if (this.isATXHeaderLine(off, &line.beg, &off, &level)) {
- line.tag = .atx_header;
- line.data = level;
- break;
- }
- }
-
- // Check whether we are starting code fence.
- if (off < this.size and this.isAnyOf2(off, '`', '~')) {
- if (this.isOpeningCodeFence(off, &off)) {
- line.tag = .fenced_code;
- line.data = 1;
- break;
- }
- }
-
- // Check for start of raw HTML block.
- if (off < this.size and !this.flags.contains(.no_html_blocks) and this.charAt(off) == '<') {}
-
- // Check for table underline.
- if (this.flags.contains(.tables) and pivot_line.tag == .text and off < this.size and this.isAnyOf(off, "|-:") and n_parents == this.containers.len) {
- var col_count: u32 = undefined;
-
- if (this.current_block != null and this.current_block.?.line_count == 1 and this.isTableUnderline(off, &off, &col_count)) {
- line.data = col_count;
- line.tag = .table_underline;
- break;
- }
- }
-
- // By default, we are normal text line.
- line.tag = .text;
- if (pivot_line.tag == .text and n_brothers + n_children == 0) {
- // lazy continuation
- n_parents = this.containers.len;
- }
-
- // Check for task mark.
- if (this.flags.contains(.tasklists) and
- n_brothers + n_children > 0 and
- off < this.size and
- isCharAnyOf(this.containers.last().?.ch, "-+*.)"))
- {
- var tmp: u32 = off;
-
- while (tmp < this.size and tmp < off + 3 and isBlank(tmp)) {
- tmp += 1;
- }
-
- if ((tmp + 2 < this.size and
- this.charAt(tmp) == '[' and
- this.isAnyOf(tmp + 1, "xX ") and
- this.charAt(tmp + 2) == ']') and
- (tmp + 3 == this.size or
- isBlank(this.charAt(tmp + 3)) or
- this.isNewline(tmp + 3)))
- {
- var task_container: *Container = if (n_children > 0) this.containers.last().? else &container;
- task_container.is_task = true;
- task_container.task_mark_off = tmp + 1;
- off = tmp + 3;
- while (off < this.size and isWhitespace(this.charAt(off))) {
- off += 1;
- }
- if (off == this.size) break;
- line.beg = off;
- }
- }
-
- break;
- }
-
- // Scan for end of the line.
- while (!(strings.hasPrefixComptime(this.source.contents.ptr[off..], "\n\n\n\n") or
- strings.hasPrefixComptime(this.source.contents.ptr[off..], "\r\n\r\n")))
- {
- off += 4;
- }
-
- while (off < this.size and !this.isNewline(off)) {
- off += 1;
- }
-
- // Set end of line
- line.end = off;
-
- // ut for ATX header, we should exclude the optional trailing mark.
- if (line.type == .atx_header) {
- var tmp = line.end;
- while (tmp > line.beg and this.charAt(tmp - 1) == ' ') {
- tmp -= 1;
- }
-
- while (tmp > line.beg and this.charAt(tmp - 1) == '#') {
- tmp -= 1;
- }
-
- if (tmp == line.beg or this.charAt(tmp - 1) == ' ' or this.flags.contains(.permissive_atxheaders)) {
- line.end = tmp;
- }
- }
-
- // Trim trailing spaces.
- switch (line.tag) {
- .indented_code, .fenced_code => {},
- else => {
- while (line.end > line.beg and this.charAt(line.end - 1) == ' ') {
- line.end -= 1;
- }
- },
- }
-
- // Eat also the new line
- if (off < this.size and this.charAt(off) == '\r') {
- off += 1;
- }
-
- if (off < this.size and this.charAt(off) == '\n') {
- off += 1;
- }
-
- end.* = off;
-
- // If we belong to a list after seeing a blank line, the list is loose.
- if (prev_line_has_list_loosening_effect and line.tag != .blank and n_parents + n_brothers > 0) {
- var c: *Container = this.containers.ptr[n_parents + n_brothers - 1];
- if (c.ch != '>') {
- var block: *Block = this.blocks.ptr[c.block_index];
- block.flags.insert(.loose_list);
- }
- }
-
- // Leave any containers we are not part of anymore.
- if (n_children == 0 and n_parents + n_brothers < this.containers.len) {
- try this.leaveChildContainers(n_parents + n_brothers);
- }
-
- // Enter any container we found a mark for
- if (n_brothers > 0) {
- std.debug.assert(n_brothers == 0);
- try this.pushContainerBytes(
- Block.Tag.li,
- this.containers.ptr[n_parents].task_mark_off,
- if (this.containers.ptr[n_parents].is_task) this.charAt(this.containers.ptr[n_parents].task_mark_off) else 0,
- Block.Flags.container_closer,
- );
- try this.pushContainerBytes(
- Block.Tag.li,
- container.task_mark_off,
- if (container.is_task) this.charAt(container.task_mark_off) else 0,
- Block.Flags.container_opener,
- );
- this.containers.ptr[n_parents].is_task = container.is_task;
- this.containers.ptr[n_parents].task_mark_off = container.task_mark_off;
- }
-
- if (n_children > 0) {
- try this.enterChildContainers(n_children);
- }
- }
- fn processLine(this: *MDParser, p_pivot_line: **const Line.Analysis, line: *Line.Analysis) !void {
- var pivot_line = p_pivot_line.*;
-
- switch (line.tag) {
- .blank => {
- // Blank line ends current leaf block.
- try this.endCurrentBlock();
- p_pivot_line.* = Line.Analysis.blank;
- },
- .hr, .atx_header => {
- try this.endCurrentBlock();
-
- // Add our single-line block
- try this.startNewBlock(line);
- try this.addLineIntoCurrentBlock(line);
- try this.endCurrentBlock();
- p_pivot_line.* = &Line.Analysis.blank;
- },
- .setext_underline => {
- this.current_block.?.tag = .table;
- this.current_block.?.data = line.data;
- this.current_block.?.flags.insert(.setext_header);
- try this.addLineIntoCurrentBlock(line);
- try this.endCurrentBlock();
- if (this.current_block == null) {
- p_pivot_line.* = &Line.Analysis.blank;
- } else {
- // This happens if we have consumed all the body as link ref. defs.
- //and downgraded the underline into start of a new paragraph block.
- line.tag = .text;
- p_pivot_line.* = line;
- }
- },
- // MD_LINE_TABLEUNDERLINE changes meaning of the current block.
- .table_underline => {
- var current_block = this.current_block.?;
- std.debug.assert(current_block.line_count == 1);
- current_block.tag = .table;
- current_block.data = line.data;
- std.debug.assert(pivot_line != &Line.Analysis.blank);
- @intToPtr(*Line.Analysis, @ptrToInt(p_pivot_line.*)).tag = .table;
- try this.addLineIntoCurrentBlock(line);
- },
- else => {
- // The current block also ends if the line has different type.
- if (line.tag != pivot_line.tag) {
- try this.endCurrentBlock();
- }
-
- // The current line may start a new block.
- if (this.current_block == null) {
- try this.startNewBlock(line);
- p_pivot_line.* = line;
- }
-
- // In all other cases the line is just a continuation of the current block.
- try this.addLineIntoCurrentBlock(line);
- },
- }
- }
- fn consumeLinkReferenceDefinitions(this: *MDParser) !void {
- _ = this;
- }
- fn addLineIntoCurrentBlock(this: *MDParser, analysis: *const Line.Analysis) !void {
- var current_block = this.current_block.?;
-
- switch (current_block.tag) {
- .code, .html => {
- if (current_block.line_count > 0)
- std.debug.assert(
- this.verbatim_lines.len == current_block.line_count + current_block.line_offset,
- );
- if (current_block.line_count == 0) {
- current_block.line_offset = this.verbatim_lines.len;
- }
-
- try this.verbatim_lines.push(this.allocator, Line.Verbatim{
- .indent = analysis.indent,
- .line = .{
- .beg = analysis.beg,
- .end = analysis.end,
- },
- });
- },
- else => {
- if (current_block.line_count > 0)
- std.debug.assert(
- this.lines.len == current_block.line_count + current_block.line_offset,
- );
- if (current_block.line_count == 0) {
- current_block.line_offset = this.lines.len;
- }
- this.lines.push(this.allocator, .{ .beg = analysis.beg, .end = analysis.end });
- },
- }
-
- current_block.line_count += 1;
- }
- fn endCurrentBlock(this: *MDParser) !void {
- _ = this;
-
- var block = this.current_block orelse return;
- // Check whether there is a reference definition. (We do this here instead
- // of in md_analyze_line() because reference definition can take multiple
- // lines.) */
- if ((block.tag == .p or block.tag == .h) and block.flags.contains(.setext_header)) {
- var lines = block.lines(this.lines);
- if (lines[0].beg == '[') {
- try this.consumeLinkReferenceDefinitions();
- block = this.current_block orelse return;
- }
- }
-
- if (block.tag == .h and block.flags.contains(.setext_header)) {
- var n_lines = block.line_count;
- if (n_lines > 1) {
- // get rid of the underline
- if (this.lines.len == block.line_count + block.line_offset) {
- this.lines.len -= 1;
- }
- block.line_count -= 1;
- } else {
- // Only the underline has left after eating the ref. defs.
- // Keep the line as beginning of a new ordinary paragraph. */
- block.tag = .p;
- }
- }
-
- // Mark we are not building any block anymore.
- this.current_block = null;
- this.current_block_index -|= 1;
- }
- fn buildRefDefHashTable(this: *MDParser) !void {
- _ = this;
- }
- fn leaveChildContainers(this: *MDParser, keep: u32) !void {
- _ = this;
- while (this.containers.len > keep) {
- var c = this.containers.last().?;
- var is_ordered_list = false;
- switch (c.ch) {
- ')', '.' => {
- is_ordered_list = true;
- },
- '-', '+', '*' => {
- try this.pushContainerBytes(
- Block.Tag.li,
- c.task_mark_off,
- if (c.is_task) this.charAt(c.task_mark_off) else 0,
- Block.Flags.container_closer,
- );
- try this.pushContainerBytes(
- if (is_ordered_list) Block.Tag.ol else Block.Tag.ul,
- c.ch,
- if (c.is_task) this.charAt(c.task_mark_off) else 0,
- Block.Flags.container_closer,
- );
- },
- '>' => {
- try this.pushContainerBytes(
- Block.Tag.quote,
- 0,
- 0,
- Block.Flags.container_closer,
- );
- },
- else => unreachable,
- }
-
- this.containers.len -= 1;
- }
- }
- fn enterChildContainers(this: *MDParser, keep: u32) !void {
- _ = this;
- var i: u32 = this.containers.len - keep;
- while (i < this.containers.len) : (i += 1) {
- var c: *Container = this.containers.ptr[i];
- var is_ordered_list = false;
-
- switch (c.ch) {
- ')', '.' => {
- is_ordered_list = true;
- },
- '-', '+', '*' => {
- // Remember offset in ctx.block_bytes so we can revisit the
- // block if we detect it is a loose list.
- try this.endCurrentBlock();
- c.block_index = this.blocks.len;
-
- try this.pushContainerBytes(
- if (is_ordered_list) Block.Tag.ol else Block.Tag.ul,
- c.start,
- c.ch,
- Block.Flags.container_opener,
- );
- try this.pushContainerBytes(
- Block.Tag.li,
- c.task_mark_off,
- if (c.is_task) this.charAt(c.task_mark_off) else 0,
- Block.Flags.container_opener,
- );
- },
- '>' => {
- try this.pushContainerBytes(
- Block.Tag.quote,
- 0,
- 0,
- Block.Flags.container_opener,
- );
- },
- else => unreachable,
- }
- }
- }
- fn pushContainer(this: *MDParser, container: Container) !void {
- try this.containers.push(this.allocator, container);
- }
-
- fn processLeafBlock(this: *MDParser, comptime tag: Block.Tag, block: *Block) anyerror!void {
- const BlockDetailType = comptime switch (tag) {
- Block.Tag.h => Block.Header,
- Block.Tag.code => Block.Code,
- Block.Tag.table => Block.Table,
- };
-
- const is_in_tight_list = if (this.containers.len == 0)
- false
- else
- !this.containers.ptr[this.containers.len - 1].is_loose;
-
- const detail: BlockDetailType = switch (comptime tag) {
- Block.Tag.h => @truncate(Block.Header, block.data),
- Block.Tag.code => try this.setupFencedCodeDetail(block),
- Block.Tag.table => .{
- .col_count = block.data,
- .head_row_count = 1,
- .body_row_count = block.line_count -| 2,
- },
- else => {},
- };
-
- if (!is_in_tight_list or comptime tag != .p) {
- try this.mdx.onEnterBlock(block.tag, BlockDetailType, detail);
- }
-
- defer {
- if (comptime tag == Block.Tag.code) {}
- }
- }
-
- fn pushContainerBytes(this: *MDParser, block_type: Block.Tag, start: u32, data: u32, flag: Block.Flags) !void {
- try this.endCurrentBlock();
- var block = Block{
- .tag = block_type,
- .line_count = start,
- .data = data,
- };
- block.flags.insert(flag);
- var prev_block: ?Block = null;
- if (this.current_block) |curr| {
- prev_block = curr.*;
- }
-
- try this.blocks.push(this.allocator, block);
- if (prev_block != null) {
- this.current_block = this.blocks.ptr[this.current_block_index];
- }
- }
- fn processBlock(this: *MDParser, comptime tag: Block.Tag, block: *Block) !void {
- const detail: Block.Detail =
- switch (comptime tag) {
- .ul => Block.Detail{
- .ul = .{
- .is_tight = !block.flags.contains(.loose_list),
- .mark = @truncate(u8, block.data),
- },
- },
- .ol => Block.Detail{
- .ol = .{
- .start = block.line_count,
- .is_tight = !block.flags.contains(.loose_list),
- .mark_delimiter = @truncate(u8, block.data),
- },
- },
- .li => Block.Detail{
- .li = .{
- .is_task = block.data != 0,
- .task_mark = @truncate(u8, block.data),
- .task_mark_offset = @intCast(u32, block.line_count),
- },
- },
- else => Block.Detail{ .none = .{} },
- };
-
- if (block.flags.contains(.container)) {
- if (block.flags.contains(.container_closer)) {
- switch (block.tag) {
- .li => try this.mdx.onLeaveBlock(tag, Block.LI, detail.li),
- .ul => try this.mdx.onLeaveBlock(tag, Block.UL, detail.ul),
- .ol => try this.mdx.onLeaveBlock(tag, Block.OL, detail.ol),
- else => try this.mdx.onLeaveBlock(block.tag, void, {}),
- }
- this.containers.len -|= switch (block.tag) {
- .ul, .ol, .blockquote => 1,
- else => 0,
- };
- }
-
- if (block.flags.contains(.container_opener)) {
- switch (comptime tag) {
- .li => try this.mdx.onEnterBlock(tag, Block.LI, detail.li),
- .ul => try this.mdx.onEnterBlock(tag, Block.UL, detail.ul),
- .ol => try this.mdx.onEnterBlock(tag, Block.OL, detail.ol),
- else => try this.mdx.onEnterBlock(block.tag, void, {}),
- }
-
- switch (comptime tag) {
- .ul, .ol => {
- this.containers.ptr[this.containers.len].is_loose = block.flags.contains(.loose_list);
- this.containers.len += 1;
- },
- .blockquote => {
- // This causes that any text in a block quote, even if
- // nested inside a tight list item, is wrapped with
- // <p>...</p>. */
- this.containers.ptr[this.containers.len].is_loose = true;
- this.containers.len += 1;
- },
- else => {},
- }
- }
- } else {
- try this.processLeafBlock(tag, block);
- }
- }
- fn processAllBlocks(this: *MDParser) !void {
- _ = this;
-
- // ctx->containers now is not needed for detection of lists and list items
- // so we reuse it for tracking what lists are loose or tight. We rely
- // on the fact the vector is large enough to hold the deepest nesting
- // level of lists.
- this.containers.len = 0;
- var blocks = this.blocks.slice();
- for (&blocks) |*block| {}
- }
- fn isContainerCompatible(pivot: *const Container, container: *const Container) bool {
- // Block quote has no "items" like lists.
- if (container.ch == '>') return false;
-
- if (container.ch != pivot.ch)
- return false;
-
- if (container.mark_indent > pivot.contents_indent)
- return false;
- return true;
- }
-
- fn isHRLine(this: *MDParser, beg: u32, end: *u32, hr_killer: *u32) bool {
- var off = beg + 1;
- var n: u32 = 1;
-
- while (off < this.size and (this.charAt(off) == this.charAt(beg) or this.charAt(off) == ' ' or this.charAt(off) == '\t')) {
- if (this.charAt(off) == this.charAt(beg))
- n += 1;
- off += 1;
- }
-
- if (n < 3) {
- hr_killer.* = off;
- return false;
- }
-
- // Nothing else can be present on the line. */
- if (off < this.size and !this.isNewline(off)) {
- hr_killer.* = off;
- return false;
- }
-
- end.* = off;
- return true;
- }
-
- fn isSetextUnderline(this: *MDParser, beg: u32, end: *u32, level: *u4) bool {
- var off = beg + 1;
- while (off < this.size and this.charAt(off) == this.charAt(beg))
- off += 1;
-
- // Optionally, space(s) can follow. */
- while (off < this.size and this.charAt(off) == ' ')
- off += 1;
-
- // But nothing more is allowed on the line.
- if (off < this.size and !this.isNewline(off))
- return false;
- level.* = if (this.charAt(beg) == '=') 1 else 2;
- end.* = off;
- return true;
- }
-
- fn isATXHeaderLine(this: *MDParser, beg: u32, p_beg: *u32, end: *u32, level: *u4) bool {
- var n: i32 = undefined;
- var off: u32 = beg + 1;
-
- while (off < this.size and this.charAt(off) == '#' and off - beg < 7) {
- off += 1;
- }
- n = off - beg;
-
- if (n > 6)
- return false;
- level.* = @intCast(u4, n);
-
- if (!(this.flags.contains(.permissive_atxheaders)) and off < this.size and
- this.charAt(off) != ' ' and this.charAt(off) != '\t' and !this.isNewline(off))
- return false;
-
- while (off < this.size and this.charAt(off) == ' ') {
- off += 1;
- }
-
- p_beg.* = off;
- end.* = off;
-
- return true;
- }
-
- fn isTableUnderline(this: *MDParser, beg: u32, end: *u32, column_column: *u32) bool {
- _ = this;
- _ = end;
- _ = column_column;
-
- var off = beg;
- var found_pipe = false;
- var col_count: u32 = 0;
-
- if (off < this.size and this.charAt(off) == '|') {
- found_pipe = true;
- off += 1;
- while (off < this.size and isWhitespace(this.charAt(off))) {
- off += 1;
- }
- }
-
- while (true) {
- var delimited = false;
-
- // Cell underline ("-----", ":----", "----:" or ":----:")if(off < this.size and this.charAt(off) == _T(':'))
- off += 1;
- if (off >= this.size or this.charAt(off) != '-')
- return false;
- while (off < this.size and this.charAt(off) == '-')
- off += 1;
- if (off < this.size and this.charAt(off) == ':')
- off += 1;
-
- col_count += 1;
-
- // Pipe delimiter (optional at the end of line). */
- while (off < this.size and isWhitespace(this.charAt(off)))
- off += 1;
- if (off < this.size and this.charAt(off) == '|') {
- delimited = true;
- found_pipe = true;
- off += 1;
- while (off < this.size and isWhitespace(this.charAt(off)))
- off += 1;
- }
-
- // Success, if we reach end of line.
- if (off >= this.size or this.isNewline(off))
- break;
-
- if (!delimited)
- return false;
- }
-
- if (!found_pipe)
- return false;
-
- column_column.* = col_count;
- end.* = off;
- return true;
- }
-
- fn isOpeningCodeFence(this: *MDParser, beg: u8, end: *u32) bool {
- var off = beg;
- const first = this.charAt(beg);
-
- while (off < this.size and this.charAt(off) == first) {
- off += 1;
- }
-
- // Fence must have at least three characters.
- if (off - beg < 3)
- return false;
-
- // Optionally, space(s) can follow
- while (off < this.size and this.charAt(off) == ' ') {
- off += 1;
- }
-
- // Optionally, an info string can follow.
- while (off < this.size and !this.isNewline(this.charAt(off))) {
- // Backtick-based fence must not contain '`' in the info string.
- if (first == '`' and this.charAt(off) == '`')
- return false;
- off += 1;
- }
-
- end.* = off;
- return true;
- }
-
- fn isClosingCodeFence(this: *MDParser, ch: u8, beg: u8, end: *u32) bool {
- var off = beg;
-
- defer {
- end.* = off;
- }
-
- while (off < this.size and this.charAt(off) == ch) {
- off += 1;
- }
-
- if (off - beg < this.code_fence_length) {
- return false;
- }
-
- // Optionally, space(s) can follow
- while (off < this.size and this.charAt(off) == ' ') {
- off += 1;
- }
-
- // But nothing more is allowed on the line.
- if (off < this.size and !this.isNewline(this.charAt(off)))
- return false;
-
- return true;
- }
-
- pub fn parse(this: *MDParser) anyerror!void {
- var pivot_line = &Line.Analysis.blank;
- var line_buf: [2]Line.Analysis = undefined;
- var line = &line_buf[0];
- var offset: u32 = 0;
-
- try this.mdx.onEnterBlock(.doc, void, {});
-
- const len: u32 = this.size;
- while (offset < len) {
- if (line == pivot_line) {
- line = if (line == &line_buf[0]) &line_buf[1] else &line_buf[0];
- }
-
- try this.analyzeLine(offset, &offset, pivot_line, line);
- try this.processLine(&pivot_line, line);
- }
-
- this.endCurrentBlock();
-
- try this.buildRefDefHashTable();
-
- this.leaveChildContainers(0);
- this.processAllBlocks();
- try this.mdx.onLeaveBlock(.doc, void, {});
- }
-};
-
-pub const MDX = struct {
- parser: JSParser,
- log: *logger.Log,
- allocator: std.mem.Allocator,
- stmts: std.ArrayListUnmanaged(js_ast.Stmt) = .{},
-
- pub const Options = struct {};
-
- pub fn onEnterBlock(this: *MDX, tag: Block.Tag, comptime Detail: type, detail: Detail) anyerror!void {
- _ = tag;
- _ = detail;
- _ = this;
- }
-
- pub fn onLeaveBlock(this: *MDX, tag: Block.Tag, comptime Detail: type, detail: Detail) anyerror!void {
- _ = tag;
- _ = detail;
- _ = this;
- }
-
- pub fn onEnterSpan(this: *MDX, tag: Span.Tag, comptime Detail: type, detail: Detail) anyerror!void {
- _ = tag;
- _ = detail;
- _ = this;
- }
-
- pub fn onLeaveSpan(this: *MDX, tag: Span.Tag, comptime Detail: type, detail: Detail) anyerror!void {
- _ = tag;
- _ = detail;
- _ = this;
- }
-
- pub fn onText(this: *MDX, tag: Text, text: []const u8) anyerror!void {
- _ = tag;
- _ = text;
- _ = this;
- }
-
- pub inline fn source(p: *const MDX) *const logger.Source {
- return &p.lexer.source;
- }
-
- pub fn e(_: *MDX, t: anytype, loc: logger.Loc) Expr {
- const Type = @TypeOf(t);
- if (@typeInfo(Type) == .Pointer) {
- return Expr.init(std.meta.Child(Type), t.*, loc);
- } else {
- return Expr.init(Type, t, loc);
- }
- }
-
- pub fn s(_: *MDX, t: anytype, loc: logger.Loc) Stmt {
- const Type = @TypeOf(t);
- if (@typeInfo(Type) == .Pointer) {
- return Stmt.init(std.meta.Child(Type), t.*, loc);
- } else {
- return Stmt.alloc(Type, t, loc);
- }
- }
-
- pub fn setup(
- this: *MDX,
- _options: ParserOptions,
- log: *logger.Log,
- source_: *const logger.Source,
- define: *Define,
- allocator: std.mem.Allocator,
- ) !void {
- try JSParser.init(
- allocator,
- log,
- source_,
- define,
- js_lexer.Lexer.initNoAutoStep(log, source_.*, allocator),
- _options,
- &this.parser,
- );
- this.lexer = try Lexer.init(&this.parser.lexer);
- this.allocator = allocator;
- this.log = log;
- this.stmts = .{};
- }
-
- pub fn parse(this: *MDX) !js_ast.Result {
- try this._parse();
- return try runVisitPassAndFinish(JSParser, &this.parser, try this.stmts.toOwnedSlice(this.allocator));
- }
-
- fn run(this: *MDX) anyerror!logger.Loc {
- _ = this;
- return logger.Loc.Empty;
- }
-
- fn _parse(this: *MDX) anyerror!void {
- var root_children = std.ArrayListUnmanaged(Expr){};
- var first_loc = try run(this, &root_children);
-
- first_loc.start = @max(first_loc.start, 0);
- const args_loc = first_loc;
- first_loc.start += 1;
- const body_loc = first_loc;
-
- // We need to simulate a function that was parsed
- _ = try this.parser.pushScopeForParsePass(.function_args, args_loc);
-
- _ = try this.parser.pushScopeForParsePass(.function_body, body_loc);
-
- const root = this.e(E.JSXElement{
- .tag = this.e(E.JSXElement.Tag.map.get(E.JSXElement.Tag.main), body_loc),
- .children = ExprNodeList.fromList(root_children),
- }, body_loc);
-
- var root_stmts = try this.allocator.alloc(Stmt, 1);
- root_stmts[0] = this.s(S.Return{ .value = root }, body_loc);
-
- try this.stmts.append(
- this.allocator,
-
- this.s(S.ExportDefault{
- .default_name = try this.parser.createDefaultName(args_loc),
- .value = .{
- .expr = this.e(E.Arrow{
- .body = G.FnBody{
- .stmts = root_stmts,
- .loc = body_loc,
- },
- .args = &[_]G.Arg{},
- .prefer_expr = true,
- }, args_loc),
- },
- }, args_loc),
- );
- }
-};
diff --git a/src/options.zig b/src/options.zig
index f30594516..3e7eec72a 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -1989,6 +1989,7 @@ pub const OutputFile = struct {
input: Fs.Path,
value: Value,
size: usize = 0,
+ size_without_sourcemap: usize = 0,
mtime: ?i128 = null,
hash: u64 = 0,
source_map_index: u32 = std.math.maxInt(u32),
@@ -2099,6 +2100,7 @@ pub const OutputFile = struct {
output_path: string,
size: ?usize = null,
input_path: []const u8 = "",
+ display_size: u32 = 0,
output_kind: JSC.API.BuildArtifact.OutputKind = .chunk,
data: union(enum) {
buffer: struct {
@@ -2125,6 +2127,7 @@ pub const OutputFile = struct {
.file => |file| file.size,
.saved => 0,
},
+ .size_without_sourcemap = options.display_size,
.hash = options.hash orelse 0,
.output_kind = options.output_kind,
.source_map_index = options.source_map_index orelse std.math.maxInt(u32),
diff --git a/src/output.zig b/src/output.zig
index 0b0780bf8..4f47d2496 100644
--- a/src/output.zig
+++ b/src/output.zig
@@ -317,6 +317,22 @@ pub fn printElapsedStdout(elapsed: f64) void {
printElapsedToWithCtx(elapsed, Output.pretty, false, {});
}
+pub fn printElapsedStdoutTrim(elapsed: f64) void {
+ switch (@floatToInt(i64, @round(elapsed))) {
+ 0...1500 => {
+ const fmt = "<r><d>[<b>{d:>}ms<r><d>]<r>";
+ const args = .{elapsed};
+ pretty(fmt, args);
+ },
+ else => {
+ const fmt = "<r><d>[<b>{d:>}s<r><d>]<r>";
+ const args = .{elapsed / 1000.0};
+
+ pretty(fmt, args);
+ },
+ }
+}
+
pub fn printStartEnd(start: i128, end: i128) void {
const elapsed = @divTrunc(@truncate(i64, end - start), @as(i64, std.time.ns_per_ms));
printElapsed(@intToFloat(f64, elapsed));
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 01c37c632..58f00c2af 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -167,6 +167,8 @@ pub const Result = struct {
is_external: bool = false,
+ is_standalone_module: bool = false,
+
// This is true when the package was loaded from within the node_modules directory.
is_from_node_modules: bool = false,
@@ -485,6 +487,8 @@ pub const Resolver = struct {
env_loader: ?*DotEnv.Loader = null,
store_fd: bool = false,
+ standalone_module_graph: ?*bun.StandaloneModuleGraph = null,
+
// These are sets that represent various conditions for the "exports" field
// in package.json.
// esm_conditions_default: bun.StringHashMap(bool),
@@ -814,6 +818,23 @@ pub const Resolver = struct {
};
}
+ if (r.standalone_module_graph) |graph| {
+ if (strings.hasPrefixComptime(import_path, "compiled://")) {
+ if (graph.files.contains(import_path)) {
+ return .{
+ .success = Result{
+ .import_kind = kind,
+ .path_pair = PathPair{
+ .primary = Path.init(import_path),
+ },
+ .is_standalone_module = true,
+ .module_type = .esm,
+ },
+ };
+ }
+ }
+ }
+
if (DataURL.parse(import_path)) |_data_url| {
const data_url: DataURL = _data_url;
// "import 'data:text/javascript,console.log(123)';"
@@ -862,7 +883,7 @@ pub const Resolver = struct {
var tmp = r.resolveWithoutSymlinks(source_dir, import_path, kind, global_cache);
switch (tmp) {
.success => |*result| {
- if (!strings.eqlComptime(result.path_pair.primary.namespace, "node"))
+ if (!strings.eqlComptime(result.path_pair.primary.namespace, "node") and !result.is_standalone_module)
r.finalizeResult(result, kind) catch |err| return .{ .failure = err };
r.flushDebugLogs(.success) catch {};
diff --git a/src/runtime.zig b/src/runtime.zig
index 96a51699e..99d8ca102 100644
--- a/src/runtime.zig
+++ b/src/runtime.zig
@@ -138,7 +138,7 @@ pub const Fallback = struct {
return ProdSourceContent;
}
}
- pub const version_hash = @embedFile("./fallback.version");
+ pub const version_hash = @import("build_options").fallback_html_version;
var version_hash_int: u32 = 0;
pub fn versionHash() u32 {
if (version_hash_int == 0) {
@@ -263,26 +263,15 @@ pub const Runtime = struct {
}
}
- pub const version_hash = @embedFile("./runtime.version");
+ pub const version_hash = @import("build_options").runtime_js_version;
var version_hash_int: u32 = 0;
pub fn versionHash() u32 {
if (version_hash_int == 0) {
- version_hash_int = @truncate(u32, std.fmt.parseInt(u64, version(), 16) catch unreachable);
+ version_hash_int = @truncate(u32, version_hash);
}
return version_hash_int;
}
- pub inline fn version() string {
- return version_hash;
- }
-
- const bytecodeCacheFilename = std.fmt.comptimePrint("__runtime.{s}", .{version_hash});
- var bytecodeCacheFetcher = Fs.BytecodeCacheFetcher{};
-
- pub fn byteCodeCacheFile(fs: *Fs.FileSystem.RealFS) ?bun.StoredFileDescriptorType {
- return bytecodeCacheFetcher.fetch(bytecodeCacheFilename, fs);
- }
-
pub const Features = struct {
react_fast_refresh: bool = false,
hot_module_reloading: bool = false,
diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig
index cc557caf1..d9dd85a7e 100644
--- a/src/sourcemap/sourcemap.zig
+++ b/src/sourcemap/sourcemap.zig
@@ -35,10 +35,84 @@ pub const SourceMapState = struct {
};
sources: [][]const u8 = &[_][]u8{},
-sources_content: [][]SourceContent,
+sources_content: []string,
mapping: Mapping.List = .{},
allocator: std.mem.Allocator,
+pub fn parse(
+ allocator: std.mem.Allocator,
+ json_source: *const Logger.Source,
+ log: *Logger.Log,
+) !SourceMap {
+ var json = try bun.JSON.ParseJSONUTF8(json_source, log, allocator);
+ var mappings = bun.sourcemap.Mapping.List{};
+
+ if (json.get("version")) |version| {
+ if (version.data != .e_number or version.data.e_number.value != 3.0) {
+ return error.@"Unsupported sourcemap version";
+ }
+ }
+
+ if (json.get("mappings")) |mappings_str| {
+ if (mappings_str.data != .e_string) {
+ return error.@"Invalid sourcemap mappings";
+ }
+
+ var parsed = bun.sourcemap.Mapping.parse(allocator, try mappings_str.data.e_string.toUTF8(allocator), null, std.math.maxInt(i32));
+ if (parsed == .fail) {
+ try log.addMsg(bun.logger.Msg{
+ .data = parsed.fail.toData("sourcemap.json"),
+ .kind = .err,
+ });
+ return error.@"Failed to parse sourcemap mappings";
+ }
+
+ mappings = parsed.success;
+ }
+
+ var sources = std.ArrayList(bun.string).init(allocator);
+ var sources_content = std.ArrayList(string).init(allocator);
+
+ if (json.get("sourcesContent")) |mappings_str| {
+ if (mappings_str.data != .e_array) {
+ return error.@"Invalid sourcemap sources";
+ }
+
+ try sources_content.ensureTotalCapacityPrecise(mappings_str.data.e_array.items.len);
+ for (mappings_str.data.e_array.items.slice()) |source| {
+ if (source.data != .e_string) {
+ return error.@"Invalid sourcemap source";
+ }
+
+ try source.data.e_string.toUTF8(allocator);
+ sources_content.appendAssumeCapacity(source.data.e_string.slice());
+ }
+ }
+
+ if (json.get("sources")) |mappings_str| {
+ if (mappings_str.data != .e_array) {
+ return error.@"Invalid sourcemap sources";
+ }
+
+ try sources.ensureTotalCapacityPrecise(mappings_str.data.e_array.items.len);
+ for (mappings_str.data.e_array.items.slice()) |source| {
+ if (source.data != .e_string) {
+ return error.@"Invalid sourcemap source";
+ }
+
+ try source.data.e_string.toUTF8(allocator);
+ sources.appendAssumeCapacity(source.data.e_string.slice());
+ }
+ }
+
+ return SourceMap{
+ .mapping = mappings,
+ .allocator = allocator,
+ .sources_content = sources_content.items,
+ .sources = sources.items,
+ };
+}
+
pub const Mapping = struct {
generated: LineColumnOffset,
original: LineColumnOffset,
diff --git a/src/standalone_bun.zig b/src/standalone_bun.zig
new file mode 100644
index 000000000..332bf5dad
--- /dev/null
+++ b/src/standalone_bun.zig
@@ -0,0 +1,480 @@
+// Originally, we tried using LIEF to inject the module graph into a MachO segment
+// But this incurred a fixed 350ms overhead on every build, which is unacceptable
+// so we give up on codesigning support on macOS for now until we can find a better solution
+const bun = @import("root").bun;
+const std = @import("std");
+const Schema = bun.Schema.Api;
+
+const Environment = bun.Environment;
+
+pub const StandaloneModuleGraph = struct {
+ bytes: []const u8 = "",
+ files: bun.StringArrayHashMap(File),
+ entry_point_id: u32 = 0,
+
+ pub fn entryPoint(this: *const StandaloneModuleGraph) *File {
+ return &this.files.values()[this.entry_point_id];
+ }
+
+ pub const CompiledModuleGraphFile = struct {
+ name: Schema.StringPointer = .{},
+ loader: bun.options.Loader = .file,
+ contents: Schema.StringPointer = .{},
+ sourcemap: Schema.StringPointer = .{},
+ };
+
+ pub const File = struct {
+ name: []const u8 = "",
+ loader: bun.options.Loader,
+ contents: []const u8 = "",
+ sourcemap: LazySourceMap,
+ };
+
+ pub const LazySourceMap = union(enum) {
+ compressed: []const u8,
+ decompressed: bun.sourcemap,
+
+ pub fn load(this: *LazySourceMap, log: *bun.logger.Log, allocator: std.mem.Allocator) !*bun.sourcemap {
+ if (this.* == .decompressed) return &this.decompressed;
+
+ var decompressed = try allocator.alloc(u8, bun.zstd.getDecompressedSize(this.compressed));
+ var result = bun.zstd.decompress(decompressed, this.compressed);
+ if (result == .err) {
+ allocator.free(decompressed);
+ log.addError(null, bun.logger.Loc.Empty, bun.span(result.err)) catch unreachable;
+ return error.@"Failed to decompress sourcemap";
+ }
+ errdefer allocator.free(decompressed);
+ var bytes = decompressed[0..result.success];
+
+ this.* = .{ .decompressed = try bun.sourcemap.parse(allocator, &bun.logger.Source.initPathString("sourcemap.json", bytes), log) };
+ return &this.decompressed;
+ }
+ };
+
+ pub const Offsets = extern struct {
+ byte_count: usize = 0,
+ modules_ptr: bun.StringPointer = .{},
+ entry_point_id: u32 = 0,
+ };
+
+ const trailer = "\n---- Bun! ----\n";
+
+ pub fn fromBytes(allocator: std.mem.Allocator, raw_bytes: []const u8, offsets: Offsets) !StandaloneModuleGraph {
+ if (raw_bytes.len == 0) return StandaloneModuleGraph{
+ .files = bun.StringArrayHashMap(File).init(allocator),
+ };
+
+ const modules_list_bytes = sliceTo(raw_bytes, offsets.modules_ptr);
+ const modules_list = std.mem.bytesAsSlice(CompiledModuleGraphFile, modules_list_bytes);
+
+ if (offsets.entry_point_id > modules_list.len) {
+ return error.@"Corrupted module graph: entry point ID is greater than module list count";
+ }
+
+ var modules = bun.StringArrayHashMap(File).init(allocator);
+ try modules.ensureTotalCapacity(modules_list.len);
+ for (modules_list) |module| {
+ modules.putAssumeCapacity(
+ sliceTo(raw_bytes, module.name),
+ File{
+ .name = sliceTo(raw_bytes, module.name),
+ .loader = module.loader,
+ .contents = sliceTo(raw_bytes, module.contents),
+ .sourcemap = LazySourceMap{
+ .compressed = sliceTo(raw_bytes, module.sourcemap),
+ },
+ },
+ );
+ }
+
+ return StandaloneModuleGraph{
+ .bytes = raw_bytes[0..offsets.byte_count],
+ .files = modules,
+ .entry_point_id = offsets.entry_point_id,
+ };
+ }
+
+ fn sliceTo(bytes: []const u8, ptr: bun.StringPointer) []const u8 {
+ if (ptr.length == 0) return "";
+
+ return bytes[ptr.offset..][0..ptr.length];
+ }
+
+ pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile) ![]u8 {
+ var serialize_trace = bun.tracy.traceNamed(@src(), "ModuleGraph.serialize");
+ defer serialize_trace.end();
+ var entry_point_id: ?usize = null;
+ var string_builder = bun.StringBuilder{};
+ var module_count: usize = 0;
+ for (output_files, 0..) |output_file, i| {
+ string_builder.count(output_file.path);
+ string_builder.count(prefix);
+ if (output_file.value == .buffer) {
+ if (output_file.output_kind == .sourcemap) {
+ string_builder.cap += bun.zstd.compressBound(output_file.value.buffer.bytes.len);
+ } else {
+ if (entry_point_id == null) {
+ if (output_file.output_kind == .@"entry-point") {
+ entry_point_id = i;
+ }
+ }
+
+ string_builder.count(output_file.value.buffer.bytes);
+ module_count += 1;
+ }
+ }
+ }
+
+ if (module_count == 0 or entry_point_id == null) return &[_]u8{};
+
+ string_builder.cap += @sizeOf(CompiledModuleGraphFile) * output_files.len;
+ string_builder.cap += trailer.len;
+ string_builder.cap += 16;
+
+ {
+ var offsets_ = Offsets{};
+ string_builder.cap += std.mem.asBytes(&offsets_).len;
+ }
+
+ try string_builder.allocate(allocator);
+
+ var modules = try std.ArrayList(CompiledModuleGraphFile).initCapacity(allocator, module_count);
+
+ for (output_files) |output_file| {
+ if (output_file.output_kind == .sourcemap) {
+ continue;
+ }
+
+ if (output_file.value != .buffer) {
+ continue;
+ }
+
+ var module = CompiledModuleGraphFile{
+ .name = string_builder.fmtAppendCount("{s}{s}", .{ prefix, output_file.path }),
+ .loader = output_file.loader,
+ .contents = string_builder.appendCount(output_file.value.buffer.bytes),
+ };
+ if (output_file.source_map_index != std.math.maxInt(u32)) {
+ var remaining_slice = string_builder.allocatedSlice()[string_builder.len..];
+ const compressed_result = bun.zstd.compress(remaining_slice, output_files[output_file.source_map_index].value.buffer.bytes, 1);
+ if (compressed_result == .err) {
+ bun.Output.panic("Unexpected error compressing sourcemap: {s}", .{bun.span(compressed_result.err)});
+ }
+ module.sourcemap = string_builder.add(compressed_result.success);
+ }
+ modules.appendAssumeCapacity(module);
+ }
+
+ var offsets = Offsets{
+ .entry_point_id = @truncate(u32, entry_point_id.?),
+ .modules_ptr = string_builder.appendCount(std.mem.sliceAsBytes(modules.items)),
+ .byte_count = string_builder.len,
+ };
+
+ _ = string_builder.append(std.mem.asBytes(&offsets));
+ _ = string_builder.append(trailer);
+
+ return string_builder.ptr.?[0..string_builder.len];
+ }
+
+ const page_size = if (Environment.isLinux and Environment.isAarch64)
+ // some linux distros do 64 KB pages on aarch64
+ 64 * 1024
+ else
+ std.mem.page_size;
+
+ pub fn inject(bytes: []const u8) i32 {
+ var buf: [512]u8 = undefined;
+ var zname = bun.span(bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @bitCast(u64, std.time.milliTimestamp())) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get temporary file name: {s}", .{@errorName(err)});
+ Global.exit(1);
+ return -1;
+ });
+
+ const cloned_executable_fd: bun.FileDescriptor = brk: {
+ var self_buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined;
+ var self_exe = std.fs.selfExePath(&self_buf) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get self executable path: {s}", .{@errorName(err)});
+ Global.exit(1);
+ return -1;
+ };
+ self_buf[self_exe.len] = 0;
+ var self_exeZ = self_buf[0..self_exe.len :0];
+
+ if (comptime Environment.isMac) {
+ // if we're on a mac, use clonefile() if we can
+ // failure is okay, clonefile is just a fast path.
+ if (bun.C.darwin.clonefile(self_exeZ.ptr, zname.ptr, 0) == 0) {
+ switch (bun.JSC.Node.Syscall.open(zname, std.os.O.WRONLY | std.os.O.CLOEXEC, 0)) {
+ .result => |res| break :brk res,
+ .err => {},
+ }
+ }
+ }
+
+ // otherwise, just copy the file
+ const fd = switch (bun.JSC.Node.Syscall.open(zname, std.os.O.CLOEXEC | std.os.O.RDONLY, 0)) {
+ .result => |res| res,
+ .err => |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open temporary file to copy bun into: {s}", .{err.toSystemError().message.slice()});
+ Global.exit(1);
+ },
+ };
+ const self_fd = switch (bun.JSC.Node.Syscall.open(self_exeZ, std.os.O.CLOEXEC | std.os.O.WRONLY | std.os.O.CREAT, 0)) {
+ .result => |res| res,
+ .err => |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open bun executable to copy from as read-only: {s}", .{err.toSystemError().message.slice()});
+ Global.exit(1);
+ },
+ };
+ defer _ = bun.JSC.Node.Syscall.close(self_fd);
+ bun.copyFile(self_fd, fd) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to copy bun executable into temporary file: {s}", .{@errorName(err)});
+ Global.exit(1);
+ };
+ break :brk fd;
+ };
+
+ // Always leave at least one full page of padding at the end of the file.
+ const total_byte_count = brk: {
+ const fstat = std.os.fstat(cloned_executable_fd) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to stat temporary file: {s}", .{@errorName(err)});
+ Global.exit(1);
+ };
+
+ const count = @intCast(usize, @max(fstat.size, 0) + page_size + @intCast(i64, bytes.len) + 8);
+
+ std.os.lseek_SET(cloned_executable_fd, 0) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to seek to end of temporary file: {s}", .{@errorName(err)});
+ Global.exit(1);
+ };
+
+ // grow it by one page + the size of the module graph
+ std.os.ftruncate(cloned_executable_fd, count) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to truncate temporary file: {s}", .{@errorName(err)});
+ Global.exit(1);
+ };
+ break :brk count;
+ };
+
+ std.os.lseek_END(cloned_executable_fd, -@intCast(i64, bytes.len + 8)) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to seek to end of temporary file: {s}", .{@errorName(err)});
+ Global.exit(1);
+ };
+
+ var remain = bytes;
+ while (remain.len > 0) {
+ switch (bun.JSC.Node.Syscall.write(cloned_executable_fd, bytes)) {
+ .result => |written| remain = remain[written..],
+ .err => |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file: {s}", .{err.toSystemError().message.slice()});
+ Global.exit(1);
+ },
+ }
+ }
+
+ // the final 8 bytes in the file are the length of the module graph with padding, excluding the trailer and offsets
+ _ = bun.JSC.Node.Syscall.write(cloned_executable_fd, std.mem.asBytes(&total_byte_count));
+
+ _ = bun.C.fchmod(cloned_executable_fd, 0o777);
+
+ return cloned_executable_fd;
+ }
+
+ pub fn toExecutable(allocator: std.mem.Allocator, output_files: []const bun.options.OutputFile, root_dir: std.fs.IterableDir, module_prefix: []const u8, outfile: []const u8) !void {
+ const bytes = try toBytes(allocator, module_prefix, output_files);
+ if (bytes.len == 0) return;
+
+ const fd = inject(bytes);
+ if (fd == -1) {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to inject into file", .{});
+ Global.exit(1);
+ }
+
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const temp_location = bun.getFdPath(fd, &buf) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get path for fd: {s}", .{@errorName(err)});
+ Global.exit(1);
+ };
+
+ if (comptime Environment.isMac) {
+ {
+ var signer = std.ChildProcess.init(
+ &.{
+ "codesign",
+ "--remove-signature",
+ temp_location,
+ },
+ bun.default_allocator,
+ );
+ if (bun.logger.Log.default_log_level.atLeast(.verbose)) {
+ signer.stdout_behavior = .Inherit;
+ signer.stderr_behavior = .Inherit;
+ signer.stdin_behavior = .Inherit;
+ } else {
+ signer.stdout_behavior = .Ignore;
+ signer.stderr_behavior = .Ignore;
+ signer.stdin_behavior = .Ignore;
+ }
+ _ = signer.spawnAndWait() catch {};
+ }
+ }
+
+ std.os.renameat(std.fs.cwd().fd, temp_location, root_dir.dir.fd, outfile) catch |err| {
+ Output.prettyErrorln("<r><red>error<r><d>:<r> failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) });
+ Global.exit(1);
+ };
+ }
+
+ pub fn fromExecutable(allocator: std.mem.Allocator) !?StandaloneModuleGraph {
+ const self_exe = (openSelfExe(.{}) catch null) orelse return null;
+ defer _ = bun.JSC.Node.Syscall.close(self_exe);
+
+ var trailer_bytes: [4096]u8 = undefined;
+ std.os.lseek_END(self_exe, -4096) catch return null;
+ var read_amount: usize = 0;
+ while (read_amount < trailer_bytes.len) {
+ switch (bun.JSC.Node.Syscall.read(self_exe, trailer_bytes[read_amount..])) {
+ .result => |read| {
+ if (read == 0) return null;
+
+ read_amount += read;
+ },
+ .err => {
+ return null;
+ },
+ }
+ }
+
+ if (read_amount < trailer.len + @sizeOf(usize) + 32)
+ // definitely missing data
+ return null;
+
+ var end = @as([]u8, &trailer_bytes).ptr + read_amount - @sizeOf(usize);
+ const total_byte_count: usize = @bitCast(usize, end[0..8].*);
+
+ if (total_byte_count > std.math.maxInt(u32) or total_byte_count < 4096) {
+ // sanity check: the total byte count should never be more than 4 GB
+ // bun is at least like 30 MB so if it reports a size less than 4096 bytes then something is wrong
+ return null;
+ }
+ end -= trailer.len;
+
+ if (!bun.strings.hasPrefixComptime(end[0..trailer.len], trailer)) {
+ // invalid trailer
+ return null;
+ }
+
+ end -= @sizeOf(Offsets);
+
+ const offsets: Offsets = std.mem.bytesAsValue(Offsets, end[0..@sizeOf(Offsets)]).*;
+ if (offsets.byte_count >= total_byte_count) {
+ // if we hit this branch then the file is corrupted and we should just give up
+ return null;
+ }
+
+ var to_read = try bun.default_allocator.alloc(u8, offsets.byte_count);
+ var to_read_from = to_read;
+
+ // Reading the data and making sure it's page-aligned + won't crash due
+ // to out of bounds using mmap() is very complicated.
+ // So even though we ensure there is at least one page of padding at the end of the file,
+ // we just read the whole thing into memory for now.
+ // at the very least
+ // if you have not a ton of code, we only do a single read() call
+ if (Environment.allow_assert or offsets.byte_count > 1024 * 3) {
+ const offset_from_end = trailer_bytes.len - (@ptrToInt(end) - @ptrToInt(@as([]u8, &trailer_bytes).ptr));
+ std.os.lseek_END(self_exe, -@intCast(i64, offset_from_end + offsets.byte_count)) catch return null;
+
+ if (comptime Environment.allow_assert) {
+ // actually we just want to verify this logic is correct in development
+ if (offsets.byte_count <= 1024 * 3) {
+ to_read_from = try bun.default_allocator.alloc(u8, offsets.byte_count);
+ }
+ }
+
+ var remain = to_read_from;
+ while (remain.len > 0) {
+ switch (bun.JSC.Node.Syscall.read(self_exe, remain)) {
+ .result => |read| {
+ if (read == 0) return null;
+
+ remain = remain[read..];
+ },
+ .err => {
+ bun.default_allocator.free(to_read);
+ return null;
+ },
+ }
+ }
+ }
+
+ if (offsets.byte_count <= 1024 * 3) {
+ // we already have the bytes
+ end -= offsets.byte_count;
+ @memcpy(to_read.ptr, end, offsets.byte_count);
+ if (comptime Environment.allow_assert) {
+ std.debug.assert(bun.strings.eqlLong(to_read, end[0..offsets.byte_count], true));
+ }
+ }
+
+ return try StandaloneModuleGraph.fromBytes(allocator, to_read, offsets);
+ }
+
+ // this is based on the Zig standard library function, except it accounts for
+ fn openSelfExe(flags: std.fs.File.OpenFlags) std.fs.OpenSelfExeError!?bun.FileDescriptor {
+ // heuristic: `bun build --compile` won't be supported if the name is "bun" or "bunx".
+ // this is a cheap way to avoid the extra overhead of opening the executable
+ // and also just makes sense.
+ if (std.os.argv.len > 0) {
+ const argv0_len = bun.len(std.os.argv[0]);
+ if (argv0_len == 3) {
+ if (bun.strings.eqlComptimeIgnoreLen(std.os.argv[0][0..argv0_len], "bun")) {
+ return null;
+ }
+ }
+
+ if (argv0_len == 4) {
+ if (bun.strings.eqlComptimeIgnoreLen(std.os.argv[0][0..argv0_len], "bunx")) {
+ return null;
+ }
+ }
+ }
+
+ if (comptime Environment.isLinux) {
+ if (std.fs.openFileAbsoluteZ("/proc/self/exe", flags)) |easymode| {
+ return easymode.handle;
+ } else |_| {
+ if (std.os.argv.len > 0) {
+ // The user doesn't have /proc/ mounted, so now we just guess and hope for the best.
+ var whichbuf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ if (bun.which(
+ &whichbuf,
+ bun.getenvZ("PATH") orelse return error.FileNotFound,
+ "",
+ bun.span(std.os.argv[0]),
+ )) |path| {
+ return (try std.fs.cwd().openFileZ(path, flags)).handle;
+ }
+ }
+
+ return error.FileNotFound;
+ }
+ }
+
+ if (comptime Environment.isWindows) {
+ return (try std.fs.openSelfExe(flags)).handle;
+ }
+ // Use of MAX_PATH_BYTES here is valid as the resulting path is immediately
+ // opened with no modification.
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const self_exe_path = try std.fs.selfExePath(&buf);
+ buf[self_exe_path.len] = 0;
+ const file = try std.fs.openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
+ return file.handle;
+ }
+};
+
+const Output = bun.Output;
+const Global = bun.Global;
diff --git a/src/string_builder.zig b/src/string_builder.zig
index abed901dd..7aba5cd89 100644
--- a/src/string_builder.zig
+++ b/src/string_builder.zig
@@ -53,6 +53,36 @@ pub fn append(this: *StringBuilder, slice: string) string {
return result;
}
+pub fn add(this: *StringBuilder, len: usize) bun.StringPointer {
+ if (comptime Environment.allow_assert) {
+ assert(this.len <= this.cap); // didn't count everything
+ assert(this.ptr != null); // must call allocate first
+ }
+
+ const start = this.len;
+ this.len += len;
+
+ if (comptime Environment.allow_assert) assert(this.len <= this.cap);
+
+ return bun.StringPointer{ .offset = @truncate(u32, start), .length = @truncate(u32, len) };
+}
+pub fn appendCount(this: *StringBuilder, slice: string) bun.StringPointer {
+ if (comptime Environment.allow_assert) {
+ assert(this.len <= this.cap); // didn't count everything
+ assert(this.ptr != null); // must call allocate first
+ }
+
+ const start = this.len;
+ bun.copy(u8, this.ptr.?[this.len..this.cap], slice);
+ const result = this.ptr.?[this.len..this.cap][0..slice.len];
+ _ = result;
+ this.len += slice.len;
+
+ if (comptime Environment.allow_assert) assert(this.len <= this.cap);
+
+ return bun.StringPointer{ .offset = @truncate(u32, start), .length = @truncate(u32, slice.len) };
+}
+
pub fn fmt(this: *StringBuilder, comptime str: string, args: anytype) string {
if (comptime Environment.allow_assert) {
assert(this.len <= this.cap); // didn't count everything
@@ -68,6 +98,25 @@ pub fn fmt(this: *StringBuilder, comptime str: string, args: anytype) string {
return out;
}
+pub fn fmtAppendCount(this: *StringBuilder, comptime str: string, args: anytype) bun.StringPointer {
+ if (comptime Environment.allow_assert) {
+ assert(this.len <= this.cap); // didn't count everything
+ assert(this.ptr != null); // must call allocate first
+ }
+
+ var buf = this.ptr.?[this.len..this.cap];
+ const out = std.fmt.bufPrint(buf, str, args) catch unreachable;
+ const off = this.len;
+ this.len += out.len;
+
+ if (comptime Environment.allow_assert) assert(this.len <= this.cap);
+
+ return bun.StringPointer{
+ .offset = @truncate(u32, off),
+ .length = @truncate(u32, out.len),
+ };
+}
+
pub fn fmtCount(this: *StringBuilder, comptime str: string, args: anytype) void {
this.cap += std.fmt.count(str, args);
}
@@ -79,3 +128,11 @@ pub fn allocatedSlice(this: *StringBuilder) []u8 {
}
return ptr[0..this.cap];
}
+
+pub fn writable(this: *StringBuilder) []u8 {
+ var ptr = this.ptr orelse return &[_]u8{};
+ if (comptime Environment.allow_assert) {
+ assert(this.cap > 0);
+ }
+ return ptr[this.len..this.cap];
+}
diff --git a/src/which.zig b/src/which.zig
index 1cef93a22..8c29e7e4a 100644
--- a/src/which.zig
+++ b/src/which.zig
@@ -33,8 +33,10 @@ pub fn which(buf: *[bun.MAX_PATH_BYTES]u8, path: []const u8, cwd: []const u8, bi
// /foo/bar/baz as a path and you're in /home/jarred?
}
- if (isValid(buf, std.mem.trimRight(u8, cwd, std.fs.path.sep_str), bin)) |len| {
- return buf[0..len :0];
+ if (cwd.len > 0) {
+ if (isValid(buf, std.mem.trimRight(u8, cwd, std.fs.path.sep_str), bin)) |len| {
+ return buf[0..len :0];
+ }
}
var path_iter = std.mem.tokenize(u8, path, ":");
diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts
index 4960c7d39..c654602bc 100644
--- a/test/bundler/bundler_edgecase.test.ts
+++ b/test/bundler/bundler_edgecase.test.ts
@@ -51,7 +51,7 @@ describe("bundler", () => {
});
itBundled("edgecase/BunPluginTreeShakeImport", {
notImplemented: true,
- // This only appears at runtime and not with bun build, even with --transpile
+ // This only appears at runtime and not with bun build, even with --no-bundle
files: {
"/entry.ts": /* js */ `
import { A, B } from "./somewhere-else";
diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts
index 471cdd7f2..0d0680efe 100644
--- a/test/bundler/expectBundled.ts
+++ b/test/bundler/expectBundled.ts
@@ -521,7 +521,7 @@ function expectBundled(
"build",
...entryPaths,
...(entryPointsRaw ?? []),
- bundling === false ? "--transpile" : [],
+ bundling === false ? "--no-bundle" : [],
outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`,
define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]),
`--target=${target}`,