aboutsummaryrefslogtreecommitdiff
path: root/src/bundler.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bundler.zig')
-rw-r--r--src/bundler.zig4704
1 files changed, 2351 insertions, 2353 deletions
diff --git a/src/bundler.zig b/src/bundler.zig
index b34241f1b..77534e602 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -38,6 +38,9 @@ const NewBunQueue = @import("./bun_queue.zig").NewBunQueue;
const NodeFallbackModules = @import("./node_fallbacks.zig");
const CacheEntry = @import("./cache.zig").FsCacheEntry;
+const Linker = linker.Linker;
+const Resolver = _resolver.Resolver;
+
// How it works end-to-end
// 1. Resolve a file path from input using the resolver
// 2. Look at the extension of that file path, and determine a loader
@@ -94,1748 +97,1409 @@ pub const ParseResult = struct {
empty: bool = false,
};
-pub fn NewBundler(cache_files: bool) type {
- return struct {
- pub const Linker = if (cache_files) linker.Linker else linker.ServeLinker;
- pub const Resolver = if (cache_files) _resolver.Resolver else _resolver.ResolverUncached;
- const ThisBundler = @This();
+const cache_files = false;
- options: options.BundleOptions,
- log: *logger.Log,
- allocator: *std.mem.Allocator,
- result: options.TransformResult = undefined,
- resolver: Resolver,
- fs: *Fs.FileSystem,
- // thread_pool: *ThreadPool,
- output_files: std.ArrayList(options.OutputFile),
- resolve_results: *ResolveResults,
- resolve_queue: ResolveQueue,
- elapsed: i128 = 0,
- needs_runtime: bool = false,
- router: ?Router = null,
-
- linker: Linker,
- timer: Timer = Timer{},
- env: *DotEnv.Loader,
-
- // must be pointer array because we can't we don't want the source to point to invalid memory if the array size is reallocated
- virtual_modules: std.ArrayList(*ClientEntryPoint),
-
- pub const isCacheEnabled = cache_files;
-
- pub fn clone(this: *ThisBundler, allocator: *std.mem.Allocator, to: *ThisBundler) !void {
- to.* = this.*;
- to.setAllocator(allocator);
- to.log = try allocator.create(logger.Log);
- to.log.* = logger.Log.init(allocator);
- to.setLog(to.log);
- }
+pub const Bundler = struct {
+ const ThisBundler = @This();
- pub fn setLog(this: *ThisBundler, log: *logger.Log) void {
- this.log = log;
- this.linker.log = log;
- this.resolver.log = log;
- }
+ options: options.BundleOptions,
+ log: *logger.Log,
+ allocator: *std.mem.Allocator,
+ result: options.TransformResult = undefined,
+ resolver: Resolver,
+ fs: *Fs.FileSystem,
+ // thread_pool: *ThreadPool,
+ output_files: std.ArrayList(options.OutputFile),
+ resolve_results: *ResolveResults,
+ resolve_queue: ResolveQueue,
+ elapsed: i128 = 0,
+ needs_runtime: bool = false,
+ router: ?Router = null,
+
+ linker: Linker,
+ timer: Timer = Timer{},
+ env: *DotEnv.Loader,
+
+ // must be pointer array because we can't we don't want the source to point to invalid memory if the array size is reallocated
+ virtual_modules: std.ArrayList(*ClientEntryPoint),
+
+ pub const isCacheEnabled = cache_files;
+
+ pub fn clone(this: *ThisBundler, allocator: *std.mem.Allocator, to: *ThisBundler) !void {
+ to.* = this.*;
+ to.setAllocator(allocator);
+ to.log = try allocator.create(logger.Log);
+ to.log.* = logger.Log.init(allocator);
+ to.setLog(to.log);
+ }
- pub fn setAllocator(this: *ThisBundler, allocator: *std.mem.Allocator) void {
- this.allocator = allocator;
- this.linker.allocator = allocator;
- this.resolver.allocator = allocator;
- }
+ pub fn setLog(this: *ThisBundler, log: *logger.Log) void {
+ this.log = log;
+ this.linker.log = log;
+ this.resolver.log = log;
+ }
- // to_bundle:
+ pub fn setAllocator(this: *ThisBundler, allocator: *std.mem.Allocator) void {
+ this.allocator = allocator;
+ this.linker.allocator = allocator;
+ this.resolver.allocator = allocator;
+ }
- // thread_pool: *ThreadPool,
+ // to_bundle:
- pub fn init(
- allocator: *std.mem.Allocator,
- log: *logger.Log,
- opts: Api.TransformOptions,
- existing_bundle: ?*NodeModuleBundle,
- env_loader_: ?*DotEnv.Loader,
- ) !ThisBundler {
- js_ast.Expr.Data.Store.create(allocator);
- js_ast.Stmt.Data.Store.create(allocator);
- var fs = try Fs.FileSystem.init1(
- allocator,
- opts.absolute_working_dir,
- );
- const bundle_options = try options.BundleOptions.fromApi(
- allocator,
- fs,
- log,
- opts,
- existing_bundle,
- );
+ // thread_pool: *ThreadPool,
- var env_loader = env_loader_ orelse brk: {
- var map = try allocator.create(DotEnv.Map);
- map.* = DotEnv.Map.init(allocator);
+ pub fn init(
+ allocator: *std.mem.Allocator,
+ log: *logger.Log,
+ opts: Api.TransformOptions,
+ existing_bundle: ?*NodeModuleBundle,
+ env_loader_: ?*DotEnv.Loader,
+ ) !ThisBundler {
+ js_ast.Expr.Data.Store.create(allocator);
+ js_ast.Stmt.Data.Store.create(allocator);
+ var fs = try Fs.FileSystem.init1(
+ allocator,
+ opts.absolute_working_dir,
+ );
+ const bundle_options = try options.BundleOptions.fromApi(
+ allocator,
+ fs,
+ log,
+ opts,
+ existing_bundle,
+ );
- var loader = try allocator.create(DotEnv.Loader);
- loader.* = DotEnv.Loader.init(map, allocator);
- break :brk loader;
- };
- // var pool = try allocator.create(ThreadPool);
- // try pool.init(ThreadPool.InitConfig{
- // .allocator = allocator,
- // });
- var resolve_results = try allocator.create(ResolveResults);
- resolve_results.* = ResolveResults.init(allocator);
- return ThisBundler{
- .options = bundle_options,
- .fs = fs,
- .allocator = allocator,
- .resolver = Resolver.init1(allocator, log, fs, bundle_options),
- .log = log,
- // .thread_pool = pool,
- .linker = undefined,
- .result = options.TransformResult{ .outbase = bundle_options.output_dir },
- .resolve_results = resolve_results,
- .resolve_queue = ResolveQueue.init(allocator),
- .output_files = std.ArrayList(options.OutputFile).init(allocator),
- .virtual_modules = std.ArrayList(*ClientEntryPoint).init(allocator),
- .env = env_loader,
- };
- }
+ var env_loader = env_loader_ orelse brk: {
+ var map = try allocator.create(DotEnv.Map);
+ map.* = DotEnv.Map.init(allocator);
- pub fn configureLinker(bundler: *ThisBundler) void {
- bundler.linker = Linker.init(
- bundler.allocator,
- bundler.log,
- &bundler.resolve_queue,
- &bundler.options,
- &bundler.resolver,
- bundler.resolve_results,
- bundler.fs,
- );
- }
+ var loader = try allocator.create(DotEnv.Loader);
+ loader.* = DotEnv.Loader.init(map, allocator);
+ break :brk loader;
+ };
+ // var pool = try allocator.create(ThreadPool);
+ // try pool.init(ThreadPool.InitConfig{
+ // .allocator = allocator,
+ // });
+ var resolve_results = try allocator.create(ResolveResults);
+ resolve_results.* = ResolveResults.init(allocator);
+ return ThisBundler{
+ .options = bundle_options,
+ .fs = fs,
+ .allocator = allocator,
+ .resolver = Resolver.init1(allocator, log, fs, bundle_options),
+ .log = log,
+ // .thread_pool = pool,
+ .linker = undefined,
+ .result = options.TransformResult{ .outbase = bundle_options.output_dir },
+ .resolve_results = resolve_results,
+ .resolve_queue = ResolveQueue.init(allocator),
+ .output_files = std.ArrayList(options.OutputFile).init(allocator),
+ .virtual_modules = std.ArrayList(*ClientEntryPoint).init(allocator),
+ .env = env_loader,
+ };
+ }
- pub fn runEnvLoader(this: *ThisBundler) !void {
- switch (this.options.env.behavior) {
- .prefix, .load_all => {
- // Step 1. Load the project root.
- var dir: *Fs.FileSystem.DirEntry = ((this.resolver.readDirInfo(this.fs.top_level_dir) catch return) orelse return).getEntries() orelse return;
+ pub fn configureLinker(bundler: *ThisBundler) void {
+ bundler.linker = Linker.init(
+ bundler.allocator,
+ bundler.log,
+ &bundler.resolve_queue,
+ &bundler.options,
+ &bundler.resolver,
+ bundler.resolve_results,
+ bundler.fs,
+ );
+ }
- // Process always has highest priority.
- this.env.loadProcess();
- if (this.options.production) {
- try this.env.load(&this.fs.fs, dir, false);
- } else {
- try this.env.load(&this.fs.fs, dir, true);
- }
- },
- else => {},
- }
+ pub fn runEnvLoader(this: *ThisBundler) !void {
+ switch (this.options.env.behavior) {
+ .prefix, .load_all => {
+ // Step 1. Load the project root.
+ var dir: *Fs.FileSystem.DirEntry = ((this.resolver.readDirInfo(this.fs.top_level_dir) catch return) orelse return).getEntries() orelse return;
+
+ // Process always has highest priority.
+ this.env.loadProcess();
+ if (this.options.production) {
+ try this.env.load(&this.fs.fs, dir, false);
+ } else {
+ try this.env.load(&this.fs.fs, dir, true);
+ }
+ },
+ else => {},
}
+ }
- // This must be run after a framework is configured, if a framework is enabled
- pub fn configureDefines(this: *ThisBundler) !void {
- if (this.options.defines_loaded) {
- return;
- }
+ // This must be run after a framework is configured, if a framework is enabled
+ pub fn configureDefines(this: *ThisBundler) !void {
+ if (this.options.defines_loaded) {
+ return;
+ }
- try this.runEnvLoader();
+ try this.runEnvLoader();
- js_ast.Expr.Data.Store.create(this.allocator);
- js_ast.Stmt.Data.Store.create(this.allocator);
- defer js_ast.Expr.Data.Store.reset();
- defer js_ast.Stmt.Data.Store.reset();
+ js_ast.Expr.Data.Store.create(this.allocator);
+ js_ast.Stmt.Data.Store.create(this.allocator);
+ defer js_ast.Expr.Data.Store.reset();
+ defer js_ast.Stmt.Data.Store.reset();
- if (this.options.framework) |framework| {
- if (this.options.platform.isClient()) {
- try this.options.loadDefines(this.allocator, this.env, &framework.client.env);
- } else {
- try this.options.loadDefines(this.allocator, this.env, &framework.server.env);
- }
+ if (this.options.framework) |framework| {
+ if (this.options.platform.isClient()) {
+ try this.options.loadDefines(this.allocator, this.env, &framework.client.env);
} else {
- try this.options.loadDefines(this.allocator, this.env, &this.options.env);
+ try this.options.loadDefines(this.allocator, this.env, &framework.server.env);
}
+ } else {
+ try this.options.loadDefines(this.allocator, this.env, &this.options.env);
}
+ }
- pub fn configureFramework(
- this: *ThisBundler,
- comptime load_defines: bool,
- ) !void {
- if (this.options.framework) |*framework| {
- if (framework.needsResolveFromPackage()) {
- var route_config = this.options.routes;
- var pair = PackageJSON.FrameworkRouterPair{ .framework = framework, .router = &route_config };
+ pub fn configureFramework(
+ this: *ThisBundler,
+ comptime load_defines: bool,
+ ) !void {
+ if (this.options.framework) |*framework| {
+ if (framework.needsResolveFromPackage()) {
+ var route_config = this.options.routes;
+ var pair = PackageJSON.FrameworkRouterPair{ .framework = framework, .router = &route_config };
- if (framework.development) {
- try this.resolver.resolveFramework(framework.package, &pair, .development, load_defines);
- } else {
- try this.resolver.resolveFramework(framework.package, &pair, .production, load_defines);
- }
+ if (framework.development) {
+ try this.resolver.resolveFramework(framework.package, &pair, .development, load_defines);
+ } else {
+ try this.resolver.resolveFramework(framework.package, &pair, .production, load_defines);
+ }
- if (this.options.areDefinesUnset()) {
- if (this.options.platform.isClient()) {
- this.options.env = framework.client.env;
- } else {
- this.options.env = framework.server.env;
- }
+ if (this.options.areDefinesUnset()) {
+ if (this.options.platform.isClient()) {
+ this.options.env = framework.client.env;
+ } else {
+ this.options.env = framework.server.env;
}
+ }
- if (pair.loaded_routes) {
- this.options.routes = route_config;
- }
- framework.resolved = true;
- this.options.framework = framework.*;
- } else if (!framework.resolved) {
- Global.panic("directly passing framework path is not implemented yet!", .{});
+ if (pair.loaded_routes) {
+ this.options.routes = route_config;
}
+ framework.resolved = true;
+ this.options.framework = framework.*;
+ } else if (!framework.resolved) {
+ Global.panic("directly passing framework path is not implemented yet!", .{});
}
}
+ }
- pub fn configureFrameworkWithResolveResult(this: *ThisBundler, comptime client: bool) !?_resolver.Result {
- if (this.options.framework != null) {
- try this.configureFramework(true);
- if (comptime client) {
- if (this.options.framework.?.client.isEnabled()) {
- return try this.resolver.resolve(this.fs.top_level_dir, this.options.framework.?.client.path, .stmt);
- }
+ pub fn configureFrameworkWithResolveResult(this: *ThisBundler, comptime client: bool) !?_resolver.Result {
+ if (this.options.framework != null) {
+ try this.configureFramework(true);
+ if (comptime client) {
+ if (this.options.framework.?.client.isEnabled()) {
+ return try this.resolver.resolve(this.fs.top_level_dir, this.options.framework.?.client.path, .stmt);
+ }
- if (this.options.framework.?.fallback.isEnabled()) {
- return try this.resolver.resolve(this.fs.top_level_dir, this.options.framework.?.fallback.path, .stmt);
- }
- } else {
- if (this.options.framework.?.server.isEnabled()) {
- return try this.resolver.resolve(this.fs.top_level_dir, this.options.framework.?.server, .stmt);
- }
+ if (this.options.framework.?.fallback.isEnabled()) {
+ return try this.resolver.resolve(this.fs.top_level_dir, this.options.framework.?.fallback.path, .stmt);
+ }
+ } else {
+ if (this.options.framework.?.server.isEnabled()) {
+ return try this.resolver.resolve(this.fs.top_level_dir, this.options.framework.?.server, .stmt);
}
}
-
- return null;
}
- pub fn configureRouter(this: *ThisBundler, comptime load_defines: bool) !void {
- try this.configureFramework(load_defines);
- defer {
- if (load_defines) {
- this.configureDefines() catch {};
- }
+ return null;
+ }
+
+ pub fn configureRouter(this: *ThisBundler, comptime load_defines: bool) !void {
+ try this.configureFramework(load_defines);
+ defer {
+ if (load_defines) {
+ this.configureDefines() catch {};
}
+ }
- // if you pass just a directory, activate the router configured for the pages directory
- // for now:
- // - "." is not supported
- // - multiple pages directories is not supported
- if (!this.options.routes.routes_enabled and this.options.entry_points.len == 1 and !this.options.serve) {
-
- // When inferring:
- // - pages directory with a file extension is not supported. e.g. "pages.app/" won't work.
- // This is a premature optimization to avoid this magical auto-detection we do here from meaningfully increasing startup time if you're just passing a file
- // readDirInfo is a recursive lookup, top-down instead of bottom-up. It opens each folder handle and potentially reads the package.jsons
- // So it is not fast! Unless it's already cached.
- var paths = [_]string{std.mem.trimLeft(u8, this.options.entry_points[0], "./")};
- if (std.mem.indexOfScalar(u8, paths[0], '.') == null) {
- var pages_dir_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- var entry = this.fs.absBuf(&paths, &pages_dir_buf);
-
- if (std.fs.path.extension(entry).len == 0) {
- allocators.constStrToU8(entry).ptr[entry.len] = '/';
-
- // Only throw if they actually passed in a route config and the directory failed to load
- var dir_info_ = this.resolver.readDirInfo(entry) catch return;
- var dir_info = dir_info_ orelse return;
-
- this.options.routes.dir = dir_info.abs_path;
- this.options.routes.extensions = std.mem.span(&options.RouteConfig.DefaultExtensions);
- this.options.routes.routes_enabled = true;
- this.router = try Router.init(this.fs, this.allocator, this.options.routes);
- try this.router.?.loadRoutes(
- dir_info,
- Resolver,
- &this.resolver,
- std.math.maxInt(u16),
- true,
- );
- this.router.?.routes.client_framework_enabled = this.options.isFrontendFrameworkEnabled();
- return;
- }
+ // if you pass just a directory, activate the router configured for the pages directory
+ // for now:
+ // - "." is not supported
+ // - multiple pages directories is not supported
+ if (!this.options.routes.routes_enabled and this.options.entry_points.len == 1 and !this.options.serve) {
+
+ // When inferring:
+ // - pages directory with a file extension is not supported. e.g. "pages.app/" won't work.
+ // This is a premature optimization to avoid this magical auto-detection we do here from meaningfully increasing startup time if you're just passing a file
+ // readDirInfo is a recursive lookup, top-down instead of bottom-up. It opens each folder handle and potentially reads the package.jsons
+ // So it is not fast! Unless it's already cached.
+ var paths = [_]string{std.mem.trimLeft(u8, this.options.entry_points[0], "./")};
+ if (std.mem.indexOfScalar(u8, paths[0], '.') == null) {
+ var pages_dir_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ var entry = this.fs.absBuf(&paths, &pages_dir_buf);
+
+ if (std.fs.path.extension(entry).len == 0) {
+ allocators.constStrToU8(entry).ptr[entry.len] = '/';
+
+ // Only throw if they actually passed in a route config and the directory failed to load
+ var dir_info_ = this.resolver.readDirInfo(entry) catch return;
+ var dir_info = dir_info_ orelse return;
+
+ this.options.routes.dir = dir_info.abs_path;
+ this.options.routes.extensions = std.mem.span(&options.RouteConfig.DefaultExtensions);
+ this.options.routes.routes_enabled = true;
+ this.router = try Router.init(this.fs, this.allocator, this.options.routes);
+ try this.router.?.loadRoutes(
+ dir_info,
+ Resolver,
+ &this.resolver,
+ std.math.maxInt(u16),
+ true,
+ );
+ this.router.?.routes.client_framework_enabled = this.options.isFrontendFrameworkEnabled();
+ return;
}
- } else if (this.options.routes.routes_enabled) {
- var dir_info_ = try this.resolver.readDirInfo(this.options.routes.dir);
- var dir_info = dir_info_ orelse return error.MissingRoutesDir;
-
- this.options.routes.dir = dir_info.abs_path;
-
- this.router = try Router.init(this.fs, this.allocator, this.options.routes);
- try this.router.?.loadRoutes(dir_info, Resolver, &this.resolver, std.math.maxInt(u16), true);
- this.router.?.routes.client_framework_enabled = this.options.isFrontendFrameworkEnabled();
- return;
}
+ } else if (this.options.routes.routes_enabled) {
+ var dir_info_ = try this.resolver.readDirInfo(this.options.routes.dir);
+ var dir_info = dir_info_ orelse return error.MissingRoutesDir;
- // If we get this far, it means they're trying to run the bundler without a preconfigured router
- if (this.options.entry_points.len > 0) {
- this.options.routes.routes_enabled = false;
- }
+ this.options.routes.dir = dir_info.abs_path;
- if (this.router) |*router| {
- router.routes.client_framework_enabled = this.options.isFrontendFrameworkEnabled();
- }
+ this.router = try Router.init(this.fs, this.allocator, this.options.routes);
+ try this.router.?.loadRoutes(dir_info, Resolver, &this.resolver, std.math.maxInt(u16), true);
+ this.router.?.routes.client_framework_enabled = this.options.isFrontendFrameworkEnabled();
+ return;
}
- pub fn resetStore(bundler: *ThisBundler) void {
- js_ast.Expr.Data.Store.reset();
- js_ast.Stmt.Data.Store.reset();
+ // If we get this far, it means they're trying to run the bundler without a preconfigured router
+ if (this.options.entry_points.len > 0) {
+ this.options.routes.routes_enabled = false;
}
- pub const GenerateNodeModuleBundle = struct {
- const BunQueue = NewBunQueue(_resolver.Result);
-
- pub const ThreadPool = struct {
- // Hardcode 512 as max number of threads for now.
- workers: [512]Worker = undefined,
- workers_used: u32 = 0,
- cpu_count: u32 = 0,
- started_workers: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
- stopped_workers: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
- completed_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
- pub fn start(this: *ThreadPool, generator: *GenerateNodeModuleBundle) !void {
- generator.bundler.env.loadProcess();
-
- this.cpu_count = @truncate(u32, @divFloor((try std.Thread.getCpuCount()) + 1, 2));
-
- if (generator.bundler.env.map.get("GOMAXPROCS")) |max_procs| {
- if (std.fmt.parseInt(u32, max_procs, 10)) |cpu_count| {
- this.cpu_count = std.math.min(this.cpu_count, cpu_count);
- } else |err| {}
- }
+ if (this.router) |*router| {
+ router.routes.client_framework_enabled = this.options.isFrontendFrameworkEnabled();
+ }
+ }
- if (this.cpu_count <= 1) return;
+ pub fn resetStore(bundler: *ThisBundler) void {
+ js_ast.Expr.Data.Store.reset();
+ js_ast.Stmt.Data.Store.reset();
+ }
- while (this.workers_used < this.cpu_count) : (this.workers_used += 1) {
- try this.workers[this.workers_used].init(generator);
- }
+ pub const GenerateNodeModuleBundle = struct {
+ const BunQueue = NewBunQueue(_resolver.Result);
+
+ pub const ThreadPool = struct {
+ // Hardcode 512 as max number of threads for now.
+ workers: [512]Worker = undefined,
+ workers_used: u32 = 0,
+ cpu_count: u32 = 0,
+ started_workers: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
+ stopped_workers: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
+ completed_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
+ pub fn start(this: *ThreadPool, generator: *GenerateNodeModuleBundle) !void {
+ generator.bundler.env.loadProcess();
+
+ this.cpu_count = @truncate(u32, @divFloor((try std.Thread.getCpuCount()) + 1, 2));
+
+ if (generator.bundler.env.map.get("GOMAXPROCS")) |max_procs| {
+ if (std.fmt.parseInt(u32, max_procs, 10)) |cpu_count| {
+ this.cpu_count = std.math.min(this.cpu_count, cpu_count);
+ } else |err| {}
}
- pub fn wait(this: *ThreadPool, generator: *GenerateNodeModuleBundle) !void {
- if (this.cpu_count <= 1) {
- var worker = generator.allocator.create(Worker) catch unreachable;
- worker.* = Worker{
- .generator = generator,
- .allocator = generator.allocator,
- .data = generator.allocator.create(Worker.WorkerData) catch unreachable,
- .thread_id = undefined,
- .thread = undefined,
- };
- worker.data.shared_buffer = try MutableString.init(generator.allocator, 0);
- worker.data.scan_pass_result = js_parser.ScanPassResult.init(generator.allocator);
- worker.data.log = generator.log;
+ if (this.cpu_count <= 1) return;
- defer {
- worker.data.deinit(generator.allocator);
- }
+ while (this.workers_used < this.cpu_count) : (this.workers_used += 1) {
+ try this.workers[this.workers_used].init(generator);
+ }
+ }
- while (generator.queue.next()) |item| {
- try generator.processFile(worker, item);
- }
+ pub fn wait(this: *ThreadPool, generator: *GenerateNodeModuleBundle) !void {
+ if (this.cpu_count <= 1) {
+ var worker = generator.allocator.create(Worker) catch unreachable;
+ worker.* = Worker{
+ .generator = generator,
+ .allocator = generator.allocator,
+ .data = generator.allocator.create(Worker.WorkerData) catch unreachable,
+ .thread_id = undefined,
+ .thread = undefined,
+ };
+ worker.data.shared_buffer = try MutableString.init(generator.allocator, 0);
+ worker.data.scan_pass_result = js_parser.ScanPassResult.init(generator.allocator);
+ worker.data.log = generator.log;
- generator.estimated_input_lines_of_code = worker.data.estimated_input_lines_of_code;
- return;
+ defer {
+ worker.data.deinit(generator.allocator);
}
- while (generator.queue.count.load(.SeqCst) != generator.pool.completed_count.load(.SeqCst)) {
- var j: usize = 0;
- while (j < 100) : (j += 1) {}
- std.atomic.spinLoopHint();
+ while (generator.queue.next()) |item| {
+ try generator.processFile(worker, item);
}
- for (this.workers[0..this.workers_used]) |*worker| {
- @atomicStore(bool, &worker.quit, true, .Release);
- }
+ generator.estimated_input_lines_of_code = worker.data.estimated_input_lines_of_code;
+ return;
+ }
- while (this.stopped_workers.load(.Acquire) != this.workers_used) {
- var j: usize = 0;
- while (j < 100) : (j += 1) {}
- std.atomic.spinLoopHint();
- }
+ while (generator.queue.count.load(.SeqCst) != generator.pool.completed_count.load(.SeqCst)) {
+ var j: usize = 0;
+ while (j < 100) : (j += 1) {}
+ std.atomic.spinLoopHint();
+ }
- for (this.workers[0..this.workers_used]) |*worker| {
- worker.thread.join();
- }
+ for (this.workers[0..this.workers_used]) |*worker| {
+ @atomicStore(bool, &worker.quit, true, .Release);
}
- pub const Task = struct {
- result: _resolver.Result,
- generator: *GenerateNodeModuleBundle,
- };
+ while (this.stopped_workers.load(.Acquire) != this.workers_used) {
+ var j: usize = 0;
+ while (j < 100) : (j += 1) {}
+ std.atomic.spinLoopHint();
+ }
- pub const Worker = struct {
- thread_id: std.Thread.Id,
- thread: std.Thread,
+ for (this.workers[0..this.workers_used]) |*worker| {
+ worker.thread.join();
+ }
+ }
- allocator: *std.mem.Allocator,
- generator: *GenerateNodeModuleBundle,
- data: *WorkerData = undefined,
- quit: bool = false,
+ pub const Task = struct {
+ result: _resolver.Result,
+ generator: *GenerateNodeModuleBundle,
+ };
- has_notify_started: bool = false,
+ pub const Worker = struct {
+ thread_id: std.Thread.Id,
+ thread: std.Thread,
- pub const WorkerData = struct {
- shared_buffer: MutableString = undefined,
- scan_pass_result: js_parser.ScanPassResult = undefined,
- log: *logger.Log,
- estimated_input_lines_of_code: usize = 0,
+ allocator: *std.mem.Allocator,
+ generator: *GenerateNodeModuleBundle,
+ data: *WorkerData = undefined,
+ quit: bool = false,
+
+ has_notify_started: bool = false,
+
+ pub const WorkerData = struct {
+ shared_buffer: MutableString = undefined,
+ scan_pass_result: js_parser.ScanPassResult = undefined,
+ log: *logger.Log,
+ estimated_input_lines_of_code: usize = 0,
+
+ pub fn deinit(this: *WorkerData, allocator: *std.mem.Allocator) void {
+ this.shared_buffer.deinit();
+ this.scan_pass_result.named_imports.deinit();
+ this.scan_pass_result.import_records.deinit();
+ allocator.destroy(this);
+ }
+ };
- pub fn deinit(this: *WorkerData, allocator: *std.mem.Allocator) void {
- this.shared_buffer.deinit();
- this.scan_pass_result.named_imports.deinit();
- this.scan_pass_result.import_records.deinit();
- allocator.destroy(this);
- }
- };
+ pub fn init(worker: *Worker, generator: *GenerateNodeModuleBundle) !void {
+ worker.generator = generator;
+ worker.allocator = generator.allocator;
+ worker.thread = try std.Thread.spawn(.{}, Worker.run, .{worker});
+ }
- pub fn init(worker: *Worker, generator: *GenerateNodeModuleBundle) !void {
- worker.generator = generator;
- worker.allocator = generator.allocator;
- worker.thread = try std.Thread.spawn(.{}, Worker.run, .{worker});
+ pub fn notifyStarted(this: *Worker) void {
+ if (!this.has_notify_started) {
+ this.has_notify_started = true;
+ _ = this.generator.pool.started_workers.fetchAdd(1, .Release);
+ std.Thread.Futex.wake(&this.generator.pool.started_workers, std.math.maxInt(u32));
}
+ }
- pub fn notifyStarted(this: *Worker) void {
- if (!this.has_notify_started) {
- this.has_notify_started = true;
- _ = this.generator.pool.started_workers.fetchAdd(1, .Release);
- std.Thread.Futex.wake(&this.generator.pool.started_workers, std.math.maxInt(u32));
- }
+ pub fn run(this: *Worker) void {
+ Output.Source.configureThread();
+ this.thread_id = std.Thread.getCurrentId();
+ if (isDebug) {
+ Output.prettyln("Thread started.\n", .{});
}
-
- pub fn run(this: *Worker) void {
- Output.Source.configureThread();
- this.thread_id = std.Thread.getCurrentId();
+ defer {
if (isDebug) {
- Output.prettyln("Thread started.\n", .{});
+ Output.prettyln("Thread stopped.\n", .{});
}
- defer {
- if (isDebug) {
- Output.prettyln("Thread stopped.\n", .{});
- }
- Output.flush();
- }
-
- this.loop() catch |err| {
- Output.prettyErrorln("<r><red>Error: {s}<r>", .{@errorName(err)});
- };
+ Output.flush();
}
- pub fn loop(this: *Worker) anyerror!void {
- defer {
- _ = this.generator.pool.stopped_workers.fetchAdd(1, .Release);
- this.notifyStarted();
+ this.loop() catch |err| {
+ Output.prettyErrorln("<r><red>Error: {s}<r>", .{@errorName(err)});
+ };
+ }
- std.Thread.Futex.wake(&this.generator.pool.stopped_workers, 1);
- // std.Thread.Futex.wake(&this.generator.queue.len, std.math.maxInt(u32));
- }
+ pub fn loop(this: *Worker) anyerror!void {
+ defer {
+ _ = this.generator.pool.stopped_workers.fetchAdd(1, .Release);
+ this.notifyStarted();
- js_ast.Expr.Data.Store.create(this.generator.allocator);
- js_ast.Stmt.Data.Store.create(this.generator.allocator);
- this.data = this.generator.allocator.create(WorkerData) catch unreachable;
- this.data.* = WorkerData{
- .log = this.generator.allocator.create(logger.Log) catch unreachable,
- .estimated_input_lines_of_code = 0,
- };
- this.data.log.* = logger.Log.init(this.generator.allocator);
- this.data.shared_buffer = try MutableString.init(this.generator.allocator, 0);
- this.data.scan_pass_result = js_parser.ScanPassResult.init(this.generator.allocator);
-
- defer {
- {
- this.generator.log_lock.lock();
- this.data.log.appendTo(this.generator.log) catch {};
- this.generator.estimated_input_lines_of_code += this.data.estimated_input_lines_of_code;
- this.generator.log_lock.unlock();
- }
+ std.Thread.Futex.wake(&this.generator.pool.stopped_workers, 1);
+ // std.Thread.Futex.wake(&this.generator.queue.len, std.math.maxInt(u32));
+ }
- this.data.deinit(this.generator.allocator);
+ js_ast.Expr.Data.Store.create(this.generator.allocator);
+ js_ast.Stmt.Data.Store.create(this.generator.allocator);
+ this.data = this.generator.allocator.create(WorkerData) catch unreachable;
+ this.data.* = WorkerData{
+ .log = this.generator.allocator.create(logger.Log) catch unreachable,
+ .estimated_input_lines_of_code = 0,
+ };
+ this.data.log.* = logger.Log.init(this.generator.allocator);
+ this.data.shared_buffer = try MutableString.init(this.generator.allocator, 0);
+ this.data.scan_pass_result = js_parser.ScanPassResult.init(this.generator.allocator);
+
+ defer {
+ {
+ this.generator.log_lock.lock();
+ this.data.log.appendTo(this.generator.log) catch {};
+ this.generator.estimated_input_lines_of_code += this.data.estimated_input_lines_of_code;
+ this.generator.log_lock.unlock();
}
- this.notifyStarted();
+ this.data.deinit(this.generator.allocator);
+ }
- while (!@atomicLoad(bool, &this.quit, .Acquire)) {
- while (this.generator.queue.next()) |item| {
- defer {
- _ = this.generator.pool.completed_count.fetchAdd(1, .Release);
- }
+ this.notifyStarted();
- try this.generator.processFile(this, item);
+ while (!@atomicLoad(bool, &this.quit, .Acquire)) {
+ while (this.generator.queue.next()) |item| {
+ defer {
+ _ = this.generator.pool.completed_count.fetchAdd(1, .Release);
}
+
+ try this.generator.processFile(this, item);
}
}
- };
- };
- write_lock: Lock,
- log_lock: Lock = Lock.init(),
- module_list: std.ArrayList(Api.JavascriptBundledModule),
- package_list: std.ArrayList(Api.JavascriptBundledPackage),
- header_string_buffer: MutableString,
-
- // Just need to know if we've already enqueued this one
- package_list_map: std.AutoHashMap(u64, u32),
- queue: *BunQueue,
- bundler: *ThisBundler,
- allocator: *std.mem.Allocator,
- tmpfile: std.fs.File,
- log: *logger.Log,
- pool: *ThreadPool,
- tmpfile_byte_offset: u32 = 0,
- code_end_byte_offset: u32 = 0,
- has_jsx: bool = false,
- estimated_input_lines_of_code: usize = 0,
-
- work_waiter: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
- list_lock: Lock = Lock.init(),
-
- dynamic_import_file_size_store: U32Map,
- dynamic_import_file_size_store_lock: Lock,
-
- always_bundled_package_hashes: []u32 = &[_]u32{},
- always_bundled_package_jsons: []*const PackageJSON = &.{},
-
- const U32Map = std.AutoHashMap(u32, u32);
- pub const current_version: u32 = 1;
- const dist_index_js_string_pointer = Api.StringPointer{ .length = "dist/index.js".len };
- const index_js_string_pointer = Api.StringPointer{ .length = "index.js".len, .offset = "dist/".len };
-
- pub fn enqueueItem(this: *GenerateNodeModuleBundle, resolve: _resolver.Result) !void {
- var result = resolve;
- var path = result.path() orelse return;
-
- const loader = this.bundler.options.loaders.get(path.name.ext) orelse .file;
- if (!loader.isJavaScriptLikeOrJSON()) return;
- path.* = try path.dupeAlloc(this.allocator);
-
- if (BundledModuleData.get(this, &result)) |mod| {
- try this.queue.upsert(mod.module_id, result);
- } else {
- try this.queue.upsert(result.hash(this.bundler.fs.top_level_dir, loader), result);
}
- }
-
- // The Bun Bundle Format
- // All the node_modules your app uses in a single compact file with metadata
- // A binary JavaScript bundle format prioritizing generation time and deserialization time
- pub const magic_bytes = "#!/usr/bin/env bun\n\n";
- // This makes it possible to do ./path-to-bundle on posix systems so you can see the raw JS contents
- // https://en.wikipedia.org/wiki/Magic_number_(programming)#In_files
- // Immediately after the magic bytes, the next character is a uint32 followed by a newline
- // 0x00000000\n
- // That uint32 denotes the byte offset in the file where the code for the bundle ends
- // - If the value is 0, that means the file did not finish writing or there are no modules
- // - This imposes a maximum bundle size of around 4,294,967,295 bytes. If your JS is more than 4 GB, it won't work.
- // The raw JavaScript is encoded as a UTF-8 string starting from the current position + 1 until the above byte offset.
- // This uint32 is useful for HTTP servers to separate:
- // - Which part of the bundle is the JS code?
- // - Which part is the metadata?
- // Without needing to do a full pass through the file, or necessarily care about the metadata.
- // The metadata is at the bottom of the file instead of the top because the metadata is written after all JS code in the bundle is written.
- // The rationale there is:
- // 1. We cannot prepend to a file without rewriting the entire file
- // 2. The metadata is variable-length and that format will change often.
- // 3. We won't have all the metadata until after all JS is finished writing
- // If you have 32 MB of JavaScript dependencies, you really want to avoid reading the code in memory.
- // - This lets you seek to the specific position in the file.
- // - HTTP servers should use sendfile() instead of copying the file to userspace memory.
- // So instead, we append metadata to the file after printing each node_module
- // When there are no more modules to process, we generate the metadata
- // To find the metadata, you look at the byte offset: initial_header[magic_bytes.len..initial_header.len - 1]
- // Then, you add that number to initial_header.len
- const initial_header = brk: {
- var buf = std.mem.zeroes([magic_bytes.len + 5]u8);
- std.mem.copy(u8, &buf, magic_bytes);
- var remainder = buf[magic_bytes.len..];
- // Write an invalid byte offset to be updated after we finish generating the code
- std.mem.writeIntNative(u32, remainder[0 .. remainder.len - 1], 0);
- buf[buf.len - 1] = '\n';
- break :brk buf;
};
- const code_start_byte_offset: u32 = initial_header.len;
- // The specifics of the metadata is not documented here. You can find it in src/api/schema.peechy.
-
- pub fn appendHeaderString(generator: *GenerateNodeModuleBundle, str: string) !Api.StringPointer {
- // This is so common we might as well just reuse it
- // Plus this is one machine word so it's a quick comparison
- if (strings.eqlComptime(str, "index.js")) {
- return index_js_string_pointer;
- } else if (strings.eqlComptime(str, "dist/index.js")) {
- return dist_index_js_string_pointer;
- }
+ };
+ write_lock: Lock,
+ log_lock: Lock = Lock.init(),
+ module_list: std.ArrayList(Api.JavascriptBundledModule),
+ package_list: std.ArrayList(Api.JavascriptBundledPackage),
+ header_string_buffer: MutableString,
+
+ // Just need to know if we've already enqueued this one
+ package_list_map: std.AutoHashMap(u64, u32),
+ queue: *BunQueue,
+ bundler: *ThisBundler,
+ allocator: *std.mem.Allocator,
+ tmpfile: std.fs.File,
+ log: *logger.Log,
+ pool: *ThreadPool,
+ tmpfile_byte_offset: u32 = 0,
+ code_end_byte_offset: u32 = 0,
+ has_jsx: bool = false,
+ estimated_input_lines_of_code: usize = 0,
- var offset = generator.header_string_buffer.list.items.len;
- try generator.header_string_buffer.append(str);
- return Api.StringPointer{
- .offset = @truncate(u32, offset),
- .length = @truncate(u32, str.len),
- };
+ work_waiter: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
+ list_lock: Lock = Lock.init(),
+
+ dynamic_import_file_size_store: U32Map,
+ dynamic_import_file_size_store_lock: Lock,
+
+ always_bundled_package_hashes: []u32 = &[_]u32{},
+ always_bundled_package_jsons: []*const PackageJSON = &.{},
+
+ const U32Map = std.AutoHashMap(u32, u32);
+ pub const current_version: u32 = 1;
+ const dist_index_js_string_pointer = Api.StringPointer{ .length = "dist/index.js".len };
+ const index_js_string_pointer = Api.StringPointer{ .length = "index.js".len, .offset = "dist/".len };
+
+ pub fn enqueueItem(this: *GenerateNodeModuleBundle, resolve: _resolver.Result) !void {
+ var result = resolve;
+ var path = result.path() orelse return;
+
+ const loader = this.bundler.options.loaders.get(path.name.ext) orelse .file;
+ if (!loader.isJavaScriptLikeOrJSON()) return;
+ path.* = try path.dupeAlloc(this.allocator);
+
+ if (BundledModuleData.get(this, &result)) |mod| {
+ try this.queue.upsert(mod.module_id, result);
+ } else {
+ try this.queue.upsert(result.hash(this.bundler.fs.top_level_dir, loader), result);
}
+ }
- pub fn generate(
- bundler: *ThisBundler,
- allocator: *std.mem.Allocator,
- framework_config: ?Api.LoadedFramework,
- route_config: ?Api.LoadedRouteConfig,
- destination: [*:0]const u8,
- estimated_input_lines_of_code: *usize,
- ) !?Api.JavascriptBundleContainer {
- var tmpdir: std.fs.Dir = try bundler.fs.fs.openTmpDir();
- var tmpname_buf: [64]u8 = undefined;
- bundler.resetStore();
- try bundler.configureDefines();
-
- const tmpname = try bundler.fs.tmpname(
- ".bun",
- std.mem.span(&tmpname_buf),
- std.hash.Wyhash.hash(0, std.mem.span(destination)),
- );
+ // The Bun Bundle Format
+ // All the node_modules your app uses in a single compact file with metadata
+ // A binary JavaScript bundle format prioritizing generation time and deserialization time
+ pub const magic_bytes = "#!/usr/bin/env bun\n\n";
+ // This makes it possible to do ./path-to-bundle on posix systems so you can see the raw JS contents
+ // https://en.wikipedia.org/wiki/Magic_number_(programming)#In_files
+ // Immediately after the magic bytes, the next character is a uint32 followed by a newline
+ // 0x00000000\n
+ // That uint32 denotes the byte offset in the file where the code for the bundle ends
+ // - If the value is 0, that means the file did not finish writing or there are no modules
+ // - This imposes a maximum bundle size of around 4,294,967,295 bytes. If your JS is more than 4 GB, it won't work.
+ // The raw JavaScript is encoded as a UTF-8 string starting from the current position + 1 until the above byte offset.
+ // This uint32 is useful for HTTP servers to separate:
+ // - Which part of the bundle is the JS code?
+ // - Which part is the metadata?
+ // Without needing to do a full pass through the file, or necessarily care about the metadata.
+ // The metadata is at the bottom of the file instead of the top because the metadata is written after all JS code in the bundle is written.
+ // The rationale there is:
+ // 1. We cannot prepend to a file without rewriting the entire file
+ // 2. The metadata is variable-length and that format will change often.
+ // 3. We won't have all the metadata until after all JS is finished writing
+ // If you have 32 MB of JavaScript dependencies, you really want to avoid reading the code in memory.
+ // - This lets you seek to the specific position in the file.
+ // - HTTP servers should use sendfile() instead of copying the file to userspace memory.
+ // So instead, we append metadata to the file after printing each node_module
+ // When there are no more modules to process, we generate the metadata
+ // To find the metadata, you look at the byte offset: initial_header[magic_bytes.len..initial_header.len - 1]
+ // Then, you add that number to initial_header.len
+ const initial_header = brk: {
+ var buf = std.mem.zeroes([magic_bytes.len + 5]u8);
+ std.mem.copy(u8, &buf, magic_bytes);
+ var remainder = buf[magic_bytes.len..];
+ // Write an invalid byte offset to be updated after we finish generating the code
+ std.mem.writeIntNative(u32, remainder[0 .. remainder.len - 1], 0);
+ buf[buf.len - 1] = '\n';
+ break :brk buf;
+ };
+ const code_start_byte_offset: u32 = initial_header.len;
+ // The specifics of the metadata is not documented here. You can find it in src/api/schema.peechy.
+
+ pub fn appendHeaderString(generator: *GenerateNodeModuleBundle, str: string) !Api.StringPointer {
+ // This is so common we might as well just reuse it
+ // Plus this is one machine word so it's a quick comparison
+ if (strings.eqlComptime(str, "index.js")) {
+ return index_js_string_pointer;
+ } else if (strings.eqlComptime(str, "dist/index.js")) {
+ return dist_index_js_string_pointer;
+ }
- var tmpfile = try tmpdir.createFileZ(tmpname, .{ .read = isDebug, .exclusive = true });
+ var offset = generator.header_string_buffer.list.items.len;
+ try generator.header_string_buffer.append(str);
+ return Api.StringPointer{
+ .offset = @truncate(u32, offset),
+ .length = @truncate(u32, str.len),
+ };
+ }
- errdefer {
- tmpfile.close();
- tmpdir.deleteFile(std.mem.span(tmpname)) catch {};
- }
+ pub fn generate(
+ bundler: *ThisBundler,
+ allocator: *std.mem.Allocator,
+ framework_config: ?Api.LoadedFramework,
+ route_config: ?Api.LoadedRouteConfig,
+ destination: [*:0]const u8,
+ estimated_input_lines_of_code: *usize,
+ ) !?Api.JavascriptBundleContainer {
+ var tmpdir: std.fs.Dir = try bundler.fs.fs.openTmpDir();
+ var tmpname_buf: [64]u8 = undefined;
+ bundler.resetStore();
+ try bundler.configureDefines();
- var generator = try allocator.create(GenerateNodeModuleBundle);
- var queue = try BunQueue.init(allocator);
- defer allocator.destroy(generator);
- generator.* = GenerateNodeModuleBundle{
- .module_list = std.ArrayList(Api.JavascriptBundledModule).init(allocator),
- .package_list = std.ArrayList(Api.JavascriptBundledPackage).init(allocator),
- .header_string_buffer = try MutableString.init(allocator, "dist/index.js".len),
- .allocator = allocator,
- .queue = queue,
- .estimated_input_lines_of_code = 0,
- // .resolve_queue = queue,
- .bundler = bundler,
- .tmpfile = tmpfile,
-
- .dynamic_import_file_size_store = U32Map.init(allocator),
- .dynamic_import_file_size_store_lock = Lock.init(),
- .log = bundler.log,
- .package_list_map = std.AutoHashMap(u64, u32).init(allocator),
- .pool = undefined,
- .write_lock = Lock.init(),
- };
- // dist/index.js appears more common than /index.js
- // but this means we can store both "dist/index.js" and "index.js" in one.
- try generator.header_string_buffer.append("dist/index.js");
- try generator.package_list_map.ensureTotalCapacity(128);
- var pool = try allocator.create(ThreadPool);
- pool.* = ThreadPool{};
- generator.pool = pool;
-
- var this = generator;
- // Always inline the runtime into the bundle
- try generator.appendBytes(&initial_header);
- // If we try to be smart and rely on .written, it turns out incorrect
- const code_start_pos = try this.tmpfile.getPos();
- if (isDebug) {
- try generator.appendBytes(runtime.Runtime.sourceContent());
- try generator.appendBytes("\n\n");
- } else {
- try generator.appendBytes(comptime runtime.Runtime.sourceContent() ++ "\n\n");
- }
+ const tmpname = try bundler.fs.tmpname(
+ ".bun",
+ std.mem.span(&tmpname_buf),
+ std.hash.Wyhash.hash(0, std.mem.span(destination)),
+ );
- if (bundler.log.level == .verbose) {
- bundler.resolver.debug_logs = try DebugLogs.init(allocator);
- }
+ var tmpfile = try tmpdir.createFileZ(tmpname, .{ .read = isDebug, .exclusive = true });
- always_bundled: {
- const root_package_json_resolved: _resolver.Result = bundler.resolver.resolve(bundler.fs.top_level_dir, "./package.json", .stmt) catch |err| {
- generator.log.addWarning(null, logger.Loc.Empty, "Please run `bun bun` from a directory containing a package.json.") catch unreachable;
- break :always_bundled;
- };
- const root_package_json = root_package_json_resolved.package_json orelse brk: {
- const read_dir = (bundler.resolver.readDirInfo(bundler.fs.top_level_dir) catch unreachable).?;
- break :brk read_dir.package_json.?;
- };
- if (root_package_json.always_bundle.len > 0) {
- var always_bundled_package_jsons = bundler.allocator.alloc(*PackageJSON, root_package_json.always_bundle.len) catch unreachable;
- var always_bundled_package_hashes = bundler.allocator.alloc(u32, root_package_json.always_bundle.len) catch unreachable;
- var i: u16 = 0;
-
- inner: for (root_package_json.always_bundle) |name| {
- std.mem.copy(u8, &tmp_buildfile_buf, name);
- std.mem.copy(u8, tmp_buildfile_buf[name.len..], "/package.json");
- const package_json_import = tmp_buildfile_buf[0 .. name.len + "/package.json".len];
- const result = bundler.resolver.resolve(bundler.fs.top_level_dir, package_json_import, .stmt) catch |err| {
- generator.log.addErrorFmt(null, logger.Loc.Empty, bundler.allocator, "{s} resolving always bundled module \"{s}\"", .{ @errorName(err), name }) catch unreachable;
- continue :inner;
- };
+ errdefer {
+ tmpfile.close();
+ tmpdir.deleteFile(std.mem.span(tmpname)) catch {};
+ }
- var package_json: *PackageJSON = result.package_json orelse brk: {
- const read_dir = (bundler.resolver.readDirInfo(package_json_import) catch unreachable).?;
- if (read_dir.package_json == null) {
- generator.log.addWarningFmt(null, logger.Loc.Empty, bundler.allocator, "{s} missing package.json. It will not be bundled", .{name}) catch unreachable;
- continue :inner;
- }
- break :brk read_dir.package_json.?;
- };
+ var generator = try allocator.create(GenerateNodeModuleBundle);
+ var queue = try BunQueue.init(allocator);
+ defer allocator.destroy(generator);
+ generator.* = GenerateNodeModuleBundle{
+ .module_list = std.ArrayList(Api.JavascriptBundledModule).init(allocator),
+ .package_list = std.ArrayList(Api.JavascriptBundledPackage).init(allocator),
+ .header_string_buffer = try MutableString.init(allocator, "dist/index.js".len),
+ .allocator = allocator,
+ .queue = queue,
+ .estimated_input_lines_of_code = 0,
+ // .resolve_queue = queue,
+ .bundler = bundler,
+ .tmpfile = tmpfile,
+
+ .dynamic_import_file_size_store = U32Map.init(allocator),
+ .dynamic_import_file_size_store_lock = Lock.init(),
+ .log = bundler.log,
+ .package_list_map = std.AutoHashMap(u64, u32).init(allocator),
+ .pool = undefined,
+ .write_lock = Lock.init(),
+ };
+ // dist/index.js appears more common than /index.js
+ // but this means we can store both "dist/index.js" and "index.js" in one.
+ try generator.header_string_buffer.append("dist/index.js");
+ try generator.package_list_map.ensureTotalCapacity(128);
+ var pool = try allocator.create(ThreadPool);
+ pool.* = ThreadPool{};
+ generator.pool = pool;
+
+ var this = generator;
+ // Always inline the runtime into the bundle
+ try generator.appendBytes(&initial_header);
+ // If we try to be smart and rely on .written, it turns out incorrect
+ const code_start_pos = try this.tmpfile.getPos();
+ if (isDebug) {
+ try generator.appendBytes(runtime.Runtime.sourceContent());
+ try generator.appendBytes("\n\n");
+ } else {
+ try generator.appendBytes(comptime runtime.Runtime.sourceContent() ++ "\n\n");
+ }
- package_json.source.key_path = result.path_pair.primary;
-
- // if (!strings.contains(result.path_pair.primary.text, package_json.name)) {
- // generator.log.addErrorFmt(
- // null,
- // logger.Loc.Empty,
- // bundler.allocator,
- // "Bundling \"{s}\" is not supported because the package isn.\n To fix this, move the package's code to a directory containing the name.\n Location: \"{s}\"",
- // .{
- // name,
- // name,
- // result.path_pair.primary.text,
- // },
- // ) catch unreachable;
- // continue :inner;
- // }
+ if (bundler.log.level == .verbose) {
+ bundler.resolver.debug_logs = try DebugLogs.init(allocator);
+ }
- always_bundled_package_jsons[i] = package_json;
- always_bundled_package_hashes[i] = package_json.hash;
- i += 1;
- }
- generator.always_bundled_package_hashes = always_bundled_package_hashes[0..i];
- generator.always_bundled_package_jsons = always_bundled_package_jsons[0..i];
+ always_bundled: {
+ const root_package_json_resolved: _resolver.Result = bundler.resolver.resolve(bundler.fs.top_level_dir, "./package.json", .stmt) catch |err| {
+ generator.log.addWarning(null, logger.Loc.Empty, "Please run `bun bun` from a directory containing a package.json.") catch unreachable;
+ break :always_bundled;
+ };
+ const root_package_json = root_package_json_resolved.package_json orelse brk: {
+ const read_dir = (bundler.resolver.readDirInfo(bundler.fs.top_level_dir) catch unreachable).?;
+ break :brk read_dir.package_json.?;
+ };
+ if (root_package_json.always_bundle.len > 0) {
+ var always_bundled_package_jsons = bundler.allocator.alloc(*PackageJSON, root_package_json.always_bundle.len) catch unreachable;
+ var always_bundled_package_hashes = bundler.allocator.alloc(u32, root_package_json.always_bundle.len) catch unreachable;
+ var i: u16 = 0;
+
+ inner: for (root_package_json.always_bundle) |name| {
+ std.mem.copy(u8, &tmp_buildfile_buf, name);
+ std.mem.copy(u8, tmp_buildfile_buf[name.len..], "/package.json");
+ const package_json_import = tmp_buildfile_buf[0 .. name.len + "/package.json".len];
+ const result = bundler.resolver.resolve(bundler.fs.top_level_dir, package_json_import, .stmt) catch |err| {
+ generator.log.addErrorFmt(null, logger.Loc.Empty, bundler.allocator, "{s} resolving always bundled module \"{s}\"", .{ @errorName(err), name }) catch unreachable;
+ continue :inner;
+ };
+
+ var package_json: *PackageJSON = result.package_json orelse brk: {
+ const read_dir = (bundler.resolver.readDirInfo(package_json_import) catch unreachable).?;
+ if (read_dir.package_json == null) {
+ generator.log.addWarningFmt(null, logger.Loc.Empty, bundler.allocator, "{s} missing package.json. It will not be bundled", .{name}) catch unreachable;
+ continue :inner;
+ }
+ break :brk read_dir.package_json.?;
+ };
+
+ package_json.source.key_path = result.path_pair.primary;
+
+ // if (!strings.contains(result.path_pair.primary.text, package_json.name)) {
+ // generator.log.addErrorFmt(
+ // null,
+ // logger.Loc.Empty,
+ // bundler.allocator,
+ // "Bundling \"{s}\" is not supported because the package isn.\n To fix this, move the package's code to a directory containing the name.\n Location: \"{s}\"",
+ // .{
+ // name,
+ // name,
+ // result.path_pair.primary.text,
+ // },
+ // ) catch unreachable;
+ // continue :inner;
+ // }
+
+ always_bundled_package_jsons[i] = package_json;
+ always_bundled_package_hashes[i] = package_json.hash;
+ i += 1;
}
+ generator.always_bundled_package_hashes = always_bundled_package_hashes[0..i];
+ generator.always_bundled_package_jsons = always_bundled_package_jsons[0..i];
}
- if (generator.log.errors > 0) return error.BundleFailed;
-
- const include_refresh_runtime =
- !this.bundler.options.production and
- this.bundler.options.jsx.supports_fast_refresh and
- bundler.options.platform.isWebLike();
-
- const resolve_queue_estimate = bundler.options.entry_points.len +
- @intCast(usize, @boolToInt(framework_config != null)) +
- @intCast(usize, @boolToInt(include_refresh_runtime)) +
- @intCast(usize, @boolToInt(bundler.options.jsx.parse));
-
- if (bundler.router) |router| {
- defer this.bundler.resetStore();
-
- const entry_points = try router.getEntryPoints(allocator);
- for (entry_points) |entry_point| {
- const source_dir = bundler.fs.top_level_dir;
- const resolved = try bundler.linker.resolver.resolve(source_dir, entry_point, .entry_point);
- try this.enqueueItem(resolved);
- }
- this.bundler.resetStore();
- } else {}
+ }
+ if (generator.log.errors > 0) return error.BundleFailed;
+
+ const include_refresh_runtime =
+ !this.bundler.options.production and
+ this.bundler.options.jsx.supports_fast_refresh and
+ bundler.options.platform.isWebLike();
- for (bundler.options.entry_points) |entry_point| {
- if (bundler.options.platform == .bun) continue;
- defer this.bundler.resetStore();
+ const resolve_queue_estimate = bundler.options.entry_points.len +
+ @intCast(usize, @boolToInt(framework_config != null)) +
+ @intCast(usize, @boolToInt(include_refresh_runtime)) +
+ @intCast(usize, @boolToInt(bundler.options.jsx.parse));
- const entry_point_path = bundler.normalizeEntryPointPath(entry_point);
+ if (bundler.router) |router| {
+ defer this.bundler.resetStore();
+
+ const entry_points = try router.getEntryPoints(allocator);
+ for (entry_points) |entry_point| {
const source_dir = bundler.fs.top_level_dir;
const resolved = try bundler.linker.resolver.resolve(source_dir, entry_point, .entry_point);
try this.enqueueItem(resolved);
}
+ this.bundler.resetStore();
+ } else {}
- if (framework_config) |conf| {
- defer this.bundler.resetStore();
+ for (bundler.options.entry_points) |entry_point| {
+ if (bundler.options.platform == .bun) continue;
+ defer this.bundler.resetStore();
- try this.bundler.configureFramework(true);
- if (bundler.options.framework) |framework| {
- if (framework.override_modules.keys.len > 0) {
- bundler.options.framework.?.override_modules_hashes = allocator.alloc(u64, framework.override_modules.keys.len) catch unreachable;
- for (framework.override_modules.keys) |key, i| {
- bundler.options.framework.?.override_modules_hashes[i] = std.hash.Wyhash.hash(0, key);
- }
- }
- if (bundler.options.platform == .bun) {
- if (framework.server.isEnabled()) {
- const resolved = try bundler.linker.resolver.resolve(
- bundler.fs.top_level_dir,
- framework.server.path,
- .entry_point,
- );
- try this.enqueueItem(resolved);
- }
- } else {
- if (framework.client.isEnabled()) {
- const resolved = try bundler.linker.resolver.resolve(
- bundler.fs.top_level_dir,
- framework.client.path,
- .entry_point,
- );
- try this.enqueueItem(resolved);
- }
+ const entry_point_path = bundler.normalizeEntryPointPath(entry_point);
+ const source_dir = bundler.fs.top_level_dir;
+ const resolved = try bundler.linker.resolver.resolve(source_dir, entry_point, .entry_point);
+ try this.enqueueItem(resolved);
+ }
- if (framework.fallback.isEnabled()) {
- const resolved = try bundler.linker.resolver.resolve(
- bundler.fs.top_level_dir,
- framework.fallback.path,
- .entry_point,
- );
- try this.enqueueItem(resolved);
- }
+ if (framework_config) |conf| {
+ defer this.bundler.resetStore();
+
+ try this.bundler.configureFramework(true);
+ if (bundler.options.framework) |framework| {
+ if (framework.override_modules.keys.len > 0) {
+ bundler.options.framework.?.override_modules_hashes = allocator.alloc(u64, framework.override_modules.keys.len) catch unreachable;
+ for (framework.override_modules.keys) |key, i| {
+ bundler.options.framework.?.override_modules_hashes[i] = std.hash.Wyhash.hash(0, key);
}
}
- } else {}
-
- // Normally, this is automatic
- // However, since we only do the parsing pass, it may not get imported automatically.
- if (bundler.options.jsx.parse) {
- defer this.bundler.resetStore();
- if (this.bundler.resolver.resolve(
- this.bundler.fs.top_level_dir,
- this.bundler.options.jsx.import_source,
- .require,
- )) |new_jsx_runtime| {
- try this.enqueueItem(new_jsx_runtime);
- } else |err| {}
- }
+ if (bundler.options.platform == .bun) {
+ if (framework.server.isEnabled()) {
+ const resolved = try bundler.linker.resolver.resolve(
+ bundler.fs.top_level_dir,
+ framework.server.path,
+ .entry_point,
+ );
+ try this.enqueueItem(resolved);
+ }
+ } else {
+ if (framework.client.isEnabled()) {
+ const resolved = try bundler.linker.resolver.resolve(
+ bundler.fs.top_level_dir,
+ framework.client.path,
+ .entry_point,
+ );
+ try this.enqueueItem(resolved);
+ }
- var refresh_runtime_module_id: u32 = 0;
- if (include_refresh_runtime) {
- defer this.bundler.resetStore();
-
- if (this.bundler.resolver.resolve(
- this.bundler.fs.top_level_dir,
- this.bundler.options.jsx.refresh_runtime,
- .require,
- )) |refresh_runtime| {
- try this.enqueueItem(refresh_runtime);
- if (BundledModuleData.get(this, &refresh_runtime)) |mod| {
- refresh_runtime_module_id = mod.module_id;
+ if (framework.fallback.isEnabled()) {
+ const resolved = try bundler.linker.resolver.resolve(
+ bundler.fs.top_level_dir,
+ framework.fallback.path,
+ .entry_point,
+ );
+ try this.enqueueItem(resolved);
}
- } else |err| {}
+ }
}
+ } else {}
- this.bundler.resetStore();
+ // Normally, this is automatic
+ // However, since we only do the parsing pass, it may not get imported automatically.
+ if (bundler.options.jsx.parse) {
+ defer this.bundler.resetStore();
+ if (this.bundler.resolver.resolve(
+ this.bundler.fs.top_level_dir,
+ this.bundler.options.jsx.import_source,
+ .require,
+ )) |new_jsx_runtime| {
+ try this.enqueueItem(new_jsx_runtime);
+ } else |err| {}
+ }
- try this.pool.start(this);
- try this.pool.wait(this);
- estimated_input_lines_of_code.* = generator.estimated_input_lines_of_code;
+ var refresh_runtime_module_id: u32 = 0;
+ if (include_refresh_runtime) {
+ defer this.bundler.resetStore();
- // if (comptime !isRelease) {
- // this.queue.checkDuplicatesSlow();
- // }
+ if (this.bundler.resolver.resolve(
+ this.bundler.fs.top_level_dir,
+ this.bundler.options.jsx.refresh_runtime,
+ .require,
+ )) |refresh_runtime| {
+ try this.enqueueItem(refresh_runtime);
+ if (BundledModuleData.get(this, &refresh_runtime)) |mod| {
+ refresh_runtime_module_id = mod.module_id;
+ }
+ } else |err| {}
+ }
- if (this.log.errors > 0) {
- tmpfile.close();
- tmpdir.deleteFile(std.mem.span(tmpname)) catch {};
- // We stop here because if there are errors we don't know if the bundle is valid
- // This manifests as a crash when sorting through the module list because we may have added files to the bundle which were never actually finished being added.
- return null;
- }
+ this.bundler.resetStore();
- // Delay by one tick so that the rest of the file loads first
- if (include_refresh_runtime and refresh_runtime_module_id > 0) {
- var refresh_runtime_injector_buf: [1024]u8 = undefined;
- var fixed_buffer = std.io.fixedBufferStream(&refresh_runtime_injector_buf);
- var fixed_buffer_writer = fixed_buffer.writer();
-
- fixed_buffer_writer.print(
- \\if ('window' in globalThis) {{
- \\ (async function() {{
- \\ BUN_RUNTIME.__injectFastRefresh(${x}());
- \\ }})();
- \\}}
- ,
- .{refresh_runtime_module_id},
- ) catch unreachable;
- try this.tmpfile.writeAll(fixed_buffer.buffer[0..fixed_buffer.pos]);
- }
+ try this.pool.start(this);
+ try this.pool.wait(this);
+ estimated_input_lines_of_code.* = generator.estimated_input_lines_of_code;
- // Ensure we never overflow
- this.code_end_byte_offset = @truncate(
- u32,
- // Doing this math ourself seems to not necessarily produce correct results
- (try this.tmpfile.getPos()),
- );
-
- var javascript_bundle_container = std.mem.zeroes(Api.JavascriptBundleContainer);
+ // if (comptime !isRelease) {
+ // this.queue.checkDuplicatesSlow();
+ // }
- std.sort.sort(
- Api.JavascriptBundledModule,
- this.module_list.items,
- this,
- GenerateNodeModuleBundle.sortJavascriptModuleByPath,
- );
+ if (this.log.errors > 0) {
+ tmpfile.close();
+ tmpdir.deleteFile(std.mem.span(tmpname)) catch {};
+ // We stop here because if there are errors we don't know if the bundle is valid
+ // This manifests as a crash when sorting through the module list because we may have added files to the bundle which were never actually finished being added.
+ return null;
+ }
- if (comptime isDebug) {
- const SeenHash = std.AutoHashMap(u64, void);
- var map = SeenHash.init(this.allocator);
- var ids = SeenHash.init(this.allocator);
- try map.ensureTotalCapacity(@truncate(u32, this.module_list.items.len));
- try ids.ensureTotalCapacity(@truncate(u32, this.module_list.items.len));
-
- for (this.module_list.items) |a| {
- const a_pkg: Api.JavascriptBundledPackage = this.package_list.items[a.package_id];
- const a_name = this.metadataStringPointer(a_pkg.name);
- const a_version = this.metadataStringPointer(a_pkg.version);
- const a_path = this.metadataStringPointer(a.path);
-
- std.debug.assert(a_name.len > 0);
- std.debug.assert(a_version.len > 0);
- std.debug.assert(a_path.len > 0);
- var hash_print = std.mem.zeroes([4096]u8);
- const hash = std.hash.Wyhash.hash(0, std.fmt.bufPrint(&hash_print, "{s}@{s}/{s}", .{ a_name, a_version, a_path }) catch unreachable);
- var result1 = map.getOrPutAssumeCapacity(hash);
- std.debug.assert(!result1.found_existing);
-
- var result2 = ids.getOrPutAssumeCapacity(a.id);
- std.debug.assert(!result2.found_existing);
- }
- }
+ // Delay by one tick so that the rest of the file loads first
+ if (include_refresh_runtime and refresh_runtime_module_id > 0) {
+ var refresh_runtime_injector_buf: [1024]u8 = undefined;
+ var fixed_buffer = std.io.fixedBufferStream(&refresh_runtime_injector_buf);
+ var fixed_buffer_writer = fixed_buffer.writer();
+
+ fixed_buffer_writer.print(
+ \\if ('window' in globalThis) {{
+ \\ (async function() {{
+ \\ BUN_RUNTIME.__injectFastRefresh(${x}());
+ \\ }})();
+ \\}}
+ ,
+ .{refresh_runtime_module_id},
+ ) catch unreachable;
+ try this.tmpfile.writeAll(fixed_buffer.buffer[0..fixed_buffer.pos]);
+ }
- var hasher = std.hash.Wyhash.init(0);
-
- // We want to sort the packages as well as the files
- // The modules sort the packages already
- // So can just copy it in the below loop.
- var sorted_package_list = try allocator.alloc(Api.JavascriptBundledPackage, this.package_list.items.len);
-
- // At this point, the module_list is sorted.
- if (this.module_list.items.len > 0) {
- var package_id_i: u32 = 0;
- var i: usize = 0;
- // Assumption: node_modules are immutable
- // Assumption: module files are immutable
- // (They're not. But, for our purposes that's okay)
- // The etag is:
- // - The hash of each module's path in sorted order
- // - The hash of each module's code size in sorted order
- // - hash(hash(package_name, package_version))
- // If this doesn't prove strong enough, we will do a proper content hash
- // But I want to avoid that overhead unless proven necessary.
- // There's a good chance we don't even strictly need an etag here.
- var bytes: [4]u8 = undefined;
- while (i < this.module_list.items.len) {
- var current_package_id = this.module_list.items[i].package_id;
- this.module_list.items[i].package_id = package_id_i;
- var offset = @truncate(u32, i);
+ // Ensure we never overflow
+ this.code_end_byte_offset = @truncate(
+ u32,
+ // Doing this math ourself seems to not necessarily produce correct results
+ (try this.tmpfile.getPos()),
+ );
- i += 1;
+ var javascript_bundle_container = std.mem.zeroes(Api.JavascriptBundleContainer);
- while (i < this.module_list.items.len and this.module_list.items[i].package_id == current_package_id) : (i += 1) {
- this.module_list.items[i].package_id = package_id_i;
- // Hash the file path
- hasher.update(this.metadataStringPointer(this.module_list.items[i].path));
- // Then the length of the code
- std.mem.writeIntNative(u32, &bytes, this.module_list.items[i].code.length);
- hasher.update(&bytes);
- }
+ std.sort.sort(
+ Api.JavascriptBundledModule,
+ this.module_list.items,
+ this,
+ GenerateNodeModuleBundle.sortJavascriptModuleByPath,
+ );
- this.package_list.items[current_package_id].modules_offset = offset;
- this.package_list.items[current_package_id].modules_length = @truncate(u32, i) - offset;
+ if (comptime isDebug) {
+ const SeenHash = std.AutoHashMap(u64, void);
+ var map = SeenHash.init(this.allocator);
+ var ids = SeenHash.init(this.allocator);
+ try map.ensureTotalCapacity(@truncate(u32, this.module_list.items.len));
+ try ids.ensureTotalCapacity(@truncate(u32, this.module_list.items.len));
+
+ for (this.module_list.items) |a| {
+ const a_pkg: Api.JavascriptBundledPackage = this.package_list.items[a.package_id];
+ const a_name = this.metadataStringPointer(a_pkg.name);
+ const a_version = this.metadataStringPointer(a_pkg.version);
+ const a_path = this.metadataStringPointer(a.path);
+
+ std.debug.assert(a_name.len > 0);
+ std.debug.assert(a_version.len > 0);
+ std.debug.assert(a_path.len > 0);
+ var hash_print = std.mem.zeroes([4096]u8);
+ const hash = std.hash.Wyhash.hash(0, std.fmt.bufPrint(&hash_print, "{s}@{s}/{s}", .{ a_name, a_version, a_path }) catch unreachable);
+ var result1 = map.getOrPutAssumeCapacity(hash);
+ std.debug.assert(!result1.found_existing);
+
+ var result2 = ids.getOrPutAssumeCapacity(a.id);
+ std.debug.assert(!result2.found_existing);
+ }
+ }
- // Hash the hash of the package name
- // it's hash(hash(package_name, package_version))
- std.mem.writeIntNative(u32, &bytes, this.package_list.items[current_package_id].hash);
+ var hasher = std.hash.Wyhash.init(0);
+
+ // We want to sort the packages as well as the files
+ // The modules sort the packages already
+ // So can just copy it in the below loop.
+ var sorted_package_list = try allocator.alloc(Api.JavascriptBundledPackage, this.package_list.items.len);
+
+ // At this point, the module_list is sorted.
+ if (this.module_list.items.len > 0) {
+ var package_id_i: u32 = 0;
+ var i: usize = 0;
+ // Assumption: node_modules are immutable
+ // Assumption: module files are immutable
+ // (They're not. But, for our purposes that's okay)
+ // The etag is:
+ // - The hash of each module's path in sorted order
+ // - The hash of each module's code size in sorted order
+ // - hash(hash(package_name, package_version))
+ // If this doesn't prove strong enough, we will do a proper content hash
+ // But I want to avoid that overhead unless proven necessary.
+ // There's a good chance we don't even strictly need an etag here.
+ var bytes: [4]u8 = undefined;
+ while (i < this.module_list.items.len) {
+ var current_package_id = this.module_list.items[i].package_id;
+ this.module_list.items[i].package_id = package_id_i;
+ var offset = @truncate(u32, i);
+
+ i += 1;
+
+ while (i < this.module_list.items.len and this.module_list.items[i].package_id == current_package_id) : (i += 1) {
+ this.module_list.items[i].package_id = package_id_i;
+ // Hash the file path
+ hasher.update(this.metadataStringPointer(this.module_list.items[i].path));
+ // Then the length of the code
+ std.mem.writeIntNative(u32, &bytes, this.module_list.items[i].code.length);
hasher.update(&bytes);
-
- sorted_package_list[package_id_i] = this.package_list.items[current_package_id];
- package_id_i += 1;
}
- }
- var javascript_bundle = std.mem.zeroes(Api.JavascriptBundle);
- javascript_bundle.modules = this.module_list.items;
- javascript_bundle.packages = sorted_package_list;
- javascript_bundle.manifest_string = this.header_string_buffer.list.items;
- const etag_u64 = hasher.final();
- // We store the etag as a ascii hex encoded u64
- // This is so we can send the bytes directly in the HTTP server instead of formatting it as hex each time.
- javascript_bundle.etag = try std.fmt.allocPrint(allocator, "{x}", .{etag_u64});
- javascript_bundle.generated_at = @truncate(u32, @intCast(u64, std.time.milliTimestamp()));
-
- const basename = std.fs.path.basename(std.mem.span(destination));
- const extname = std.fs.path.extension(basename);
- javascript_bundle.import_from_name = if (bundler.options.platform == .bun)
- "/node_modules.server.bun"
- else
- try std.fmt.allocPrint(
- this.allocator,
- "/{s}.{x}.bun",
- .{
- basename[0 .. basename.len - extname.len],
- etag_u64,
- },
- );
+ this.package_list.items[current_package_id].modules_offset = offset;
+ this.package_list.items[current_package_id].modules_length = @truncate(u32, i) - offset;
- javascript_bundle_container.bundle_format_version = current_version;
- javascript_bundle_container.bundle = javascript_bundle;
- javascript_bundle_container.code_length = this.code_end_byte_offset;
- javascript_bundle_container.framework = framework_config;
- javascript_bundle_container.routes = route_config;
-
- var start_pos = try this.tmpfile.getPos();
- var tmpwriter = std.io.bufferedWriter(this.tmpfile.writer());
- const SchemaWriter = schema.Writer(@TypeOf(tmpwriter.writer()));
- var schema_file_writer = SchemaWriter.init(tmpwriter.writer());
- try javascript_bundle_container.encode(&schema_file_writer);
- try tmpwriter.flush();
-
- // sanity check
- if (isDebug) {
- try this.tmpfile.seekTo(start_pos);
- var contents = try allocator.alloc(u8, (try this.tmpfile.getEndPos()) - start_pos);
- var read_bytes = try this.tmpfile.read(contents);
- var buf = contents[0..read_bytes];
- var reader = schema.Reader.init(buf, allocator);
-
- var decoder = try Api.JavascriptBundleContainer.decode(
- &reader,
- );
- std.debug.assert(decoder.code_length.? == javascript_bundle_container.code_length.?);
+ // Hash the hash of the package name
+ // it's hash(hash(package_name, package_version))
+ std.mem.writeIntNative(u32, &bytes, this.package_list.items[current_package_id].hash);
+ hasher.update(&bytes);
+
+ sorted_package_list[package_id_i] = this.package_list.items[current_package_id];
+ package_id_i += 1;
}
+ }
- var code_length_bytes: [4]u8 = undefined;
- std.mem.writeIntNative(u32, &code_length_bytes, this.code_end_byte_offset);
- _ = try std.os.pwrite(this.tmpfile.handle, &code_length_bytes, magic_bytes.len);
-
- // Without his mutex, we get a crash at this location:
- // try std.os.renameat(tmpdir.fd, tmpname, top_dir.fd, destination);
- // ^
- const top_dir = try std.fs.openDirAbsolute(Fs.FileSystem.instance.top_level_dir, .{});
- _ = C.fchmod(
- this.tmpfile.handle,
- // chmod 777
- 0000010 | 0000100 | 0000001 | 0001000 | 0000040 | 0000004 | 0000002 | 0000400 | 0000200 | 0000020,
+ var javascript_bundle = std.mem.zeroes(Api.JavascriptBundle);
+ javascript_bundle.modules = this.module_list.items;
+ javascript_bundle.packages = sorted_package_list;
+ javascript_bundle.manifest_string = this.header_string_buffer.list.items;
+ const etag_u64 = hasher.final();
+ // We store the etag as a ascii hex encoded u64
+ // This is so we can send the bytes directly in the HTTP server instead of formatting it as hex each time.
+ javascript_bundle.etag = try std.fmt.allocPrint(allocator, "{x}", .{etag_u64});
+ javascript_bundle.generated_at = @truncate(u32, @intCast(u64, std.time.milliTimestamp()));
+
+ const basename = std.fs.path.basename(std.mem.span(destination));
+ const extname = std.fs.path.extension(basename);
+ javascript_bundle.import_from_name = if (bundler.options.platform == .bun)
+ "/node_modules.server.bun"
+ else
+ try std.fmt.allocPrint(
+ this.allocator,
+ "/{s}.{x}.bun",
+ .{
+ basename[0 .. basename.len - extname.len],
+ etag_u64,
+ },
);
- try std.os.renameatZ(tmpdir.fd, tmpname, top_dir.fd, destination);
- // Print any errors at the end
- // try this.log.print(Output.errorWriter());
- return javascript_bundle_container;
- }
- pub fn metadataStringPointer(this: *GenerateNodeModuleBundle, ptr: Api.StringPointer) string {
- return this.header_string_buffer.list.items[ptr.offset .. ptr.offset + ptr.length];
+ javascript_bundle_container.bundle_format_version = current_version;
+ javascript_bundle_container.bundle = javascript_bundle;
+ javascript_bundle_container.code_length = this.code_end_byte_offset;
+ javascript_bundle_container.framework = framework_config;
+ javascript_bundle_container.routes = route_config;
+
+ var start_pos = try this.tmpfile.getPos();
+ var tmpwriter = std.io.bufferedWriter(this.tmpfile.writer());
+ const SchemaWriter = schema.Writer(@TypeOf(tmpwriter.writer()));
+ var schema_file_writer = SchemaWriter.init(tmpwriter.writer());
+ try javascript_bundle_container.encode(&schema_file_writer);
+ try tmpwriter.flush();
+
+ // sanity check
+ if (isDebug) {
+ try this.tmpfile.seekTo(start_pos);
+ var contents = try allocator.alloc(u8, (try this.tmpfile.getEndPos()) - start_pos);
+ var read_bytes = try this.tmpfile.read(contents);
+ var buf = contents[0..read_bytes];
+ var reader = schema.Reader.init(buf, allocator);
+
+ var decoder = try Api.JavascriptBundleContainer.decode(
+ &reader,
+ );
+ std.debug.assert(decoder.code_length.? == javascript_bundle_container.code_length.?);
}
- // Since we trim the prefixes, we must also compare the package name and version
- pub fn sortJavascriptModuleByPath(ctx: *GenerateNodeModuleBundle, a: Api.JavascriptBundledModule, b: Api.JavascriptBundledModule) bool {
- return switch (std.mem.order(
+ var code_length_bytes: [4]u8 = undefined;
+ std.mem.writeIntNative(u32, &code_length_bytes, this.code_end_byte_offset);
+ _ = try std.os.pwrite(this.tmpfile.handle, &code_length_bytes, magic_bytes.len);
+
+ // Without his mutex, we get a crash at this location:
+ // try std.os.renameat(tmpdir.fd, tmpname, top_dir.fd, destination);
+ // ^
+ const top_dir = try std.fs.openDirAbsolute(Fs.FileSystem.instance.top_level_dir, .{});
+ _ = C.fchmod(
+ this.tmpfile.handle,
+ // chmod 777
+ 0000010 | 0000100 | 0000001 | 0001000 | 0000040 | 0000004 | 0000002 | 0000400 | 0000200 | 0000020,
+ );
+ try std.os.renameatZ(tmpdir.fd, tmpname, top_dir.fd, destination);
+ // Print any errors at the end
+ // try this.log.print(Output.errorWriter());
+ return javascript_bundle_container;
+ }
+
+ pub fn metadataStringPointer(this: *GenerateNodeModuleBundle, ptr: Api.StringPointer) string {
+ return this.header_string_buffer.list.items[ptr.offset .. ptr.offset + ptr.length];
+ }
+
+ // Since we trim the prefixes, we must also compare the package name and version
+ pub fn sortJavascriptModuleByPath(ctx: *GenerateNodeModuleBundle, a: Api.JavascriptBundledModule, b: Api.JavascriptBundledModule) bool {
+ return switch (std.mem.order(
+ u8,
+ ctx.metadataStringPointer(
+ ctx.package_list.items[a.package_id].name,
+ ),
+ ctx.metadataStringPointer(
+ ctx.package_list.items[b.package_id].name,
+ ),
+ )) {
+ .eq => switch (std.mem.order(
u8,
ctx.metadataStringPointer(
- ctx.package_list.items[a.package_id].name,
+ ctx.package_list.items[a.package_id].version,
),
ctx.metadataStringPointer(
- ctx.package_list.items[b.package_id].name,
+ ctx.package_list.items[b.package_id].version,
),
)) {
- .eq => switch (std.mem.order(
+ .eq => std.mem.order(
u8,
- ctx.metadataStringPointer(
- ctx.package_list.items[a.package_id].version,
- ),
- ctx.metadataStringPointer(
- ctx.package_list.items[b.package_id].version,
- ),
- )) {
- .eq => std.mem.order(
- u8,
- ctx.metadataStringPointer(a.path),
- ctx.metadataStringPointer(b.path),
- ) == .lt,
- .lt => true,
- else => false,
- },
+ ctx.metadataStringPointer(a.path),
+ ctx.metadataStringPointer(b.path),
+ ) == .lt,
.lt => true,
else => false,
- };
- }
+ },
+ .lt => true,
+ else => false,
+ };
+ }
- // pub fn sortJavascriptPackageByName(ctx: *GenerateNodeModuleBundle, a: Api.JavascriptBundledPackage, b: Api.JavascriptBundledPackage) bool {
- // return std.mem.order(u8, ctx.metadataStringPointer(a.name), ctx.metadataStringPointer(b.name)) == .lt;
- // }
+ // pub fn sortJavascriptPackageByName(ctx: *GenerateNodeModuleBundle, a: Api.JavascriptBundledPackage, b: Api.JavascriptBundledPackage) bool {
+ // return std.mem.order(u8, ctx.metadataStringPointer(a.name), ctx.metadataStringPointer(b.name)) == .lt;
+ // }
+
+ pub fn appendBytes(generator: *GenerateNodeModuleBundle, bytes: anytype) !void {
+ try generator.tmpfile.writeAll(bytes);
+ generator.tmpfile_byte_offset += @truncate(u32, bytes.len);
+ }
- pub fn appendBytes(generator: *GenerateNodeModuleBundle, bytes: anytype) !void {
- try generator.tmpfile.writeAll(bytes);
- generator.tmpfile_byte_offset += @truncate(u32, bytes.len);
+ const BundledModuleData = struct {
+ import_path: string,
+ package_path: string,
+ package: *const PackageJSON,
+ module_id: u32,
+
+ pub fn getForceBundle(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result) ?BundledModuleData {
+ return _get(this, resolve_result, true, false);
}
- const BundledModuleData = struct {
- import_path: string,
- package_path: string,
- package: *const PackageJSON,
- module_id: u32,
+ pub fn getForceBundleForMain(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result) ?BundledModuleData {
+ return _get(this, resolve_result, true, true);
+ }
- pub fn getForceBundle(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result) ?BundledModuleData {
- return _get(this, resolve_result, true, false);
+ threadlocal var normalized_package_path: [512]u8 = undefined;
+ threadlocal var normalized_package_path2: [512]u8 = undefined;
+ inline fn _get(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result, comptime force: bool, comptime is_main: bool) ?BundledModuleData {
+ const path = resolve_result.pathConst() orelse return null;
+ if (strings.eqlComptime(path.namespace, "node")) {
+ const _import_path = path.text["/bun-vfs/node_modules/".len..][resolve_result.package_json.?.name.len + 1 ..];
+ return BundledModuleData{
+ .import_path = _import_path,
+ .package_path = path.text["/bun-vfs/node_modules/".len..],
+ .package = resolve_result.package_json.?,
+ .module_id = resolve_result.package_json.?.hashModule(_import_path),
+ };
}
- pub fn getForceBundleForMain(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result) ?BundledModuleData {
- return _get(this, resolve_result, true, true);
- }
+ var import_path = path.text;
+ var package_path = path.text;
+ var file_path = path.text;
+
+ if (resolve_result.package_json) |pkg| {
+ if (std.mem.indexOfScalar(u32, this.always_bundled_package_hashes, pkg.hash) != null) {
+ const key_path_source_dir = pkg.source.key_path.sourceDir();
+ const default_source_dir = pkg.source.path.sourceDir();
+ if (strings.startsWith(path.text, key_path_source_dir)) {
+ import_path = path.text[key_path_source_dir.len..];
+ } else if (strings.startsWith(path.text, default_source_dir)) {
+ import_path = path.text[default_source_dir.len..];
+ } else if (strings.startsWith(path.pretty, pkg.name)) {
+ import_path = path.pretty[pkg.name.len + 1 ..];
+ }
- threadlocal var normalized_package_path: [512]u8 = undefined;
- threadlocal var normalized_package_path2: [512]u8 = undefined;
- inline fn _get(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result, comptime force: bool, comptime is_main: bool) ?BundledModuleData {
- const path = resolve_result.pathConst() orelse return null;
- if (strings.eqlComptime(path.namespace, "node")) {
- const _import_path = path.text["/bun-vfs/node_modules/".len..][resolve_result.package_json.?.name.len + 1 ..];
+ var buf_to_use: []u8 = if (is_main) &normalized_package_path2 else &normalized_package_path;
+
+ std.mem.copy(u8, buf_to_use, pkg.name);
+ buf_to_use[pkg.name.len] = '/';
+ std.mem.copy(u8, buf_to_use[pkg.name.len + 1 ..], import_path);
+ package_path = buf_to_use[0 .. pkg.name.len + import_path.len + 1];
return BundledModuleData{
- .import_path = _import_path,
- .package_path = path.text["/bun-vfs/node_modules/".len..],
- .package = resolve_result.package_json.?,
- .module_id = resolve_result.package_json.?.hashModule(_import_path),
+ .import_path = import_path,
+ .package_path = package_path,
+ .package = pkg,
+ .module_id = pkg.hashModule(package_path),
};
}
+ }
- var import_path = path.text;
- var package_path = path.text;
- var file_path = path.text;
-
- if (resolve_result.package_json) |pkg| {
- if (std.mem.indexOfScalar(u32, this.always_bundled_package_hashes, pkg.hash) != null) {
- const key_path_source_dir = pkg.source.key_path.sourceDir();
- const default_source_dir = pkg.source.path.sourceDir();
- if (strings.startsWith(path.text, key_path_source_dir)) {
- import_path = path.text[key_path_source_dir.len..];
- } else if (strings.startsWith(path.text, default_source_dir)) {
- import_path = path.text[default_source_dir.len..];
- } else if (strings.startsWith(path.pretty, pkg.name)) {
- import_path = path.pretty[pkg.name.len + 1 ..];
- }
+ const root: _resolver.RootPathPair = this.bundler.resolver.rootNodeModulePackageJSON(
+ resolve_result,
+ ) orelse return null;
+
+ var base_path = root.base_path;
+ const package_json = root.package_json;
+
+ // Easymode: the file path doesn't need to be remapped.
+ if (strings.startsWith(file_path, base_path)) {
+ import_path = std.mem.trimLeft(u8, path.text[base_path.len..], "/");
+ package_path = std.mem.trim(u8, path.text[base_path.len - package_json.name.len - 1 ..], "/");
+ std.debug.assert(import_path.len > 0);
+ return BundledModuleData{
+ .import_path = import_path,
+ .package_path = package_path,
+ .package = package_json,
+ .module_id = package_json.hashModule(package_path),
+ };
+ }
+
+ if (std.mem.lastIndexOf(u8, file_path, package_json.name)) |i| {
+ package_path = file_path[i..];
+ import_path = package_path[package_json.name.len + 1 ..];
+ std.debug.assert(import_path.len > 0);
+ return BundledModuleData{
+ .import_path = import_path,
+ .package_path = package_path,
+ .package = package_json,
+ .module_id = package_json.hashModule(package_path),
+ };
+ }
- var buf_to_use: []u8 = if (is_main) &normalized_package_path2 else &normalized_package_path;
+ if (comptime force) {
+ if (std.mem.indexOfScalar(u32, this.always_bundled_package_hashes, root.package_json.hash)) |pkg_json_i| {
+ const pkg_json = this.always_bundled_package_jsons[pkg_json_i];
+ base_path = pkg_json.source.key_path.sourceDir();
- std.mem.copy(u8, buf_to_use, pkg.name);
- buf_to_use[pkg.name.len] = '/';
- std.mem.copy(u8, buf_to_use[pkg.name.len + 1 ..], import_path);
- package_path = buf_to_use[0 .. pkg.name.len + import_path.len + 1];
+ if (strings.startsWith(file_path, base_path)) {
+ import_path = std.mem.trimLeft(u8, path.text[base_path.len..], "/");
+ package_path = std.mem.trim(u8, path.text[base_path.len - package_json.name.len - 1 ..], "/");
+ std.debug.assert(import_path.len > 0);
return BundledModuleData{
.import_path = import_path,
.package_path = package_path,
- .package = pkg,
- .module_id = pkg.hashModule(package_path),
+ .package = package_json,
+ .module_id = package_json.hashModule(package_path),
};
}
- }
-
- const root: _resolver.RootPathPair = this.bundler.resolver.rootNodeModulePackageJSON(
- resolve_result,
- ) orelse return null;
-
- var base_path = root.base_path;
- const package_json = root.package_json;
-
- // Easymode: the file path doesn't need to be remapped.
- if (strings.startsWith(file_path, base_path)) {
- import_path = std.mem.trimLeft(u8, path.text[base_path.len..], "/");
- package_path = std.mem.trim(u8, path.text[base_path.len - package_json.name.len - 1 ..], "/");
- std.debug.assert(import_path.len > 0);
- return BundledModuleData{
- .import_path = import_path,
- .package_path = package_path,
- .package = package_json,
- .module_id = package_json.hashModule(package_path),
- };
- }
-
- if (std.mem.lastIndexOf(u8, file_path, package_json.name)) |i| {
- package_path = file_path[i..];
- import_path = package_path[package_json.name.len + 1 ..];
- std.debug.assert(import_path.len > 0);
- return BundledModuleData{
- .import_path = import_path,
- .package_path = package_path,
- .package = package_json,
- .module_id = package_json.hashModule(package_path),
- };
- }
-
- if (comptime force) {
- if (std.mem.indexOfScalar(u32, this.always_bundled_package_hashes, root.package_json.hash)) |pkg_json_i| {
- const pkg_json = this.always_bundled_package_jsons[pkg_json_i];
- base_path = pkg_json.source.key_path.sourceDir();
-
- if (strings.startsWith(file_path, base_path)) {
- import_path = std.mem.trimLeft(u8, path.text[base_path.len..], "/");
- package_path = std.mem.trim(u8, path.text[base_path.len - package_json.name.len - 1 ..], "/");
- std.debug.assert(import_path.len > 0);
- return BundledModuleData{
- .import_path = import_path,
- .package_path = package_path,
- .package = package_json,
- .module_id = package_json.hashModule(package_path),
- };
- }
- if (std.mem.lastIndexOf(u8, file_path, package_json.name)) |i| {
- package_path = file_path[i..];
- import_path = package_path[package_json.name.len + 1 ..];
- std.debug.assert(import_path.len > 0);
- return BundledModuleData{
- .import_path = import_path,
- .package_path = package_path,
- .package = package_json,
- .module_id = package_json.hashModule(package_path),
- };
- }
+ if (std.mem.lastIndexOf(u8, file_path, package_json.name)) |i| {
+ package_path = file_path[i..];
+ import_path = package_path[package_json.name.len + 1 ..];
+ std.debug.assert(import_path.len > 0);
+ return BundledModuleData{
+ .import_path = import_path,
+ .package_path = package_path,
+ .package = package_json,
+ .module_id = package_json.hashModule(package_path),
+ };
}
- unreachable;
}
-
- return null;
+ unreachable;
}
- pub fn get(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result) ?BundledModuleData {
- return _get(this, resolve_result, false, false);
- }
- };
+ return null;
+ }
- fn writeEmptyModule(this: *GenerateNodeModuleBundle, package_relative_path: string, module_id: u32) !u32 {
- this.write_lock.lock();
- defer this.write_lock.unlock();
- var code_offset = @truncate(u32, try this.tmpfile.getPos());
- var writer = this.tmpfile.writer();
- var buffered = std.io.bufferedWriter(writer);
-
- var bufwriter = buffered.writer();
- try bufwriter.writeAll("// ");
- try bufwriter.writeAll(package_relative_path);
- try bufwriter.writeAll(" (disabled/empty)\nexport var $");
- std.fmt.formatInt(module_id, 16, .lower, .{}, bufwriter) catch unreachable;
- try bufwriter.writeAll(" = () => { var obj = {}; Object.defineProperty(obj, 'default', { value: obj, enumerable: false, configurable: true }, obj); return obj; }; \n");
- try buffered.flush();
- this.tmpfile_byte_offset = @truncate(u32, try this.tmpfile.getPos());
- return code_offset;
+ pub fn get(this: *GenerateNodeModuleBundle, resolve_result: *const _resolver.Result) ?BundledModuleData {
+ return _get(this, resolve_result, false, false);
}
+ };
- fn processImportRecord(this: *GenerateNodeModuleBundle, import_record: ImportRecord) !void {}
- var json_ast_symbols = [_]js_ast.Symbol{
- js_ast.Symbol{ .original_name = "$$m" },
- js_ast.Symbol{ .original_name = "exports" },
- js_ast.Symbol{ .original_name = "module" },
- js_ast.Symbol{ .original_name = "CONGRATS_YOU_FOUND_A_BUG" },
- js_ast.Symbol{ .original_name = "$$bun_runtime_json_parse" },
- };
- const json_parse_string = "parse";
- var json_ast_symbols_list = std.mem.span(&json_ast_symbols);
- threadlocal var override_file_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-
- pub fn appendToModuleList(
- this: *GenerateNodeModuleBundle,
- package: *const PackageJSON,
- module_id: u32,
- code_offset: u32,
- package_relative_path: string,
- ) !void {
- this.list_lock.lock();
- defer this.list_lock.unlock();
-
- const code_length = @atomicLoad(u32, &this.tmpfile_byte_offset, .SeqCst) - code_offset;
+ fn writeEmptyModule(this: *GenerateNodeModuleBundle, package_relative_path: string, module_id: u32) !u32 {
+ this.write_lock.lock();
+ defer this.write_lock.unlock();
+ var code_offset = @truncate(u32, try this.tmpfile.getPos());
+ var writer = this.tmpfile.writer();
+ var buffered = std.io.bufferedWriter(writer);
+
+ var bufwriter = buffered.writer();
+ try bufwriter.writeAll("// ");
+ try bufwriter.writeAll(package_relative_path);
+ try bufwriter.writeAll(" (disabled/empty)\nexport var $");
+ std.fmt.formatInt(module_id, 16, .lower, .{}, bufwriter) catch unreachable;
+ try bufwriter.writeAll(" = () => { var obj = {}; Object.defineProperty(obj, 'default', { value: obj, enumerable: false, configurable: true }, obj); return obj; }; \n");
+ try buffered.flush();
+ this.tmpfile_byte_offset = @truncate(u32, try this.tmpfile.getPos());
+ return code_offset;
+ }
- if (comptime isDebug) {
- std.debug.assert(code_length > 0);
- std.debug.assert(package.hash != 0);
- std.debug.assert(package.version.len > 0);
- std.debug.assert(package.name.len > 0);
- std.debug.assert(module_id > 0);
- }
+ fn processImportRecord(this: *GenerateNodeModuleBundle, import_record: ImportRecord) !void {}
+ var json_ast_symbols = [_]js_ast.Symbol{
+ js_ast.Symbol{ .original_name = "$$m" },
+ js_ast.Symbol{ .original_name = "exports" },
+ js_ast.Symbol{ .original_name = "module" },
+ js_ast.Symbol{ .original_name = "CONGRATS_YOU_FOUND_A_BUG" },
+ js_ast.Symbol{ .original_name = "$$bun_runtime_json_parse" },
+ };
+ const json_parse_string = "parse";
+ var json_ast_symbols_list = std.mem.span(&json_ast_symbols);
+ threadlocal var override_file_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+
+ pub fn appendToModuleList(
+ this: *GenerateNodeModuleBundle,
+ package: *const PackageJSON,
+ module_id: u32,
+ code_offset: u32,
+ package_relative_path: string,
+ ) !void {
+ this.list_lock.lock();
+ defer this.list_lock.unlock();
- var package_get_or_put_entry = try this.package_list_map.getOrPut(package.hash);
+ const code_length = @atomicLoad(u32, &this.tmpfile_byte_offset, .SeqCst) - code_offset;
- if (!package_get_or_put_entry.found_existing) {
- package_get_or_put_entry.value_ptr.* = @truncate(u32, this.package_list.items.len);
- try this.package_list.append(
- Api.JavascriptBundledPackage{
- .name = try this.appendHeaderString(package.name),
- .version = try this.appendHeaderString(package.version),
- .hash = package.hash,
- },
- );
- this.has_jsx = this.has_jsx or strings.eql(package.name, this.bundler.options.jsx.package_name);
- }
+ if (comptime isDebug) {
+ std.debug.assert(code_length > 0);
+ std.debug.assert(package.hash != 0);
+ std.debug.assert(package.version.len > 0);
+ std.debug.assert(package.name.len > 0);
+ std.debug.assert(module_id > 0);
+ }
- var path_extname_length = @truncate(u8, std.fs.path.extension(package_relative_path).len);
- try this.module_list.append(
- Api.JavascriptBundledModule{
- .path = try this.appendHeaderString(
- package_relative_path,
- ),
- .path_extname_length = path_extname_length,
- .package_id = package_get_or_put_entry.value_ptr.*,
- .id = module_id,
- .code = Api.StringPointer{
- .length = @truncate(u32, code_length),
- .offset = @truncate(u32, code_offset),
- },
+ var package_get_or_put_entry = try this.package_list_map.getOrPut(package.hash);
+
+ if (!package_get_or_put_entry.found_existing) {
+ package_get_or_put_entry.value_ptr.* = @truncate(u32, this.package_list.items.len);
+ try this.package_list.append(
+ Api.JavascriptBundledPackage{
+ .name = try this.appendHeaderString(package.name),
+ .version = try this.appendHeaderString(package.version),
+ .hash = package.hash,
},
);
+ this.has_jsx = this.has_jsx or strings.eql(package.name, this.bundler.options.jsx.package_name);
}
- threadlocal var json_e_string: js_ast.E.String = undefined;
- threadlocal var json_e_call: js_ast.E.Call = undefined;
- threadlocal var json_e_identifier: js_ast.E.Identifier = undefined;
- threadlocal var json_call_args: [1]js_ast.Expr = undefined;
- pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, _resolve: _resolver.Result) !void {
- const resolve = _resolve;
- if (resolve.is_external) return;
-
- var shared_buffer = &worker.data.shared_buffer;
- var scan_pass_result = &worker.data.scan_pass_result;
-
- const is_from_node_modules = resolve.isLikelyNodeModule() or brk: {
- if (resolve.package_json) |pkg| {
- break :brk std.mem.indexOfScalar(u32, this.always_bundled_package_hashes, pkg.hash) != null;
- }
- break :brk false;
+
+ var path_extname_length = @truncate(u8, std.fs.path.extension(package_relative_path).len);
+ try this.module_list.append(
+ Api.JavascriptBundledModule{
+ .path = try this.appendHeaderString(
+ package_relative_path,
+ ),
+ .path_extname_length = path_extname_length,
+ .package_id = package_get_or_put_entry.value_ptr.*,
+ .id = module_id,
+ .code = Api.StringPointer{
+ .length = @truncate(u32, code_length),
+ .offset = @truncate(u32, code_offset),
+ },
+ },
+ );
+ }
+ threadlocal var json_e_string: js_ast.E.String = undefined;
+ threadlocal var json_e_call: js_ast.E.Call = undefined;
+ threadlocal var json_e_identifier: js_ast.E.Identifier = undefined;
+ threadlocal var json_call_args: [1]js_ast.Expr = undefined;
+ pub fn processFile(this: *GenerateNodeModuleBundle, worker: *ThreadPool.Worker, _resolve: _resolver.Result) !void {
+ const resolve = _resolve;
+ if (resolve.is_external) return;
+
+ var shared_buffer = &worker.data.shared_buffer;
+ var scan_pass_result = &worker.data.scan_pass_result;
+
+ const is_from_node_modules = resolve.isLikelyNodeModule() or brk: {
+ if (resolve.package_json) |pkg| {
+ break :brk std.mem.indexOfScalar(u32, this.always_bundled_package_hashes, pkg.hash) != null;
+ }
+ break :brk false;
+ };
+ var file_path = (resolve.pathConst() orelse unreachable).*;
+ const source_dir = file_path.sourceDir();
+ const loader = this.bundler.options.loader(file_path.name.ext);
+ var bundler = this.bundler;
+ defer scan_pass_result.reset();
+ defer shared_buffer.reset();
+ defer this.bundler.resetStore();
+ var log = worker.data.log;
+
+ // If we're in a node_module, build that almost normally
+ if (is_from_node_modules) {
+ var written: usize = undefined;
+ var code_offset: u32 = 0;
+
+ const module_data = BundledModuleData.getForceBundleForMain(this, &resolve) orelse {
+ const fake_path = logger.Source.initPathString(file_path.text, "");
+ log.addResolveError(
+ &fake_path,
+ logger.Range.None,
+ this.allocator,
+ "Bug while resolving: \"{s}\"",
+ .{file_path.text},
+ resolve.import_kind,
+ ) catch {};
+ return error.ResolveError;
};
- var file_path = (resolve.pathConst() orelse unreachable).*;
- const source_dir = file_path.sourceDir();
- const loader = this.bundler.options.loader(file_path.name.ext);
- var bundler = this.bundler;
- defer scan_pass_result.reset();
- defer shared_buffer.reset();
- defer this.bundler.resetStore();
- var log = worker.data.log;
-
- // If we're in a node_module, build that almost normally
- if (is_from_node_modules) {
- var written: usize = undefined;
- var code_offset: u32 = 0;
-
- const module_data = BundledModuleData.getForceBundleForMain(this, &resolve) orelse {
- const fake_path = logger.Source.initPathString(file_path.text, "");
- log.addResolveError(
- &fake_path,
- logger.Range.None,
- this.allocator,
- "Bug while resolving: \"{s}\"",
- .{file_path.text},
- resolve.import_kind,
- ) catch {};
- return error.ResolveError;
- };
- const module_id = module_data.module_id;
- const package = module_data.package;
- const package_relative_path = module_data.import_path;
-
- file_path.pretty = module_data.package_path;
-
- const entry: CacheEntry = brk: {
- if (this.bundler.options.framework) |framework| {
- if (framework.override_modules_hashes.len > 0) {
- const package_relative_path_hash = std.hash.Wyhash.hash(0, module_data.package_path);
- if (std.mem.indexOfScalar(
- u64,
- framework.override_modules_hashes,
- package_relative_path_hash,
- )) |index| {
- const relative_path = [_]string{
- framework.resolved_dir,
- framework.override_modules.values[index],
- };
- var override_path = this.bundler.fs.absBuf(
- &relative_path,
- &override_file_path_buf,
- );
- override_file_path_buf[override_path.len] = 0;
- var override_pathZ = override_file_path_buf[0..override_path.len :0];
- break :brk try bundler.resolver.caches.fs.readFileShared(
- bundler.fs,
- override_pathZ,
- 0,
- null,
- shared_buffer,
- );
- }
+ const module_id = module_data.module_id;
+ const package = module_data.package;
+ const package_relative_path = module_data.import_path;
+
+ file_path.pretty = module_data.package_path;
+
+ const entry: CacheEntry = brk: {
+ if (this.bundler.options.framework) |framework| {
+ if (framework.override_modules_hashes.len > 0) {
+ const package_relative_path_hash = std.hash.Wyhash.hash(0, module_data.package_path);
+ if (std.mem.indexOfScalar(
+ u64,
+ framework.override_modules_hashes,
+ package_relative_path_hash,
+ )) |index| {
+ const relative_path = [_]string{
+ framework.resolved_dir,
+ framework.override_modules.values[index],
+ };
+ var override_path = this.bundler.fs.absBuf(
+ &relative_path,
+ &override_file_path_buf,
+ );
+ override_file_path_buf[override_path.len] = 0;
+ var override_pathZ = override_file_path_buf[0..override_path.len :0];
+ break :brk try bundler.resolver.caches.fs.readFileShared(
+ bundler.fs,
+ override_pathZ,
+ 0,
+ null,
+ shared_buffer,
+ );
}
}
+ }
- if (!strings.eqlComptime(file_path.namespace, "node"))
- break :brk try bundler.resolver.caches.fs.readFileShared(
- bundler.fs,
- file_path.textZ(),
- resolve.dirname_fd,
- if (resolve.file_fd != 0) resolve.file_fd else null,
- shared_buffer,
- );
+ if (!strings.eqlComptime(file_path.namespace, "node"))
+ break :brk try bundler.resolver.caches.fs.readFileShared(
+ bundler.fs,
+ file_path.textZ(),
+ resolve.dirname_fd,
+ if (resolve.file_fd != 0) resolve.file_fd else null,
+ shared_buffer,
+ );
- break :brk CacheEntry{
- .contents = NodeFallbackModules.contentsFromPath(file_path.text) orelse "",
- };
+ break :brk CacheEntry{
+ .contents = NodeFallbackModules.contentsFromPath(file_path.text) orelse "",
};
+ };
- var approximate_newline_count: usize = 0;
- defer worker.data.estimated_input_lines_of_code += approximate_newline_count;
-
- // Handle empty files
- // We can't just ignore them. Sometimes code will try to import it. Often because of TypeScript types.
- // So we just say it's an empty object. Empty object mimicks what "browser": false does as well.
- // TODO: optimize this so that all the exports for these are done in one line instead of writing repeatedly
- if (entry.contents.len == 0 or (entry.contents.len < 33 and strings.trim(entry.contents, " \n\r").len == 0)) {
- code_offset = try this.writeEmptyModule(module_data.package_path, module_id);
- } else {
- var ast: js_ast.Ast = undefined;
-
- const source = logger.Source.initRecycledFile(
- Fs.File{
- .path = file_path,
- .contents = entry.contents,
- },
- bundler.allocator,
- ) catch return null;
-
- switch (loader) {
- .jsx,
- .tsx,
- .js,
- .ts,
- => {
- var jsx = bundler.options.jsx;
- jsx.parse = loader.isJSX();
-
- var opts = js_parser.Parser.Options.init(jsx, loader);
- opts.transform_require_to_import = false;
- opts.enable_bundling = true;
- opts.warn_about_unbundled_modules = false;
-
- ast = (try bundler.resolver.caches.js.parse(
- bundler.allocator,
- opts,
- bundler.options.define,
- log,
- &source,
- )) orelse return;
- approximate_newline_count = ast.approximate_newline_count;
- if (ast.import_records.len > 0) {
- for (ast.import_records) |*import_record, record_id| {
-
- // Don't resolve the runtime
- if (import_record.is_internal or import_record.is_unused) {
- continue;
- }
-
- if (bundler.linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*_resolved_import| {
- if (_resolved_import.is_external) {
- continue;
- }
- var path = _resolved_import.path() orelse {
- import_record.path.is_disabled = true;
- import_record.is_bundled = true;
- continue;
- };
-
- const loader_ = bundler.options.loader(path.name.ext);
-
- if (!loader_.isJavaScriptLikeOrJSON()) {
- import_record.path.is_disabled = true;
- import_record.is_bundled = true;
- continue;
- }
-
- // if (_resolved_import.package_json == null) |pkg_json| {
- // _resolved_import.package_json = if (pkg_json.hash == resolve.package_json.?.hash)
- // resolve.package_json
- // else
- // _resolved_import.package_json;
- // }
-
- const resolved_import: *const _resolver.Result = _resolved_import;
-
- const _module_data = BundledModuleData.getForceBundle(this, resolved_import) orelse unreachable;
- import_record.module_id = _module_data.module_id;
- std.debug.assert(import_record.module_id != 0);
- import_record.is_bundled = true;
-
- path.* = try path.dupeAlloc(this.allocator);
-
- import_record.path = path.*;
-
- try this.queue.upsert(
- _module_data.module_id,
- _resolved_import.*,
- );
- } else |err| {
- if (comptime isDebug) {
- Output.prettyErrorln("\n<r><red>{s}<r> on resolving \"{s}\" from \"{s}\"", .{
- @errorName(err),
- import_record.path.text,
- file_path.text,
- });
- }
-
- switch (err) {
- error.ModuleNotFound => {
- if (isPackagePath(import_record.path.text)) {
- if (this.bundler.options.platform.isWebLike() and options.ExternalModules.isNodeBuiltin(import_record.path.text)) {
- try log.addResolveErrorWithTextDupe(
- &source,
- import_record.range,
- this.allocator,
- "Could not resolve Node.js builtin: \"{s}\".",
- .{import_record.path.text},
- import_record.kind,
- );
- } else {
- try log.addResolveErrorWithTextDupe(
- &source,
- import_record.range,
- this.allocator,
- "Could not resolve: \"{s}\". Maybe you need to \"npm install\" (or yarn/pnpm)?",
- .{import_record.path.text},
- import_record.kind,
- );
- }
- } else {
- try log.addResolveErrorWithTextDupe(
- &source,
- import_record.range,
- this.allocator,
- "Could not resolve: \"{s}\"",
- .{
- import_record.path.text,
- },
- import_record.kind,
- );
- }
- },
- // assume other errors are already in the log
- else => {},
- }
- }
- }
- }
- },
- .json => {
- // parse the JSON _only_ to catch errors at build time.
- const json_parse_result = json_parser.ParseJSONForBundling(&source, worker.data.log, worker.allocator) catch return;
-
- if (json_parse_result.tag != .empty) {
- const expr = brk: {
- // If it's an ascii string, we just print it out with a big old JSON.parse()
- if (json_parse_result.tag == .ascii) {
- json_e_string = js_ast.E.String{ .utf8 = source.contents, .prefer_template = true };
- var json_string_expr = js_ast.Expr{ .data = .{ .e_string = &json_e_string }, .loc = logger.Loc{ .start = 0 } };
- json_call_args[0] = json_string_expr;
- json_e_identifier = js_ast.E.Identifier{ .ref = Ref{ .source_index = 0, .inner_index = @intCast(Ref.Int, json_ast_symbols_list.len - 1) } };
-
- json_e_call = js_ast.E.Call{
- .target = js_ast.Expr{ .data = .{ .e_identifier = &json_e_identifier }, .loc = logger.Loc{ .start = 0 } },
- .args = std.mem.span(&json_call_args),
- };
- break :brk js_ast.Expr{ .data = .{ .e_call = &json_e_call }, .loc = logger.Loc{ .start = 0 } };
- // If we're going to have to convert it to a UTF16, just make it an object actually
- } else {
- break :brk json_parse_result.expr;
- }
- };
-
- var stmt = js_ast.Stmt.alloc(worker.allocator, js_ast.S.ExportDefault, js_ast.S.ExportDefault{
- .value = js_ast.StmtOrExpr{ .expr = expr },
- .default_name = js_ast.LocRef{ .loc = logger.Loc{}, .ref = Ref{} },
- }, logger.Loc{ .start = 0 });
- var stmts = worker.allocator.alloc(js_ast.Stmt, 1) catch unreachable;
- stmts[0] = stmt;
- var parts = worker.allocator.alloc(js_ast.Part, 1) catch unreachable;
- parts[0] = js_ast.Part{ .stmts = stmts };
- ast = js_ast.Ast.initTest(parts);
-
- ast.runtime_imports = runtime.Runtime.Imports{};
- ast.runtime_imports.@"$$m" = .{ .ref = Ref{ .source_index = 0, .inner_index = 0 }, .primary = Ref.None, .backup = Ref.None };
- ast.runtime_imports.__export = .{ .ref = Ref{ .source_index = 0, .inner_index = 1 }, .primary = Ref.None, .backup = Ref.None };
- ast.symbols = json_ast_symbols_list;
- ast.module_ref = Ref{ .source_index = 0, .inner_index = 2 };
- ast.exports_ref = ast.runtime_imports.__export.?.ref;
- ast.bundle_export_ref = Ref{ .source_index = 0, .inner_index = 3 };
- } else {
- var parts = &[_]js_ast.Part{};
- ast = js_ast.Ast.initTest(parts);
- }
- },
- else => {
- return;
- },
- }
-
- switch (ast.parts.len) {
- // It can be empty after parsing too
- // A file like this is an example:
- // "//# sourceMappingURL=validator.js.map"
- 0 => {
- code_offset = try this.writeEmptyModule(module_data.package_path, module_id);
- },
- else => {
- const register_ref = ast.runtime_imports.@"$$m".?.ref;
- const E = js_ast.E;
- const Expr = js_ast.Expr;
- const Stmt = js_ast.Stmt;
-
- var prepend_part: js_ast.Part = undefined;
- var needs_prepend_part = false;
- if (ast.parts.len > 1) {
- for (ast.parts) |part| {
- if (part.tag != .none and part.stmts.len > 0) {
- prepend_part = part;
- needs_prepend_part = true;
- break;
- }
- }
- }
-
- var package_path = js_ast.E.String{ .utf8 = module_data.package_path };
-
- var target_identifier = E.Identifier{ .ref = register_ref };
- var cjs_args: [2]js_ast.G.Arg = undefined;
- var module_binding = js_ast.B.Identifier{ .ref = ast.module_ref.? };
- var exports_binding = js_ast.B.Identifier{ .ref = ast.exports_ref.? };
-
- var part = &ast.parts[ast.parts.len - 1];
-
- var new_stmts: [1]Stmt = undefined;
- var register_args: [1]Expr = undefined;
- var closure = E.Arrow{
- .args = &cjs_args,
- .body = .{
- .loc = logger.Loc.Empty,
- .stmts = part.stmts,
- },
- };
-
- cjs_args[0] = js_ast.G.Arg{
- .binding = js_ast.Binding{
- .loc = logger.Loc.Empty,
- .data = .{ .b_identifier = &module_binding },
- },
- };
- cjs_args[1] = js_ast.G.Arg{
- .binding = js_ast.Binding{
- .loc = logger.Loc.Empty,
- .data = .{ .b_identifier = &exports_binding },
- },
- };
-
- var properties: [1]js_ast.G.Property = undefined;
- var e_object = E.Object{
- .properties = &properties,
- };
- const module_path_str = js_ast.Expr{ .data = .{ .e_string = &package_path }, .loc = logger.Loc.Empty };
- properties[0] = js_ast.G.Property{
- .key = module_path_str,
- .value = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_arrow = &closure } },
- };
-
- // if (!ast.uses_module_ref) {
- // var symbol = &ast.symbols[ast.module_ref.?.inner_index];
- // symbol.original_name = "_$$";
- // }
-
- // $$m(12345, "react", "index.js", function(module, exports) {
-
- // })
- var accessor = js_ast.E.Index{ .index = module_path_str, .target = js_ast.Expr{
- .data = .{ .e_object = &e_object },
- .loc = logger.Loc.Empty,
- } };
- register_args[0] = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_index = &accessor } };
-
- var call_register = E.Call{
- .target = Expr{
- .data = .{ .e_identifier = &target_identifier },
- .loc = logger.Loc{ .start = 0 },
- },
- .args = &register_args,
- };
- var register_expr = Expr{ .loc = call_register.target.loc, .data = .{ .e_call = &call_register } };
- var decls: [1]js_ast.G.Decl = undefined;
- var bundle_export_binding = js_ast.B.Identifier{ .ref = ast.runtime_imports.@"$$m".?.ref };
- var binding = js_ast.Binding{
- .loc = register_expr.loc,
- .data = .{ .b_identifier = &bundle_export_binding },
- };
- decls[0] = js_ast.G.Decl{
- .value = register_expr,
- .binding = binding,
- };
- var export_var = js_ast.S.Local{
- .decls = &decls,
- .is_export = true,
- };
- new_stmts[0] = Stmt{ .loc = register_expr.loc, .data = .{ .s_local = &export_var } };
- part.stmts = &new_stmts;
-
- var writer = js_printer.NewFileWriter(this.tmpfile);
- var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols});
-
- // It should only have one part.
- ast.parts = ast.parts[ast.parts.len - 1 ..];
- const write_result =
- try js_printer.printCommonJSThreaded(
- @TypeOf(writer),
- writer,
- ast,
- js_ast.Symbol.Map.initList(symbols),
- &source,
- false,
- js_printer.Options{
- .to_module_ref = Ref.RuntimeRef,
- .bundle_export_ref = ast.runtime_imports.@"$$m".?.ref,
- .source_path = file_path,
- .externals = ast.externals,
- .indent = 0,
- .require_ref = ast.require_ref,
- .module_hash = module_id,
- .runtime_imports = ast.runtime_imports,
- .prepend_part_value = &prepend_part,
- .prepend_part_key = if (needs_prepend_part) closure.body.stmts.ptr else null,
- },
- Linker,
- &bundler.linker,
- &this.write_lock,
- std.fs.File,
- this.tmpfile,
- std.fs.File.getPos,
- &this.tmpfile_byte_offset,
- );
+ var approximate_newline_count: usize = 0;
+ defer worker.data.estimated_input_lines_of_code += approximate_newline_count;
- code_offset = write_result.off;
- },
- }
- }
+ // Handle empty files
+ // We can't just ignore them. Sometimes code will try to import it. Often because of TypeScript types.
+ // So we just say it's an empty object. Empty object mimicks what "browser": false does as well.
+ // TODO: optimize this so that all the exports for these are done in one line instead of writing repeatedly
+ if (entry.contents.len == 0 or (entry.contents.len < 33 and strings.trim(entry.contents, " \n\r").len == 0)) {
+ code_offset = try this.writeEmptyModule(module_data.package_path, module_id);
+ } else {
+ var ast: js_ast.Ast = undefined;
- if (comptime isDebug) {
- Output.prettyln("{s}@{s}/{s} - {d}:{d} \n", .{ package.name, package.version, package_relative_path, package.hash, module_id });
- Output.flush();
- std.debug.assert(package_relative_path.len > 0);
- }
+ const source = logger.Source.initRecycledFile(
+ Fs.File{
+ .path = file_path,
+ .contents = entry.contents,
+ },
+ bundler.allocator,
+ ) catch return null;
- try this.appendToModuleList(
- package,
- module_id,
- code_offset,
- package_relative_path,
- );
- } else {
- // If it's app code, scan but do not fully parse.
switch (loader) {
.jsx,
.tsx,
.js,
.ts,
=> {
- const entry = bundler.resolver.caches.fs.readFileShared(
- bundler.fs,
- file_path.textZ(),
- resolve.dirname_fd,
- if (resolve.file_fd != 0) resolve.file_fd else null,
- shared_buffer,
- ) catch return;
- if (entry.contents.len == 0 or (entry.contents.len < 33 and strings.trim(entry.contents, " \n\r").len == 0)) return;
-
- const source = logger.Source.initRecycledFile(Fs.File{ .path = file_path, .contents = entry.contents }, bundler.allocator) catch return null;
-
var jsx = bundler.options.jsx;
jsx.parse = loader.isJSX();
+
var opts = js_parser.Parser.Options.init(jsx, loader);
+ opts.transform_require_to_import = false;
+ opts.enable_bundling = true;
+ opts.warn_about_unbundled_modules = false;
- try bundler.resolver.caches.js.scan(
+ ast = (try bundler.resolver.caches.js.parse(
bundler.allocator,
- scan_pass_result,
opts,
bundler.options.define,
log,
&source,
- );
- worker.data.estimated_input_lines_of_code += scan_pass_result.approximate_newline_count;
+ )) orelse return;
+ approximate_newline_count = ast.approximate_newline_count;
+ if (ast.import_records.len > 0) {
+ for (ast.import_records) |*import_record, record_id| {
- {
- for (scan_pass_result.import_records.items) |*import_record, i| {
+ // Don't resolve the runtime
if (import_record.is_internal or import_record.is_unused) {
continue;
}
@@ -1844,93 +1508,51 @@ pub fn NewBundler(cache_files: bool) type {
if (_resolved_import.is_external) {
continue;
}
+ var path = _resolved_import.path() orelse {
+ import_record.path.is_disabled = true;
+ import_record.is_bundled = true;
+ continue;
+ };
- var path = _resolved_import.path() orelse continue;
+ const loader_ = bundler.options.loader(path.name.ext);
- const loader_ = this.bundler.options.loader(path.name.ext);
- if (!loader_.isJavaScriptLikeOrJSON()) continue;
+ if (!loader_.isJavaScriptLikeOrJSON()) {
+ import_record.path.is_disabled = true;
+ import_record.is_bundled = true;
+ continue;
+ }
- path.* = try path.dupeAlloc(this.allocator);
+ // if (_resolved_import.package_json == null) |pkg_json| {
+ // _resolved_import.package_json = if (pkg_json.hash == resolve.package_json.?.hash)
+ // resolve.package_json
+ // else
+ // _resolved_import.package_json;
+ // }
- if (BundledModuleData.get(this, _resolved_import)) |mod| {
- if (comptime !FeatureFlags.bundle_dynamic_import) {
- if (import_record.kind == .dynamic)
- continue;
- } else {
- // When app code dynamically imports a large file
- // Don't bundle it. Leave it as a separate file.
- // The main value from bundling in development is to minimize tiny, waterfall http requests
- // If you're importing > 100 KB file dynamically, developer is probably explicitly trying to do that.
- // There's a tradeoff between "I want to minimize page load time"
- if (import_record.kind == .dynamic) {
- this.dynamic_import_file_size_store_lock.lock();
- defer this.dynamic_import_file_size_store_lock.unlock();
- var dynamic_import_file_size = this.dynamic_import_file_size_store.getOrPut(mod.module_id) catch unreachable;
- if (!dynamic_import_file_size.found_existing) {
- var fd = _resolved_import.file_fd;
- var can_close = false;
- if (fd == 0) {
- dynamic_import_file_size.value_ptr.* = 0;
- fd = (std.fs.openFileAbsolute(path.textZ(), .{}) catch |err| {
- this.log.addRangeWarningFmt(
- &source,
- import_record.range,
- worker.allocator,
- "{s} opening file: \"{s}\"",
- .{ @errorName(err), path.text },
- ) catch unreachable;
- continue;
- }).handle;
- can_close = true;
- Fs.FileSystem.setMaxFd(fd);
- }
-
- defer {
- if (can_close and bundler.fs.fs.needToCloseFiles()) {
- var _file = std.fs.File{ .handle = fd };
- _file.close();
- _resolved_import.file_fd = 0;
- } else if (FeatureFlags.store_file_descriptors) {
- _resolved_import.file_fd = fd;
- }
- }
+ const resolved_import: *const _resolver.Result = _resolved_import;
- var file = std.fs.File{ .handle = fd };
- var stat = file.stat() catch |err| {
- this.log.addRangeWarningFmt(
- &source,
- import_record.range,
- worker.allocator,
- "{s} stat'ing file: \"{s}\"",
- .{ @errorName(err), path.text },
- ) catch unreachable;
- dynamic_import_file_size.value_ptr.* = 0;
- continue;
- };
+ const _module_data = BundledModuleData.getForceBundle(this, resolved_import) orelse unreachable;
+ import_record.module_id = _module_data.module_id;
+ std.debug.assert(import_record.module_id != 0);
+ import_record.is_bundled = true;
- dynamic_import_file_size.value_ptr.* = @truncate(u32, stat.size);
- }
+ path.* = try path.dupeAlloc(this.allocator);
- if (dynamic_import_file_size.value_ptr.* > 1024 * 100)
- continue;
- }
- }
+ import_record.path = path.*;
- std.debug.assert(mod.module_id != 0);
- try this.queue.upsert(
- mod.module_id,
- _resolved_import.*,
- );
- } else {
- try this.queue.upsert(
- _resolved_import.hash(
- this.bundler.fs.top_level_dir,
- loader_,
- ),
- _resolved_import.*,
- );
- }
+ try this.queue.upsert(
+ _module_data.module_id,
+ _resolved_import.*,
+ );
} else |err| {
+ if (comptime isDebug) {
+ Output.prettyErrorln("\n<r><red>{s}<r> on resolving \"{s}\" from \"{s}\"", .{
+ @errorName(err),
+ import_record.path.text,
+ file_path.text,
+ });
+ }
+
switch (err) {
error.ModuleNotFound => {
if (isPackagePath(import_record.path.text)) {
@@ -1973,862 +1595,1238 @@ pub fn NewBundler(cache_files: bool) type {
}
}
},
- else => {},
+ .json => {
+ // parse the JSON _only_ to catch errors at build time.
+ const json_parse_result = json_parser.ParseJSONForBundling(&source, worker.data.log, worker.allocator) catch return;
+
+ if (json_parse_result.tag != .empty) {
+ const expr = brk: {
+ // If it's an ascii string, we just print it out with a big old JSON.parse()
+ if (json_parse_result.tag == .ascii) {
+ json_e_string = js_ast.E.String{ .utf8 = source.contents, .prefer_template = true };
+ var json_string_expr = js_ast.Expr{ .data = .{ .e_string = &json_e_string }, .loc = logger.Loc{ .start = 0 } };
+ json_call_args[0] = json_string_expr;
+ json_e_identifier = js_ast.E.Identifier{ .ref = Ref{ .source_index = 0, .inner_index = @intCast(Ref.Int, json_ast_symbols_list.len - 1) } };
+
+ json_e_call = js_ast.E.Call{
+ .target = js_ast.Expr{ .data = .{ .e_identifier = &json_e_identifier }, .loc = logger.Loc{ .start = 0 } },
+ .args = std.mem.span(&json_call_args),
+ };
+ break :brk js_ast.Expr{ .data = .{ .e_call = &json_e_call }, .loc = logger.Loc{ .start = 0 } };
+ // If we're going to have to convert it to a UTF16, just make it an object actually
+ } else {
+ break :brk json_parse_result.expr;
+ }
+ };
+
+ var stmt = js_ast.Stmt.alloc(worker.allocator, js_ast.S.ExportDefault, js_ast.S.ExportDefault{
+ .value = js_ast.StmtOrExpr{ .expr = expr },
+ .default_name = js_ast.LocRef{ .loc = logger.Loc{}, .ref = Ref{} },
+ }, logger.Loc{ .start = 0 });
+ var stmts = worker.allocator.alloc(js_ast.Stmt, 1) catch unreachable;
+ stmts[0] = stmt;
+ var parts = worker.allocator.alloc(js_ast.Part, 1) catch unreachable;
+ parts[0] = js_ast.Part{ .stmts = stmts };
+ ast = js_ast.Ast.initTest(parts);
+
+ ast.runtime_imports = runtime.Runtime.Imports{};
+ ast.runtime_imports.@"$$m" = .{ .ref = Ref{ .source_index = 0, .inner_index = 0 }, .primary = Ref.None, .backup = Ref.None };
+ ast.runtime_imports.__export = .{ .ref = Ref{ .source_index = 0, .inner_index = 1 }, .primary = Ref.None, .backup = Ref.None };
+ ast.symbols = json_ast_symbols_list;
+ ast.module_ref = Ref{ .source_index = 0, .inner_index = 2 };
+ ast.exports_ref = ast.runtime_imports.__export.?.ref;
+ ast.bundle_export_ref = Ref{ .source_index = 0, .inner_index = 3 };
+ } else {
+ var parts = &[_]js_ast.Part{};
+ ast = js_ast.Ast.initTest(parts);
+ }
+ },
+ else => {
+ return;
+ },
}
- }
- }
- };
- pub const BuildResolveResultPair = struct {
- written: usize,
- input_fd: ?StoredFileDescriptorType,
- empty: bool = false,
- };
- pub fn buildWithResolveResult(
- bundler: *ThisBundler,
- resolve_result: _resolver.Result,
- allocator: *std.mem.Allocator,
- loader: options.Loader,
- comptime Writer: type,
- writer: Writer,
- comptime import_path_format: options.BundleOptions.ImportPathFormat,
- file_descriptor: ?StoredFileDescriptorType,
- filepath_hash: u32,
- comptime WatcherType: type,
- watcher: *WatcherType,
- client_entry_point: ?*ClientEntryPoint,
- ) !BuildResolveResultPair {
- if (resolve_result.is_external) {
- return BuildResolveResultPair{
- .written = 0,
- .input_fd = null,
- };
- }
+ switch (ast.parts.len) {
+ // It can be empty after parsing too
+ // A file like this is an example:
+ // "//# sourceMappingURL=validator.js.map"
+ 0 => {
+ code_offset = try this.writeEmptyModule(module_data.package_path, module_id);
+ },
+ else => {
+ const register_ref = ast.runtime_imports.@"$$m".?.ref;
+ const E = js_ast.E;
+ const Expr = js_ast.Expr;
+ const Stmt = js_ast.Stmt;
+
+ var prepend_part: js_ast.Part = undefined;
+ var needs_prepend_part = false;
+ if (ast.parts.len > 1) {
+ for (ast.parts) |part| {
+ if (part.tag != .none and part.stmts.len > 0) {
+ prepend_part = part;
+ needs_prepend_part = true;
+ break;
+ }
+ }
+ }
- errdefer bundler.resetStore();
+ var package_path = js_ast.E.String{ .utf8 = module_data.package_path };
- var file_path = (resolve_result.pathConst() orelse {
- return BuildResolveResultPair{
- .written = 0,
- .input_fd = null,
- };
- }).*;
+ var target_identifier = E.Identifier{ .ref = register_ref };
+ var cjs_args: [2]js_ast.G.Arg = undefined;
+ var module_binding = js_ast.B.Identifier{ .ref = ast.module_ref.? };
+ var exports_binding = js_ast.B.Identifier{ .ref = ast.exports_ref.? };
- if (strings.indexOf(file_path.text, bundler.fs.top_level_dir)) |i| {
- file_path.pretty = file_path.text[i + bundler.fs.top_level_dir.len ..];
- } else if (!file_path.is_symlink) {
- file_path.pretty = allocator.dupe(u8, bundler.fs.relativeTo(file_path.text)) catch unreachable;
- }
+ var part = &ast.parts[ast.parts.len - 1];
- var old_bundler_allocator = bundler.allocator;
- bundler.allocator = allocator;
- defer bundler.allocator = old_bundler_allocator;
- var old_linker_allocator = bundler.linker.allocator;
- defer bundler.linker.allocator = old_linker_allocator;
- bundler.linker.allocator = allocator;
-
- switch (loader) {
- .css => {
- const CSSBundlerHMR = Css.NewBundler(
- Writer,
- @TypeOf(&bundler.linker),
- @TypeOf(&bundler.resolver.caches.fs),
- WatcherType,
- @TypeOf(bundler.fs),
- true,
- );
+ var new_stmts: [1]Stmt = undefined;
+ var register_args: [1]Expr = undefined;
+ var closure = E.Arrow{
+ .args = &cjs_args,
+ .body = .{
+ .loc = logger.Loc.Empty,
+ .stmts = part.stmts,
+ },
+ };
- const CSSBundler = Css.NewBundler(
- Writer,
- @TypeOf(&bundler.linker),
- @TypeOf(&bundler.resolver.caches.fs),
- WatcherType,
- @TypeOf(bundler.fs),
- false,
- );
+ cjs_args[0] = js_ast.G.Arg{
+ .binding = js_ast.Binding{
+ .loc = logger.Loc.Empty,
+ .data = .{ .b_identifier = &module_binding },
+ },
+ };
+ cjs_args[1] = js_ast.G.Arg{
+ .binding = js_ast.Binding{
+ .loc = logger.Loc.Empty,
+ .data = .{ .b_identifier = &exports_binding },
+ },
+ };
- return BuildResolveResultPair{
- .written = brk: {
- if (bundler.options.hot_module_reloading) {
- break :brk (try CSSBundlerHMR.bundle(
- file_path.text,
- bundler.fs,
- writer,
- watcher,
- &bundler.resolver.caches.fs,
- filepath_hash,
- file_descriptor,
- allocator,
- bundler.log,
- &bundler.linker,
- )).written;
- } else {
- break :brk (try CSSBundler.bundle(
- file_path.text,
- bundler.fs,
- writer,
- watcher,
- &bundler.resolver.caches.fs,
- filepath_hash,
- file_descriptor,
- allocator,
- bundler.log,
- &bundler.linker,
- )).written;
- }
- },
- .input_fd = file_descriptor,
- };
- },
- else => {
- var result = bundler.parse(
- allocator,
- file_path,
- loader,
- resolve_result.dirname_fd,
- file_descriptor,
- filepath_hash,
- client_entry_point,
- ) orelse {
- bundler.resetStore();
- return BuildResolveResultPair{
- .written = 0,
- .input_fd = null,
- };
- };
+ var properties: [1]js_ast.G.Property = undefined;
+ var e_object = E.Object{
+ .properties = &properties,
+ };
+ const module_path_str = js_ast.Expr{ .data = .{ .e_string = &package_path }, .loc = logger.Loc.Empty };
+ properties[0] = js_ast.G.Property{
+ .key = module_path_str,
+ .value = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_arrow = &closure } },
+ };
- if (result.empty) {
- return BuildResolveResultPair{ .written = 0, .input_fd = result.input_fd, .empty = true };
- }
+ // if (!ast.uses_module_ref) {
+ // var symbol = &ast.symbols[ast.module_ref.?.inner_index];
+ // symbol.original_name = "_$$";
+ // }
- try bundler.linker.link(file_path, &result, import_path_format, false);
+ // $$m(12345, "react", "index.js", function(module, exports) {
+
+ // })
+ var accessor = js_ast.E.Index{ .index = module_path_str, .target = js_ast.Expr{
+ .data = .{ .e_object = &e_object },
+ .loc = logger.Loc.Empty,
+ } };
+ register_args[0] = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_index = &accessor } };
+
+ var call_register = E.Call{
+ .target = Expr{
+ .data = .{ .e_identifier = &target_identifier },
+ .loc = logger.Loc{ .start = 0 },
+ },
+ .args = &register_args,
+ };
+ var register_expr = Expr{ .loc = call_register.target.loc, .data = .{ .e_call = &call_register } };
+ var decls: [1]js_ast.G.Decl = undefined;
+ var bundle_export_binding = js_ast.B.Identifier{ .ref = ast.runtime_imports.@"$$m".?.ref };
+ var binding = js_ast.Binding{
+ .loc = register_expr.loc,
+ .data = .{ .b_identifier = &bundle_export_binding },
+ };
+ decls[0] = js_ast.G.Decl{
+ .value = register_expr,
+ .binding = binding,
+ };
+ var export_var = js_ast.S.Local{
+ .decls = &decls,
+ .is_export = true,
+ };
+ new_stmts[0] = Stmt{ .loc = register_expr.loc, .data = .{ .s_local = &export_var } };
+ part.stmts = &new_stmts;
- return BuildResolveResultPair{
- .written = switch (result.ast.exports_kind) {
- .esm => try bundler.print(
- result,
- Writer,
- writer,
- .esm,
- ),
- .cjs => try bundler.print(
- result,
- Writer,
+ var writer = js_printer.NewFileWriter(this.tmpfile);
+ var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols});
+
+ // It should only have one part.
+ ast.parts = ast.parts[ast.parts.len - 1 ..];
+ const write_result =
+ try js_printer.printCommonJSThreaded(
+ @TypeOf(writer),
writer,
- .cjs,
- ),
- else => unreachable,
+ ast,
+ js_ast.Symbol.Map.initList(symbols),
+ &source,
+ false,
+ js_printer.Options{
+ .to_module_ref = Ref.RuntimeRef,
+ .bundle_export_ref = ast.runtime_imports.@"$$m".?.ref,
+ .source_path = file_path,
+ .externals = ast.externals,
+ .indent = 0,
+ .require_ref = ast.require_ref,
+ .module_hash = module_id,
+ .runtime_imports = ast.runtime_imports,
+ .prepend_part_value = &prepend_part,
+ .prepend_part_key = if (needs_prepend_part) closure.body.stmts.ptr else null,
+ },
+ Linker,
+ &bundler.linker,
+ &this.write_lock,
+ std.fs.File,
+ this.tmpfile,
+ std.fs.File.getPos,
+ &this.tmpfile_byte_offset,
+ );
+
+ code_offset = write_result.off;
},
- .input_fd = result.input_fd,
- };
- },
- }
- }
+ }
+ }
- pub fn buildWithResolveResultEager(
- bundler: *ThisBundler,
- resolve_result: _resolver.Result,
- comptime import_path_format: options.BundleOptions.ImportPathFormat,
- comptime Outstream: type,
- outstream: Outstream,
- client_entry_point_: ?*ClientEntryPoint,
- ) !?options.OutputFile {
- if (resolve_result.is_external) {
- return null;
- }
+ if (comptime isDebug) {
+ Output.prettyln("{s}@{s}/{s} - {d}:{d} \n", .{ package.name, package.version, package_relative_path, package.hash, module_id });
+ Output.flush();
+ std.debug.assert(package_relative_path.len > 0);
+ }
- var file_path = (resolve_result.pathConst() orelse return null).*;
+ try this.appendToModuleList(
+ package,
+ module_id,
+ code_offset,
+ package_relative_path,
+ );
+ } else {
+ // If it's app code, scan but do not fully parse.
+ switch (loader) {
+ .jsx,
+ .tsx,
+ .js,
+ .ts,
+ => {
+ const entry = bundler.resolver.caches.fs.readFileShared(
+ bundler.fs,
+ file_path.textZ(),
+ resolve.dirname_fd,
+ if (resolve.file_fd != 0) resolve.file_fd else null,
+ shared_buffer,
+ ) catch return;
+ if (entry.contents.len == 0 or (entry.contents.len < 33 and strings.trim(entry.contents, " \n\r").len == 0)) return;
+
+ const source = logger.Source.initRecycledFile(Fs.File{ .path = file_path, .contents = entry.contents }, bundler.allocator) catch return null;
+
+ var jsx = bundler.options.jsx;
+ jsx.parse = loader.isJSX();
+ var opts = js_parser.Parser.Options.init(jsx, loader);
+
+ try bundler.resolver.caches.js.scan(
+ bundler.allocator,
+ scan_pass_result,
+ opts,
+ bundler.options.define,
+ log,
+ &source,
+ );
+ worker.data.estimated_input_lines_of_code += scan_pass_result.approximate_newline_count;
- // Step 1. Parse & scan
- const loader = bundler.options.loader(file_path.name.ext);
+ {
+ for (scan_pass_result.import_records.items) |*import_record, i| {
+ if (import_record.is_internal or import_record.is_unused) {
+ continue;
+ }
- if (client_entry_point_) |client_entry_point| {
- file_path = client_entry_point.source.path;
- }
+ if (bundler.linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*_resolved_import| {
+ if (_resolved_import.is_external) {
+ continue;
+ }
- file_path.pretty = Linker.relative_paths_list.append(string, bundler.fs.relativeTo(file_path.text)) catch unreachable;
+ var path = _resolved_import.path() orelse continue;
- var output_file = options.OutputFile{
- .input = file_path,
- .loader = loader,
- .value = undefined,
- };
+ const loader_ = this.bundler.options.loader(path.name.ext);
+ if (!loader_.isJavaScriptLikeOrJSON()) continue;
- var file: std.fs.File = undefined;
+ path.* = try path.dupeAlloc(this.allocator);
- if (Outstream == std.fs.Dir) {
- const output_dir = outstream;
+ if (BundledModuleData.get(this, _resolved_import)) |mod| {
+ if (comptime !FeatureFlags.bundle_dynamic_import) {
+ if (import_record.kind == .dynamic)
+ continue;
+ } else {
+ // When app code dynamically imports a large file
+ // Don't bundle it. Leave it as a separate file.
+ // The main value from bundling in development is to minimize tiny, waterfall http requests
+ // If you're importing > 100 KB file dynamically, developer is probably explicitly trying to do that.
+ // There's a tradeoff between "I want to minimize page load time"
+ if (import_record.kind == .dynamic) {
+ this.dynamic_import_file_size_store_lock.lock();
+ defer this.dynamic_import_file_size_store_lock.unlock();
+ var dynamic_import_file_size = this.dynamic_import_file_size_store.getOrPut(mod.module_id) catch unreachable;
+ if (!dynamic_import_file_size.found_existing) {
+ var fd = _resolved_import.file_fd;
+ var can_close = false;
+ if (fd == 0) {
+ dynamic_import_file_size.value_ptr.* = 0;
+ fd = (std.fs.openFileAbsolute(path.textZ(), .{}) catch |err| {
+ this.log.addRangeWarningFmt(
+ &source,
+ import_record.range,
+ worker.allocator,
+ "{s} opening file: \"{s}\"",
+ .{ @errorName(err), path.text },
+ ) catch unreachable;
+ continue;
+ }).handle;
+ can_close = true;
+ Fs.FileSystem.setMaxFd(fd);
+ }
+
+ defer {
+ if (can_close and bundler.fs.fs.needToCloseFiles()) {
+ var _file = std.fs.File{ .handle = fd };
+ _file.close();
+ _resolved_import.file_fd = 0;
+ } else if (FeatureFlags.store_file_descriptors) {
+ _resolved_import.file_fd = fd;
+ }
+ }
- if (std.fs.path.dirname(file_path.pretty)) |dirname| {
- try output_dir.makePath(dirname);
+ var file = std.fs.File{ .handle = fd };
+ var stat = file.stat() catch |err| {
+ this.log.addRangeWarningFmt(
+ &source,
+ import_record.range,
+ worker.allocator,
+ "{s} stat'ing file: \"{s}\"",
+ .{ @errorName(err), path.text },
+ ) catch unreachable;
+ dynamic_import_file_size.value_ptr.* = 0;
+ continue;
+ };
+
+ dynamic_import_file_size.value_ptr.* = @truncate(u32, stat.size);
+ }
+
+ if (dynamic_import_file_size.value_ptr.* > 1024 * 100)
+ continue;
+ }
+ }
+
+ std.debug.assert(mod.module_id != 0);
+ try this.queue.upsert(
+ mod.module_id,
+ _resolved_import.*,
+ );
+ } else {
+ try this.queue.upsert(
+ _resolved_import.hash(
+ this.bundler.fs.top_level_dir,
+ loader_,
+ ),
+ _resolved_import.*,
+ );
+ }
+ } else |err| {
+ switch (err) {
+ error.ModuleNotFound => {
+ if (isPackagePath(import_record.path.text)) {
+ if (this.bundler.options.platform.isWebLike() and options.ExternalModules.isNodeBuiltin(import_record.path.text)) {
+ try log.addResolveErrorWithTextDupe(
+ &source,
+ import_record.range,
+ this.allocator,
+ "Could not resolve Node.js builtin: \"{s}\".",
+ .{import_record.path.text},
+ import_record.kind,
+ );
+ } else {
+ try log.addResolveErrorWithTextDupe(
+ &source,
+ import_record.range,
+ this.allocator,
+ "Could not resolve: \"{s}\". Maybe you need to \"npm install\" (or yarn/pnpm)?",
+ .{import_record.path.text},
+ import_record.kind,
+ );
+ }
+ } else {
+ try log.addResolveErrorWithTextDupe(
+ &source,
+ import_record.range,
+ this.allocator,
+ "Could not resolve: \"{s}\"",
+ .{
+ import_record.path.text,
+ },
+ import_record.kind,
+ );
+ }
+ },
+ // assume other errors are already in the log
+ else => {},
+ }
+ }
+ }
+ }
+ },
+ else => {},
}
- file = try output_dir.createFile(file_path.pretty, .{});
- } else {
- file = outstream;
}
+ }
+ };
- switch (loader) {
- .jsx, .tsx, .js, .ts, .json => {
- var result = bundler.parse(
- bundler.allocator,
- file_path,
- loader,
- resolve_result.dirname_fd,
- null,
- null,
- client_entry_point_,
- ) orelse {
- return null;
- };
+ pub const BuildResolveResultPair = struct {
+ written: usize,
+ input_fd: ?StoredFileDescriptorType,
+ empty: bool = false,
+ };
+ pub fn buildWithResolveResult(
+ bundler: *ThisBundler,
+ resolve_result: _resolver.Result,
+ allocator: *std.mem.Allocator,
+ loader: options.Loader,
+ comptime Writer: type,
+ writer: Writer,
+ comptime import_path_format: options.BundleOptions.ImportPathFormat,
+ file_descriptor: ?StoredFileDescriptorType,
+ filepath_hash: u32,
+ comptime WatcherType: type,
+ watcher: *WatcherType,
+ client_entry_point: ?*ClientEntryPoint,
+ ) !BuildResolveResultPair {
+ if (resolve_result.is_external) {
+ return BuildResolveResultPair{
+ .written = 0,
+ .input_fd = null,
+ };
+ }
- try bundler.linker.link(
- file_path,
- &result,
- import_path_format,
- false,
- );
+ errdefer bundler.resetStore();
- output_file.size = try bundler.print(
- result,
- js_printer.FileWriter,
- js_printer.NewFileWriter(file),
- .esm,
- );
+ var file_path = (resolve_result.pathConst() orelse {
+ return BuildResolveResultPair{
+ .written = 0,
+ .input_fd = null,
+ };
+ }).*;
- var file_op = options.OutputFile.FileOperation.fromFile(file.handle, file_path.pretty);
+ if (strings.indexOf(file_path.text, bundler.fs.top_level_dir)) |i| {
+ file_path.pretty = file_path.text[i + bundler.fs.top_level_dir.len ..];
+ } else if (!file_path.is_symlink) {
+ file_path.pretty = allocator.dupe(u8, bundler.fs.relativeTo(file_path.text)) catch unreachable;
+ }
- file_op.fd = file.handle;
+ var old_bundler_allocator = bundler.allocator;
+ bundler.allocator = allocator;
+ defer bundler.allocator = old_bundler_allocator;
+ var old_linker_allocator = bundler.linker.allocator;
+ defer bundler.linker.allocator = old_linker_allocator;
+ bundler.linker.allocator = allocator;
- file_op.is_tmpdir = false;
+ switch (loader) {
+ .css => {
+ const CSSBundlerHMR = Css.NewBundler(
+ Writer,
+ @TypeOf(&bundler.linker),
+ @TypeOf(&bundler.resolver.caches.fs),
+ WatcherType,
+ @TypeOf(bundler.fs),
+ true,
+ );
- if (Outstream == std.fs.Dir) {
- file_op.dir = outstream.fd;
+ const CSSBundler = Css.NewBundler(
+ Writer,
+ @TypeOf(&bundler.linker),
+ @TypeOf(&bundler.resolver.caches.fs),
+ WatcherType,
+ @TypeOf(bundler.fs),
+ false,
+ );
- if (bundler.fs.fs.needToCloseFiles()) {
- file.close();
- file_op.fd = 0;
+ return BuildResolveResultPair{
+ .written = brk: {
+ if (bundler.options.hot_module_reloading) {
+ break :brk (try CSSBundlerHMR.bundle(
+ file_path.text,
+ bundler.fs,
+ writer,
+ watcher,
+ &bundler.resolver.caches.fs,
+ filepath_hash,
+ file_descriptor,
+ allocator,
+ bundler.log,
+ &bundler.linker,
+ )).written;
+ } else {
+ break :brk (try CSSBundler.bundle(
+ file_path.text,
+ bundler.fs,
+ writer,
+ watcher,
+ &bundler.resolver.caches.fs,
+ filepath_hash,
+ file_descriptor,
+ allocator,
+ bundler.log,
+ &bundler.linker,
+ )).written;
}
- }
+ },
+ .input_fd = file_descriptor,
+ };
+ },
+ else => {
+ var result = bundler.parse(
+ allocator,
+ file_path,
+ loader,
+ resolve_result.dirname_fd,
+ file_descriptor,
+ filepath_hash,
+ client_entry_point,
+ ) orelse {
+ bundler.resetStore();
+ return BuildResolveResultPair{
+ .written = 0,
+ .input_fd = null,
+ };
+ };
- output_file.value = .{ .move = file_op };
- },
- .css => {
- const CSSWriter = Css.NewWriter(
- std.fs.File,
- @TypeOf(&bundler.linker),
- import_path_format,
- void,
- );
- const entry = bundler.resolver.caches.fs.readFile(
- bundler.fs,
- file_path.text,
- resolve_result.dirname_fd,
- !cache_files,
- null,
- ) catch return null;
+ if (result.empty) {
+ return BuildResolveResultPair{ .written = 0, .input_fd = result.input_fd, .empty = true };
+ }
- const _file = Fs.File{ .path = file_path, .contents = entry.contents };
- var source = try logger.Source.initFile(_file, bundler.allocator);
- source.contents_is_recycled = !cache_files;
- var css_writer = CSSWriter.init(
- &source,
- file,
- &bundler.linker,
- bundler.log,
- );
- var did_warn = false;
- try css_writer.run(bundler.log, bundler.allocator, &did_warn);
- output_file.size = css_writer.written;
- var file_op = options.OutputFile.FileOperation.fromFile(file.handle, file_path.pretty);
+ try bundler.linker.link(file_path, &result, import_path_format, false);
- file_op.fd = file.handle;
+ return BuildResolveResultPair{
+ .written = switch (result.ast.exports_kind) {
+ .esm => try bundler.print(
+ result,
+ Writer,
+ writer,
+ .esm,
+ ),
+ .cjs => try bundler.print(
+ result,
+ Writer,
+ writer,
+ .cjs,
+ ),
+ else => unreachable,
+ },
+ .input_fd = result.input_fd,
+ };
+ },
+ }
+ }
- file_op.is_tmpdir = false;
+ pub fn buildWithResolveResultEager(
+ bundler: *ThisBundler,
+ resolve_result: _resolver.Result,
+ comptime import_path_format: options.BundleOptions.ImportPathFormat,
+ comptime Outstream: type,
+ outstream: Outstream,
+ client_entry_point_: ?*ClientEntryPoint,
+ ) !?options.OutputFile {
+ if (resolve_result.is_external) {
+ return null;
+ }
- if (Outstream == std.fs.Dir) {
- file_op.dir = outstream.fd;
+ var file_path = (resolve_result.pathConst() orelse return null).*;
- if (bundler.fs.fs.needToCloseFiles()) {
- file.close();
- file_op.fd = 0;
- }
- }
+ // Step 1. Parse & scan
+ const loader = bundler.options.loader(file_path.name.ext);
- output_file.value = .{ .move = file_op };
- },
- .file => {
- var hashed_name = try bundler.linker.getHashedFilename(file_path, null);
- var pathname = try bundler.allocator.alloc(u8, hashed_name.len + file_path.name.ext.len);
- std.mem.copy(u8, pathname, hashed_name);
- std.mem.copy(u8, pathname[hashed_name.len..], file_path.name.ext);
- const dir = if (bundler.options.output_dir_handle) |output_handle| output_handle.fd else 0;
-
- output_file.value = .{
- .copy = options.OutputFile.FileOperation{
- .pathname = pathname,
- .dir = dir,
- .is_outdir = true,
- },
- };
- },
+ if (client_entry_point_) |client_entry_point| {
+ file_path = client_entry_point.source.path;
+ }
- // // TODO:
- // else => {},
- }
+ file_path.pretty = Linker.relative_paths_list.append(string, bundler.fs.relativeTo(file_path.text)) catch unreachable;
+
+ var output_file = options.OutputFile{
+ .input = file_path,
+ .loader = loader,
+ .value = undefined,
+ };
- return output_file;
+ var file: std.fs.File = undefined;
+
+ if (Outstream == std.fs.Dir) {
+ const output_dir = outstream;
+
+ if (std.fs.path.dirname(file_path.pretty)) |dirname| {
+ try output_dir.makePath(dirname);
+ }
+ file = try output_dir.createFile(file_path.pretty, .{});
+ } else {
+ file = outstream;
}
- pub fn print(
- bundler: *ThisBundler,
- result: ParseResult,
- comptime Writer: type,
- writer: Writer,
- comptime format: js_printer.Format,
- ) !usize {
- const ast = result.ast;
- var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols});
-
- return switch (format) {
- .cjs => try js_printer.printCommonJS(
- Writer,
- writer,
- ast,
- js_ast.Symbol.Map.initList(symbols),
- &result.source,
- false,
- js_printer.Options{
- .to_module_ref = Ref.RuntimeRef,
- .externals = ast.externals,
- .runtime_imports = ast.runtime_imports,
- .require_ref = ast.require_ref,
- .css_import_behavior = bundler.options.cssImportBehavior(),
- },
- Linker,
- &bundler.linker,
- ),
- .esm => try js_printer.printAst(
- Writer,
- writer,
- ast,
- js_ast.Symbol.Map.initList(symbols),
- &result.source,
+ switch (loader) {
+ .jsx, .tsx, .js, .ts, .json => {
+ var result = bundler.parse(
+ bundler.allocator,
+ file_path,
+ loader,
+ resolve_result.dirname_fd,
+ null,
+ null,
+ client_entry_point_,
+ ) orelse {
+ return null;
+ };
+
+ try bundler.linker.link(
+ file_path,
+ &result,
+ import_path_format,
false,
- js_printer.Options{
- .to_module_ref = Ref.RuntimeRef,
- .externals = ast.externals,
- .runtime_imports = ast.runtime_imports,
- .require_ref = ast.require_ref,
+ );
- .css_import_behavior = bundler.options.cssImportBehavior(),
- },
- Linker,
- &bundler.linker,
- ),
- };
- }
+ output_file.size = try bundler.print(
+ result,
+ js_printer.FileWriter,
+ js_printer.NewFileWriter(file),
+ .esm,
+ );
- pub fn parse(
- bundler: *ThisBundler,
- allocator: *std.mem.Allocator,
- path: Fs.Path,
- loader: options.Loader,
- // only used when file_descriptor is null
- dirname_fd: StoredFileDescriptorType,
- file_descriptor: ?StoredFileDescriptorType,
- file_hash: ?u32,
- client_entry_point_: anytype,
- ) ?ParseResult {
- if (FeatureFlags.tracing) {
- bundler.timer.start();
- }
- defer {
- if (FeatureFlags.tracing) {
- bundler.timer.stop();
- bundler.elapsed += bundler.timer.elapsed;
- }
- }
- var result: ParseResult = undefined;
- var input_fd: ?StoredFileDescriptorType = null;
+ var file_op = options.OutputFile.FileOperation.fromFile(file.handle, file_path.pretty);
- const source: logger.Source = brk: {
- if (client_entry_point_) |client_entry_point| {
- if (@hasField(std.meta.Child(@TypeOf(client_entry_point)), "source")) {
- break :brk client_entry_point.source;
- }
- }
+ file_op.fd = file.handle;
- if (strings.eqlComptime(path.namespace, "node")) {
- if (NodeFallbackModules.contentsFromPath(path.text)) |code| {
- break :brk logger.Source.initPathString(path.text, code);
- }
+ file_op.is_tmpdir = false;
- break :brk logger.Source.initPathString(path.text, "");
+ if (Outstream == std.fs.Dir) {
+ file_op.dir = outstream.fd;
+
+ if (bundler.fs.fs.needToCloseFiles()) {
+ file.close();
+ file_op.fd = 0;
+ }
}
+ output_file.value = .{ .move = file_op };
+ },
+ .css => {
+ const CSSWriter = Css.NewWriter(
+ std.fs.File,
+ @TypeOf(&bundler.linker),
+ import_path_format,
+ void,
+ );
const entry = bundler.resolver.caches.fs.readFile(
bundler.fs,
- path.text,
- dirname_fd,
- true,
- file_descriptor,
- ) catch |err| {
- bundler.log.addErrorFmt(null, logger.Loc.Empty, bundler.allocator, "{s} reading \"{s}\"", .{ @errorName(err), path.text }) catch {};
- return null;
- };
- input_fd = entry.fd;
- break :brk logger.Source.initRecycledFile(Fs.File{ .path = path, .contents = entry.contents }, bundler.allocator) catch return null;
- };
+ file_path.text,
+ resolve_result.dirname_fd,
+ !cache_files,
+ null,
+ ) catch return null;
+
+ const _file = Fs.File{ .path = file_path, .contents = entry.contents };
+ var source = try logger.Source.initFile(_file, bundler.allocator);
+ source.contents_is_recycled = !cache_files;
+ var css_writer = CSSWriter.init(
+ &source,
+ file,
+ &bundler.linker,
+ bundler.log,
+ );
+ var did_warn = false;
+ try css_writer.run(bundler.log, bundler.allocator, &did_warn);
+ output_file.size = css_writer.written;
+ var file_op = options.OutputFile.FileOperation.fromFile(file.handle, file_path.pretty);
- if (source.contents.len == 0 or (source.contents.len < 33 and std.mem.trim(u8, source.contents, "\n\r ").len == 0)) {
- return ParseResult{ .source = source, .input_fd = input_fd, .loader = loader, .empty = true, .ast = js_ast.Ast.empty };
- }
+ file_op.fd = file.handle;
- switch (loader) {
- .js,
- .jsx,
- .ts,
- .tsx,
- => {
- var jsx = bundler.options.jsx;
- jsx.parse = loader.isJSX();
- var opts = js_parser.Parser.Options.init(jsx, loader);
- opts.enable_bundling = false;
- opts.transform_require_to_import = true;
- opts.can_import_from_bundle = bundler.options.node_modules_bundle != null;
-
- // HMR is enabled when devserver is running
- // unless you've explicitly disabled it
- // or you're running in SSR
- // or the file is a node_module
- opts.features.hot_module_reloading = bundler.options.hot_module_reloading and
- bundler.options.platform != .bun and
- (!opts.can_import_from_bundle or
- (opts.can_import_from_bundle and !path.isNodeModule()));
- opts.features.react_fast_refresh = opts.features.hot_module_reloading and
- jsx.parse and
- bundler.options.jsx.supports_fast_refresh;
- opts.filepath_hash_for_hmr = file_hash orelse 0;
- opts.warn_about_unbundled_modules = bundler.options.platform != .bun;
- const value = (bundler.resolver.caches.js.parse(
- allocator,
- opts,
- bundler.options.define,
- bundler.log,
- &source,
- ) catch null) orelse return null;
- return ParseResult{
- .ast = value,
- .source = source,
- .loader = loader,
- .input_fd = input_fd,
- };
- },
- .json => {
- var expr = json_parser.ParseJSON(&source, bundler.log, allocator) catch return null;
- var stmt = js_ast.Stmt.alloc(allocator, js_ast.S.ExportDefault, js_ast.S.ExportDefault{
- .value = js_ast.StmtOrExpr{ .expr = expr },
- .default_name = js_ast.LocRef{ .loc = logger.Loc{}, .ref = Ref{} },
- }, logger.Loc{ .start = 0 });
- var stmts = allocator.alloc(js_ast.Stmt, 1) catch unreachable;
- stmts[0] = stmt;
- var parts = allocator.alloc(js_ast.Part, 1) catch unreachable;
- parts[0] = js_ast.Part{ .stmts = stmts };
-
- return ParseResult{
- .ast = js_ast.Ast.initTest(parts),
- .source = source,
- .loader = loader,
- .input_fd = input_fd,
- };
- },
- .css => {},
- else => Global.panic("Unsupported loader {s} for path: {s}", .{ loader, source.path.text }),
- }
+ file_op.is_tmpdir = false;
- return null;
- }
+ if (Outstream == std.fs.Dir) {
+ file_op.dir = outstream.fd;
- // This is public so it can be used by the HTTP handler when matching against public dir.
- pub threadlocal var tmp_buildfile_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- threadlocal var tmp_buildfile_buf2: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ if (bundler.fs.fs.needToCloseFiles()) {
+ file.close();
+ file_op.fd = 0;
+ }
+ }
- // We try to be mostly stateless when serving
- // This means we need a slightly different resolver setup
- pub fn buildFile(
- bundler: *ThisBundler,
- log: *logger.Log,
- allocator: *std.mem.Allocator,
- relative_path: string,
- _extension: string,
- comptime client_entry_point_enabled: bool,
- comptime serve_as_package_path: bool,
- ) !ServeResult {
- var extension = _extension;
- var old_log = bundler.log;
- var old_allocator = bundler.allocator;
-
- bundler.setLog(log);
- defer bundler.setLog(old_log);
-
- if (strings.eqlComptime(relative_path, "__runtime.js")) {
- return ServeResult{
- .file = options.OutputFile.initBuf(runtime.Runtime.sourceContent(), "__runtime.js", .js),
- .mime_type = MimeType.javascript,
+ output_file.value = .{ .move = file_op };
+ },
+ .file => {
+ var hashed_name = try bundler.linker.getHashedFilename(file_path, null);
+ var pathname = try bundler.allocator.alloc(u8, hashed_name.len + file_path.name.ext.len);
+ std.mem.copy(u8, pathname, hashed_name);
+ std.mem.copy(u8, pathname[hashed_name.len..], file_path.name.ext);
+ const dir = if (bundler.options.output_dir_handle) |output_handle| output_handle.fd else 0;
+
+ output_file.value = .{
+ .copy = options.OutputFile.FileOperation{
+ .pathname = pathname,
+ .dir = dir,
+ .is_outdir = true,
+ },
};
- }
+ },
- var absolute_path = if (comptime serve_as_package_path)
- relative_path
- else
- resolve_path.joinAbsStringBuf(
- bundler.fs.top_level_dir,
- &tmp_buildfile_buf,
- &([_][]const u8{relative_path}),
- .auto,
- );
+ // // TODO:
+ // else => {},
+ }
- defer {
- js_ast.Expr.Data.Store.reset();
- js_ast.Stmt.Data.Store.reset();
+ return output_file;
+ }
+
+ pub fn print(
+ bundler: *ThisBundler,
+ result: ParseResult,
+ comptime Writer: type,
+ writer: Writer,
+ comptime format: js_printer.Format,
+ ) !usize {
+ const ast = result.ast;
+ var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols});
+
+ return switch (format) {
+ .cjs => try js_printer.printCommonJS(
+ Writer,
+ writer,
+ ast,
+ js_ast.Symbol.Map.initList(symbols),
+ &result.source,
+ false,
+ js_printer.Options{
+ .to_module_ref = Ref.RuntimeRef,
+ .externals = ast.externals,
+ .runtime_imports = ast.runtime_imports,
+ .require_ref = ast.require_ref,
+ .css_import_behavior = bundler.options.cssImportBehavior(),
+ },
+ Linker,
+ &bundler.linker,
+ ),
+ .esm => try js_printer.printAst(
+ Writer,
+ writer,
+ ast,
+ js_ast.Symbol.Map.initList(symbols),
+ &result.source,
+ false,
+ js_printer.Options{
+ .to_module_ref = Ref.RuntimeRef,
+ .externals = ast.externals,
+ .runtime_imports = ast.runtime_imports,
+ .require_ref = ast.require_ref,
+
+ .css_import_behavior = bundler.options.cssImportBehavior(),
+ },
+ Linker,
+ &bundler.linker,
+ ),
+ };
+ }
+
+ pub fn parse(
+ bundler: *ThisBundler,
+ allocator: *std.mem.Allocator,
+ path: Fs.Path,
+ loader: options.Loader,
+ // only used when file_descriptor is null
+ dirname_fd: StoredFileDescriptorType,
+ file_descriptor: ?StoredFileDescriptorType,
+ file_hash: ?u32,
+ client_entry_point_: anytype,
+ ) ?ParseResult {
+ if (FeatureFlags.tracing) {
+ bundler.timer.start();
+ }
+ defer {
+ if (FeatureFlags.tracing) {
+ bundler.timer.stop();
+ bundler.elapsed += bundler.timer.elapsed;
}
+ }
+ var result: ParseResult = undefined;
+ var input_fd: ?StoredFileDescriptorType = null;
- // If the extension is .js, omit it.
- // if (absolute_path.len > ".js".len and strings.eqlComptime(absolute_path[absolute_path.len - ".js".len ..], ".js")) {
- // absolute_path = absolute_path[0 .. absolute_path.len - ".js".len];
- // }
+ const source: logger.Source = brk: {
+ if (client_entry_point_) |client_entry_point| {
+ if (@hasField(std.meta.Child(@TypeOf(client_entry_point)), "source")) {
+ break :brk client_entry_point.source;
+ }
+ }
- const resolved = if (comptime !client_entry_point_enabled) (try bundler.resolver.resolve(bundler.fs.top_level_dir, absolute_path, .stmt)) else brk: {
- const absolute_pathname = Fs.PathName.init(absolute_path);
-
- const loader_for_ext = bundler.options.loader(absolute_pathname.ext);
-
- // The expected pathname looks like:
- // /pages/index.entry.tsx
- // /pages/index.entry.js
- // /pages/index.entry.ts
- // /pages/index.entry.jsx
- if (loader_for_ext.supportsClientEntryPoint()) {
- const absolute_pathname_pathname = Fs.PathName.init(absolute_pathname.base);
-
- if (strings.eqlComptime(absolute_pathname_pathname.ext, ".entry")) {
- const trail_dir = absolute_pathname.dirWithTrailingSlash();
- var len: usize = trail_dir.len;
- std.mem.copy(u8, tmp_buildfile_buf2[0..len], trail_dir);
-
- std.mem.copy(u8, tmp_buildfile_buf2[len..], absolute_pathname_pathname.base);
- len += absolute_pathname_pathname.base.len;
- std.mem.copy(u8, tmp_buildfile_buf2[len..], absolute_pathname.ext);
- len += absolute_pathname.ext.len;
- std.debug.assert(len > 0);
- const decoded_entry_point_path = tmp_buildfile_buf2[0..len];
- break :brk (try bundler.resolver.resolve(bundler.fs.top_level_dir, decoded_entry_point_path, .entry_point));
- }
+ if (strings.eqlComptime(path.namespace, "node")) {
+ if (NodeFallbackModules.contentsFromPath(path.text)) |code| {
+ break :brk logger.Source.initPathString(path.text, code);
}
- break :brk (try bundler.resolver.resolve(bundler.fs.top_level_dir, absolute_path, .stmt));
+ break :brk logger.Source.initPathString(path.text, "");
+ }
+
+ const entry = bundler.resolver.caches.fs.readFile(
+ bundler.fs,
+ path.text,
+ dirname_fd,
+ true,
+ file_descriptor,
+ ) catch |err| {
+ bundler.log.addErrorFmt(null, logger.Loc.Empty, bundler.allocator, "{s} reading \"{s}\"", .{ @errorName(err), path.text }) catch {};
+ return null;
};
+ input_fd = entry.fd;
+ break :brk logger.Source.initRecycledFile(Fs.File{ .path = path, .contents = entry.contents }, bundler.allocator) catch return null;
+ };
- const path = (resolved.pathConst() orelse return error.ModuleNotFound);
+ if (source.contents.len == 0 or (source.contents.len < 33 and std.mem.trim(u8, source.contents, "\n\r ").len == 0)) {
+ return ParseResult{ .source = source, .input_fd = input_fd, .loader = loader, .empty = true, .ast = js_ast.Ast.empty };
+ }
- const loader = bundler.options.loader(path.name.ext);
- const mime_type_ext = bundler.options.out_extensions.get(path.name.ext) orelse path.name.ext;
+ switch (loader) {
+ .js,
+ .jsx,
+ .ts,
+ .tsx,
+ => {
+ var jsx = bundler.options.jsx;
+ jsx.parse = loader.isJSX();
+ var opts = js_parser.Parser.Options.init(jsx, loader);
+ opts.enable_bundling = false;
+ opts.transform_require_to_import = true;
+ opts.can_import_from_bundle = bundler.options.node_modules_bundle != null;
+
+ // HMR is enabled when devserver is running
+ // unless you've explicitly disabled it
+ // or you're running in SSR
+ // or the file is a node_module
+ opts.features.hot_module_reloading = bundler.options.hot_module_reloading and
+ bundler.options.platform != .bun and
+ (!opts.can_import_from_bundle or
+ (opts.can_import_from_bundle and !path.isNodeModule()));
+ opts.features.react_fast_refresh = opts.features.hot_module_reloading and
+ jsx.parse and
+ bundler.options.jsx.supports_fast_refresh;
+ opts.filepath_hash_for_hmr = file_hash orelse 0;
+ opts.warn_about_unbundled_modules = bundler.options.platform != .bun;
+ const value = (bundler.resolver.caches.js.parse(
+ allocator,
+ opts,
+ bundler.options.define,
+ bundler.log,
+ &source,
+ ) catch null) orelse return null;
+ return ParseResult{
+ .ast = value,
+ .source = source,
+ .loader = loader,
+ .input_fd = input_fd,
+ };
+ },
+ .json => {
+ var expr = json_parser.ParseJSON(&source, bundler.log, allocator) catch return null;
+ var stmt = js_ast.Stmt.alloc(allocator, js_ast.S.ExportDefault, js_ast.S.ExportDefault{
+ .value = js_ast.StmtOrExpr{ .expr = expr },
+ .default_name = js_ast.LocRef{ .loc = logger.Loc{}, .ref = Ref{} },
+ }, logger.Loc{ .start = 0 });
+ var stmts = allocator.alloc(js_ast.Stmt, 1) catch unreachable;
+ stmts[0] = stmt;
+ var parts = allocator.alloc(js_ast.Part, 1) catch unreachable;
+ parts[0] = js_ast.Part{ .stmts = stmts };
- switch (loader) {
- .js, .jsx, .ts, .tsx, .css => {
- return ServeResult{
- .file = options.OutputFile.initPending(loader, resolved),
- .mime_type = MimeType.byLoader(
- loader,
- mime_type_ext[1..],
- ),
- };
- },
- .json => {
- return ServeResult{
- .file = options.OutputFile.initPending(loader, resolved),
- .mime_type = MimeType.transpiled_json,
- };
- },
- else => {
- var abs_path = path.text;
- const file = try std.fs.openFileAbsolute(abs_path, .{ .read = true });
- var stat = try file.stat();
- return ServeResult{
- .file = options.OutputFile.initFile(file, abs_path, stat.size),
- .mime_type = MimeType.byLoader(
- loader,
- mime_type_ext[1..],
- ),
- };
- },
- }
+ return ParseResult{
+ .ast = js_ast.Ast.initTest(parts),
+ .source = source,
+ .loader = loader,
+ .input_fd = input_fd,
+ };
+ },
+ .css => {},
+ else => Global.panic("Unsupported loader {s} for path: {s}", .{ loader, source.path.text }),
}
- pub fn normalizeEntryPointPath(bundler: *ThisBundler, _entry: string) string {
- var paths = [_]string{_entry};
- var entry = bundler.fs.abs(&paths);
+ return null;
+ }
+
+ // This is public so it can be used by the HTTP handler when matching against public dir.
+ pub threadlocal var tmp_buildfile_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ threadlocal var tmp_buildfile_buf2: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- std.fs.accessAbsolute(entry, .{}) catch |err| {
- return _entry;
+ // We try to be mostly stateless when serving
+ // This means we need a slightly different resolver setup
+ pub fn buildFile(
+ bundler: *ThisBundler,
+ log: *logger.Log,
+ allocator: *std.mem.Allocator,
+ relative_path: string,
+ _extension: string,
+ comptime client_entry_point_enabled: bool,
+ comptime serve_as_package_path: bool,
+ ) !ServeResult {
+ var extension = _extension;
+ var old_log = bundler.log;
+ var old_allocator = bundler.allocator;
+
+ bundler.setLog(log);
+ defer bundler.setLog(old_log);
+
+ if (strings.eqlComptime(relative_path, "__runtime.js")) {
+ return ServeResult{
+ .file = options.OutputFile.initBuf(runtime.Runtime.sourceContent(), "__runtime.js", .js),
+ .mime_type = MimeType.javascript,
};
+ }
- entry = bundler.fs.relativeTo(entry);
-
- if (!strings.startsWith(entry, "./")) {
- // Entry point paths without a leading "./" are interpreted as package
- // paths. This happens because they go through general path resolution
- // like all other import paths so that plugins can run on them. Requiring
- // a leading "./" for a relative path simplifies writing plugins because
- // entry points aren't a special case.
- //
- // However, requiring a leading "./" also breaks backward compatibility
- // and makes working with the CLI more difficult. So attempt to insert
- // "./" automatically when needed. We don't want to unconditionally insert
- // a leading "./" because the path may not be a file system path. For
- // example, it may be a URL. So only insert a leading "./" when the path
- // is an exact match for an existing file.
- var __entry = bundler.allocator.alloc(u8, "./".len + entry.len) catch unreachable;
- __entry[0] = '.';
- __entry[1] = '/';
- std.mem.copy(u8, __entry[2..__entry.len], entry);
- entry = __entry;
- }
+ var absolute_path = if (comptime serve_as_package_path)
+ relative_path
+ else
+ resolve_path.joinAbsStringBuf(
+ bundler.fs.top_level_dir,
+ &tmp_buildfile_buf,
+ &([_][]const u8{relative_path}),
+ .auto,
+ );
- return entry;
+ defer {
+ js_ast.Expr.Data.Store.reset();
+ js_ast.Stmt.Data.Store.reset();
}
- fn enqueueEntryPoints(bundler: *ThisBundler, entry_points: []_resolver.Result, comptime normalize_entry_point: bool) usize {
- var entry_point_i: usize = 0;
+ // If the extension is .js, omit it.
+ // if (absolute_path.len > ".js".len and strings.eqlComptime(absolute_path[absolute_path.len - ".js".len ..], ".js")) {
+ // absolute_path = absolute_path[0 .. absolute_path.len - ".js".len];
+ // }
+
+ const resolved = if (comptime !client_entry_point_enabled) (try bundler.resolver.resolve(bundler.fs.top_level_dir, absolute_path, .stmt)) else brk: {
+ const absolute_pathname = Fs.PathName.init(absolute_path);
+
+ const loader_for_ext = bundler.options.loader(absolute_pathname.ext);
+
+ // The expected pathname looks like:
+ // /pages/index.entry.tsx
+ // /pages/index.entry.js
+ // /pages/index.entry.ts
+ // /pages/index.entry.jsx
+ if (loader_for_ext.supportsClientEntryPoint()) {
+ const absolute_pathname_pathname = Fs.PathName.init(absolute_pathname.base);
+
+ if (strings.eqlComptime(absolute_pathname_pathname.ext, ".entry")) {
+ const trail_dir = absolute_pathname.dirWithTrailingSlash();
+ var len: usize = trail_dir.len;
+ std.mem.copy(u8, tmp_buildfile_buf2[0..len], trail_dir);
+
+ std.mem.copy(u8, tmp_buildfile_buf2[len..], absolute_pathname_pathname.base);
+ len += absolute_pathname_pathname.base.len;
+ std.mem.copy(u8, tmp_buildfile_buf2[len..], absolute_pathname.ext);
+ len += absolute_pathname.ext.len;
+ std.debug.assert(len > 0);
+ const decoded_entry_point_path = tmp_buildfile_buf2[0..len];
+ break :brk (try bundler.resolver.resolve(bundler.fs.top_level_dir, decoded_entry_point_path, .entry_point));
+ }
+ }
- for (bundler.options.entry_points) |_entry| {
- var entry: string = if (comptime normalize_entry_point) bundler.normalizeEntryPointPath(_entry) else _entry;
+ break :brk (try bundler.resolver.resolve(bundler.fs.top_level_dir, absolute_path, .stmt));
+ };
- defer {
- js_ast.Expr.Data.Store.reset();
- js_ast.Stmt.Data.Store.reset();
- }
+ const path = (resolved.pathConst() orelse return error.ModuleNotFound);
- const result = bundler.resolver.resolve(bundler.fs.top_level_dir, entry, .entry_point) catch |err| {
- Output.prettyError("Error resolving \"{s}\": {s}\n", .{ entry, @errorName(err) });
- continue;
+ const loader = bundler.options.loader(path.name.ext);
+ const mime_type_ext = bundler.options.out_extensions.get(path.name.ext) orelse path.name.ext;
+
+ switch (loader) {
+ .js, .jsx, .ts, .tsx, .css => {
+ return ServeResult{
+ .file = options.OutputFile.initPending(loader, resolved),
+ .mime_type = MimeType.byLoader(
+ loader,
+ mime_type_ext[1..],
+ ),
+ };
+ },
+ .json => {
+ return ServeResult{
+ .file = options.OutputFile.initPending(loader, resolved),
+ .mime_type = MimeType.transpiled_json,
+ };
+ },
+ else => {
+ var abs_path = path.text;
+ const file = try std.fs.openFileAbsolute(abs_path, .{ .read = true });
+ var stat = try file.stat();
+ return ServeResult{
+ .file = options.OutputFile.initFile(file, abs_path, stat.size),
+ .mime_type = MimeType.byLoader(
+ loader,
+ mime_type_ext[1..],
+ ),
};
+ },
+ }
+ }
- if (result.pathConst() == null) {
- Output.prettyError("\"{s}\" is disabled due to \"browser\" field in package.json.\n", .{
- entry,
- });
- continue;
- }
+ pub fn normalizeEntryPointPath(bundler: *ThisBundler, _entry: string) string {
+ var paths = [_]string{_entry};
+ var entry = bundler.fs.abs(&paths);
- if (bundler.linker.enqueueResolveResult(&result) catch unreachable) {
- entry_points[entry_point_i] = result;
- entry_point_i += 1;
- }
- }
+ std.fs.accessAbsolute(entry, .{}) catch |err| {
+ return _entry;
+ };
- return entry_point_i;
+ entry = bundler.fs.relativeTo(entry);
+
+ if (!strings.startsWith(entry, "./")) {
+ // Entry point paths without a leading "./" are interpreted as package
+ // paths. This happens because they go through general path resolution
+ // like all other import paths so that plugins can run on them. Requiring
+ // a leading "./" for a relative path simplifies writing plugins because
+ // entry points aren't a special case.
+ //
+ // However, requiring a leading "./" also breaks backward compatibility
+ // and makes working with the CLI more difficult. So attempt to insert
+ // "./" automatically when needed. We don't want to unconditionally insert
+ // a leading "./" because the path may not be a file system path. For
+ // example, it may be a URL. So only insert a leading "./" when the path
+ // is an exact match for an existing file.
+ var __entry = bundler.allocator.alloc(u8, "./".len + entry.len) catch unreachable;
+ __entry[0] = '.';
+ __entry[1] = '/';
+ std.mem.copy(u8, __entry[2..__entry.len], entry);
+ entry = __entry;
}
- pub fn bundle(
- allocator: *std.mem.Allocator,
- log: *logger.Log,
- opts: Api.TransformOptions,
- ) !options.TransformResult {
- var bundler = try ThisBundler.init(allocator, log, opts, null, null);
- bundler.configureLinker();
- try bundler.configureRouter(false);
- try bundler.configureDefines();
+ return entry;
+ }
- var skip_normalize = false;
- var load_from_routes = false;
- if (bundler.options.routes.routes_enabled and bundler.options.entry_points.len == 0) {
- if (bundler.router) |router| {
- bundler.options.entry_points = try router.getEntryPoints(allocator);
- skip_normalize = true;
- load_from_routes = true;
- }
+ fn enqueueEntryPoints(bundler: *ThisBundler, entry_points: []_resolver.Result, comptime normalize_entry_point: bool) usize {
+ var entry_point_i: usize = 0;
+
+ for (bundler.options.entry_points) |_entry| {
+ var entry: string = if (comptime normalize_entry_point) bundler.normalizeEntryPointPath(_entry) else _entry;
+
+ defer {
+ js_ast.Expr.Data.Store.reset();
+ js_ast.Stmt.Data.Store.reset();
}
- if (bundler.options.write and bundler.options.output_dir.len > 0) {}
+ const result = bundler.resolver.resolve(bundler.fs.top_level_dir, entry, .entry_point) catch |err| {
+ Output.prettyError("Error resolving \"{s}\": {s}\n", .{ entry, @errorName(err) });
+ continue;
+ };
- // 100.00 µs std.fifo.LinearFifo(resolver.Result,std.fifo.LinearFifoBufferType { .Dynamic = {}}).writeItemAssumeCapacity
- if (bundler.options.resolve_mode != .lazy) {
- try bundler.resolve_queue.ensureUnusedCapacity(3);
+ if (result.pathConst() == null) {
+ Output.prettyError("\"{s}\" is disabled due to \"browser\" field in package.json.\n", .{
+ entry,
+ });
+ continue;
}
- var entry_points = try allocator.alloc(_resolver.Result, bundler.options.entry_points.len);
- if (skip_normalize) {
- entry_points = entry_points[0..bundler.enqueueEntryPoints(entry_points, false)];
- } else {
- entry_points = entry_points[0..bundler.enqueueEntryPoints(entry_points, true)];
+ if (bundler.linker.enqueueResolveResult(&result) catch unreachable) {
+ entry_points[entry_point_i] = result;
+ entry_point_i += 1;
}
+ }
- if (log.level == .verbose) {
- bundler.resolver.debug_logs = try DebugLogs.init(allocator);
+ return entry_point_i;
+ }
+
+ pub fn bundle(
+ allocator: *std.mem.Allocator,
+ log: *logger.Log,
+ opts: Api.TransformOptions,
+ ) !options.TransformResult {
+ var bundler = try ThisBundler.init(allocator, log, opts, null, null);
+ bundler.configureLinker();
+ try bundler.configureRouter(false);
+ try bundler.configureDefines();
+
+ var skip_normalize = false;
+ var load_from_routes = false;
+ if (bundler.options.routes.routes_enabled and bundler.options.entry_points.len == 0) {
+ if (bundler.router) |router| {
+ bundler.options.entry_points = try router.getEntryPoints(allocator);
+ skip_normalize = true;
+ load_from_routes = true;
}
+ }
- var did_start = false;
+ if (bundler.options.write and bundler.options.output_dir.len > 0) {}
- if (bundler.options.output_dir_handle == null) {
- const outstream = std.io.getStdOut();
+ // 100.00 µs std.fifo.LinearFifo(resolver.Result,std.fifo.LinearFifoBufferType { .Dynamic = {}}).writeItemAssumeCapacity
+ if (bundler.options.resolve_mode != .lazy) {
+ try bundler.resolve_queue.ensureUnusedCapacity(3);
+ }
- if (load_from_routes) {
- if (bundler.options.framework) |*framework| {
- if (framework.client.isEnabled()) {
- did_start = true;
- try switch (bundler.options.import_path_format) {
- .relative => bundler.processResolveQueue(.relative, true, @TypeOf(outstream), outstream),
- .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, true, @TypeOf(outstream), outstream),
- .absolute_url => bundler.processResolveQueue(.absolute_url, true, @TypeOf(outstream), outstream),
- .absolute_path => bundler.processResolveQueue(.absolute_path, true, @TypeOf(outstream), outstream),
- .package_path => bundler.processResolveQueue(.package_path, true, @TypeOf(outstream), outstream),
- };
- }
+ var entry_points = try allocator.alloc(_resolver.Result, bundler.options.entry_points.len);
+ if (skip_normalize) {
+ entry_points = entry_points[0..bundler.enqueueEntryPoints(entry_points, false)];
+ } else {
+ entry_points = entry_points[0..bundler.enqueueEntryPoints(entry_points, true)];
+ }
+
+ if (log.level == .verbose) {
+ bundler.resolver.debug_logs = try DebugLogs.init(allocator);
+ }
+
+ var did_start = false;
+
+ if (bundler.options.output_dir_handle == null) {
+ const outstream = std.io.getStdOut();
+
+ if (load_from_routes) {
+ if (bundler.options.framework) |*framework| {
+ if (framework.client.isEnabled()) {
+ did_start = true;
+ try switch (bundler.options.import_path_format) {
+ .relative => bundler.processResolveQueue(.relative, true, @TypeOf(outstream), outstream),
+ .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, true, @TypeOf(outstream), outstream),
+ .absolute_url => bundler.processResolveQueue(.absolute_url, true, @TypeOf(outstream), outstream),
+ .absolute_path => bundler.processResolveQueue(.absolute_path, true, @TypeOf(outstream), outstream),
+ .package_path => bundler.processResolveQueue(.package_path, true, @TypeOf(outstream), outstream),
+ };
}
}
+ }
- if (!did_start) {
- try switch (bundler.options.import_path_format) {
- .relative => bundler.processResolveQueue(.relative, false, @TypeOf(outstream), outstream),
- .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, false, @TypeOf(outstream), outstream),
- .absolute_url => bundler.processResolveQueue(.absolute_url, false, @TypeOf(outstream), outstream),
- .absolute_path => bundler.processResolveQueue(.absolute_path, false, @TypeOf(outstream), outstream),
- .package_path => bundler.processResolveQueue(.package_path, false, @TypeOf(outstream), outstream),
- };
- }
- } else {
- const output_dir = bundler.options.output_dir_handle orelse {
- Output.printError("Invalid or missing output directory.", .{});
- Output.flush();
- Global.crash();
+ if (!did_start) {
+ try switch (bundler.options.import_path_format) {
+ .relative => bundler.processResolveQueue(.relative, false, @TypeOf(outstream), outstream),
+ .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, false, @TypeOf(outstream), outstream),
+ .absolute_url => bundler.processResolveQueue(.absolute_url, false, @TypeOf(outstream), outstream),
+ .absolute_path => bundler.processResolveQueue(.absolute_path, false, @TypeOf(outstream), outstream),
+ .package_path => bundler.processResolveQueue(.package_path, false, @TypeOf(outstream), outstream),
};
+ }
+ } else {
+ const output_dir = bundler.options.output_dir_handle orelse {
+ Output.printError("Invalid or missing output directory.", .{});
+ Output.flush();
+ Global.crash();
+ };
- if (load_from_routes) {
- if (bundler.options.framework) |*framework| {
- if (framework.client.isEnabled()) {
- did_start = true;
- try switch (bundler.options.import_path_format) {
- .relative => bundler.processResolveQueue(.relative, true, std.fs.Dir, output_dir),
- .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, true, std.fs.Dir, output_dir),
- .absolute_url => bundler.processResolveQueue(.absolute_url, true, std.fs.Dir, output_dir),
- .absolute_path => bundler.processResolveQueue(.absolute_path, true, std.fs.Dir, output_dir),
- .package_path => bundler.processResolveQueue(.package_path, true, std.fs.Dir, output_dir),
- };
- }
+ if (load_from_routes) {
+ if (bundler.options.framework) |*framework| {
+ if (framework.client.isEnabled()) {
+ did_start = true;
+ try switch (bundler.options.import_path_format) {
+ .relative => bundler.processResolveQueue(.relative, true, std.fs.Dir, output_dir),
+ .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, true, std.fs.Dir, output_dir),
+ .absolute_url => bundler.processResolveQueue(.absolute_url, true, std.fs.Dir, output_dir),
+ .absolute_path => bundler.processResolveQueue(.absolute_path, true, std.fs.Dir, output_dir),
+ .package_path => bundler.processResolveQueue(.package_path, true, std.fs.Dir, output_dir),
+ };
}
}
-
- if (!did_start) {
- try switch (bundler.options.import_path_format) {
- .relative => bundler.processResolveQueue(.relative, false, std.fs.Dir, output_dir),
- .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, false, std.fs.Dir, output_dir),
- .absolute_url => bundler.processResolveQueue(.absolute_url, false, std.fs.Dir, output_dir),
- .absolute_path => bundler.processResolveQueue(.absolute_path, false, std.fs.Dir, output_dir),
- .package_path => bundler.processResolveQueue(.package_path, false, std.fs.Dir, output_dir),
- };
- }
}
- // if (log.level == .verbose) {
- // for (log.msgs.items) |msg| {
- // try msg.writeFormat(std.io.getStdOut().writer());
- // }
- // }
-
- if (bundler.linker.any_needs_runtime) {
- try bundler.output_files.append(
- options.OutputFile.initBuf(runtime.Runtime.sourceContent(), bundler.linker.runtime_source_path, .js),
- );
+ if (!did_start) {
+ try switch (bundler.options.import_path_format) {
+ .relative => bundler.processResolveQueue(.relative, false, std.fs.Dir, output_dir),
+ .relative_nodejs => bundler.processResolveQueue(.relative_nodejs, false, std.fs.Dir, output_dir),
+ .absolute_url => bundler.processResolveQueue(.absolute_url, false, std.fs.Dir, output_dir),
+ .absolute_path => bundler.processResolveQueue(.absolute_path, false, std.fs.Dir, output_dir),
+ .package_path => bundler.processResolveQueue(.package_path, false, std.fs.Dir, output_dir),
+ };
}
+ }
- if (FeatureFlags.tracing) {
- Output.prettyErrorln(
- "<r><d>\n---Tracing---\nResolve time: {d}\nParsing time: {d}\n---Tracing--\n\n<r>",
- .{
- bundler.resolver.elapsed,
- bundler.elapsed,
- },
- );
- }
+ // if (log.level == .verbose) {
+ // for (log.msgs.items) |msg| {
+ // try msg.writeFormat(std.io.getStdOut().writer());
+ // }
+ // }
- var final_result = try options.TransformResult.init(try allocator.dupe(u8, bundler.result.outbase), bundler.output_files.toOwnedSlice(), log, allocator);
- final_result.root_dir = bundler.options.output_dir_handle;
- return final_result;
+ if (bundler.linker.any_needs_runtime) {
+ try bundler.output_files.append(
+ options.OutputFile.initBuf(runtime.Runtime.sourceContent(), bundler.linker.runtime_source_path, .js),
+ );
}
- // pub fn processResolveQueueWithThreadPool(bundler)
+ if (FeatureFlags.tracing) {
+ Output.prettyErrorln(
+ "<r><d>\n---Tracing---\nResolve time: {d}\nParsing time: {d}\n---Tracing--\n\n<r>",
+ .{
+ bundler.resolver.elapsed,
+ bundler.elapsed,
+ },
+ );
+ }
- pub fn processResolveQueue(
- bundler: *ThisBundler,
- comptime import_path_format: options.BundleOptions.ImportPathFormat,
- comptime wrap_entry_point: bool,
- comptime Outstream: type,
- outstream: Outstream,
- ) !void {
- // var count: u8 = 0;
- while (bundler.resolve_queue.readItem()) |item| {
- js_ast.Expr.Data.Store.reset();
- js_ast.Stmt.Data.Store.reset();
+ var final_result = try options.TransformResult.init(try allocator.dupe(u8, bundler.result.outbase), bundler.output_files.toOwnedSlice(), log, allocator);
+ final_result.root_dir = bundler.options.output_dir_handle;
+ return final_result;
+ }
- // defer count += 1;
-
- if (comptime wrap_entry_point) {
- var path = item.pathConst() orelse unreachable;
- const loader = bundler.options.loader(path.name.ext);
-
- if (item.import_kind == .entry_point and loader.supportsClientEntryPoint()) {
- var client_entry_point = try bundler.allocator.create(ClientEntryPoint);
- client_entry_point.* = ClientEntryPoint{};
- try client_entry_point.generate(ThisBundler, bundler, path.name, bundler.options.framework.?.client.path);
- try bundler.virtual_modules.append(client_entry_point);
-
- const entry_point_output_file = bundler.buildWithResolveResultEager(
- item,
- import_path_format,
- Outstream,
- outstream,
- client_entry_point,
- ) catch continue orelse continue;
- bundler.output_files.append(entry_point_output_file) catch unreachable;
-
- js_ast.Expr.Data.Store.reset();
- js_ast.Stmt.Data.Store.reset();
-
- // At this point, the entry point will be de-duped.
- // So we just immediately build it.
- var item_not_entrypointed = item;
- item_not_entrypointed.import_kind = .stmt;
- const original_output_file = bundler.buildWithResolveResultEager(
- item_not_entrypointed,
- import_path_format,
- Outstream,
- outstream,
- null,
- ) catch continue orelse continue;
- bundler.output_files.append(original_output_file) catch unreachable;
-
- continue;
- }
- }
+ // pub fn processResolveQueueWithThreadPool(bundler)
- const output_file = bundler.buildWithResolveResultEager(
- item,
- import_path_format,
- Outstream,
- outstream,
- null,
- ) catch continue orelse continue;
- bundler.output_files.append(output_file) catch unreachable;
+ pub fn processResolveQueue(
+ bundler: *ThisBundler,
+ comptime import_path_format: options.BundleOptions.ImportPathFormat,
+ comptime wrap_entry_point: bool,
+ comptime Outstream: type,
+ outstream: Outstream,
+ ) !void {
+ // var count: u8 = 0;
+ while (bundler.resolve_queue.readItem()) |item| {
+ js_ast.Expr.Data.Store.reset();
+ js_ast.Stmt.Data.Store.reset();
+
+ // defer count += 1;
+
+ if (comptime wrap_entry_point) {
+ var path = item.pathConst() orelse unreachable;
+ const loader = bundler.options.loader(path.name.ext);
+
+ if (item.import_kind == .entry_point and loader.supportsClientEntryPoint()) {
+ var client_entry_point = try bundler.allocator.create(ClientEntryPoint);
+ client_entry_point.* = ClientEntryPoint{};
+ try client_entry_point.generate(ThisBundler, bundler, path.name, bundler.options.framework.?.client.path);
+ try bundler.virtual_modules.append(client_entry_point);
+
+ const entry_point_output_file = bundler.buildWithResolveResultEager(
+ item,
+ import_path_format,
+ Outstream,
+ outstream,
+ client_entry_point,
+ ) catch continue orelse continue;
+ bundler.output_files.append(entry_point_output_file) catch unreachable;
- // if (count >= 3) return try bundler.processResolveQueueWithThreadPool(import_path_format, wrap_entry_point, Outstream, outstream);
+ js_ast.Expr.Data.Store.reset();
+ js_ast.Stmt.Data.Store.reset();
+
+ // At this point, the entry point will be de-duped.
+ // So we just immediately build it.
+ var item_not_entrypointed = item;
+ item_not_entrypointed.import_kind = .stmt;
+ const original_output_file = bundler.buildWithResolveResultEager(
+ item_not_entrypointed,
+ import_path_format,
+ Outstream,
+ outstream,
+ null,
+ ) catch continue orelse continue;
+ bundler.output_files.append(original_output_file) catch unreachable;
+
+ continue;
+ }
}
- }
- };
-}
-pub const Bundler = NewBundler(true);
-pub const ServeBundler = NewBundler(false);
+ const output_file = bundler.buildWithResolveResultEager(
+ item,
+ import_path_format,
+ Outstream,
+ outstream,
+ null,
+ ) catch continue orelse continue;
+ bundler.output_files.append(output_file) catch unreachable;
+
+ // if (count >= 3) return try bundler.processResolveQueueWithThreadPool(import_path_format, wrap_entry_point, Outstream, outstream);
+ }
+ }
+};
pub const Transformer = struct {
opts: Api.TransformOptions,