aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/api/JSBundler.zig31
-rw-r--r--src/bundler/bundle_v2.zig82
-rw-r--r--src/cli.zig10
-rw-r--r--src/cli/build_command.zig31
-rw-r--r--src/options.zig3
-rw-r--r--src/resolver/resolve_path.zig143
6 files changed, 243 insertions, 57 deletions
diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig
index f17fe99d1..c95c359f1 100644
--- a/src/bun.js/api/JSBundler.zig
+++ b/src/bun.js/api/JSBundler.zig
@@ -11,6 +11,7 @@ const js = JSC.C;
const WebCore = @import("../webcore/response.zig");
const Bundler = bun.bundler;
const options = @import("../../options.zig");
+const resolve_path = @import("../../resolver/resolve_path.zig");
const VirtualMachine = JavaScript.VirtualMachine;
const ScriptSrcStream = std.io.FixedBufferStream([]u8);
const ZigString = JSC.ZigString;
@@ -54,6 +55,7 @@ pub const JSBundler = struct {
loaders: ?Api.LoaderMap = null,
dir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
outdir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
+ rootdir: OwnedString = OwnedString.initEmpty(bun.default_allocator),
serve: Serve = .{},
jsx: options.JSX.Pragma = .{},
code_splitting: bool = false,
@@ -74,6 +76,7 @@ pub const JSBundler = struct {
.define = bun.StringMap.init(allocator, true),
.dir = OwnedString.initEmpty(allocator),
.outdir = OwnedString.initEmpty(allocator),
+ .rootdir = OwnedString.initEmpty(allocator),
.names = .{
.owned_entry_point = OwnedString.initEmpty(allocator),
.owned_chunk = OwnedString.initEmpty(allocator),
@@ -253,6 +256,33 @@ pub const JSBundler = struct {
return error.JSException;
}
+ {
+ const path: ZigString.Slice = brk: {
+ if (try config.getOptional(globalThis, "root", ZigString.Slice)) |slice| {
+ break :brk slice;
+ }
+
+ const entry_points = this.entry_points.keys();
+
+ if (entry_points.len == 1) {
+ break :brk ZigString.Slice.fromUTF8NeverFree(std.fs.path.dirname(entry_points[0]) orelse ".");
+ }
+
+ break :brk ZigString.Slice.fromUTF8NeverFree(resolve_path.getIfExistsLongestCommonPath(entry_points) orelse ".");
+ };
+
+ defer path.deinit();
+
+ var dir = std.fs.cwd().openDir(path.slice(), .{}) catch |err| {
+ globalThis.throwPretty("{s}: failed to open root directory: {s}", .{ @errorName(err), path.slice() });
+ return error.JSException;
+ };
+ defer dir.close();
+
+ var rootdir_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ this.rootdir.appendSliceExact(try bun.getFdPath(dir.fd, &rootdir_buf)) catch unreachable;
+ }
+
if (try config.getArray(globalThis, "external")) |externals| {
var iter = externals.arrayIterator(globalThis);
while (iter.next()) |entry_point| {
@@ -455,6 +485,7 @@ pub const JSBundler = struct {
}
self.names.deinit();
self.outdir.deinit();
+ self.rootdir.deinit();
self.public_path.deinit();
}
};
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index 0ddb4476e..57adc904a 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -1599,6 +1599,7 @@ pub const BundleV2 = struct {
bundler.options.public_path = config.public_path.list.items;
bundler.options.output_dir = config.outdir.toOwnedSliceLeaky();
+ bundler.options.root_dir = config.rootdir.toOwnedSliceLeaky();
bundler.options.minify_syntax = config.minify.syntax;
bundler.options.minify_whitespace = config.minify.whitespace;
bundler.options.minify_identifiers = config.minify.identifiers;
@@ -3865,7 +3866,15 @@ const LinkerContext = struct {
const pathname = Fs.PathName.init(this.graph.entry_points.items(.output_path)[chunk.entry_point.entry_point_id].slice());
chunk.template.placeholder.name = pathname.base;
chunk.template.placeholder.ext = "js";
- chunk.template.placeholder.dir = pathname.dir;
+
+ var dir = std.fs.cwd().openDir(pathname.dir, .{}) catch |err| {
+ try this.log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "{s}: failed to open entry point directory: {s}", .{ @errorName(err), pathname.dir });
+ return error.FailedToOpenEntryPointDirectory;
+ };
+ defer dir.close();
+
+ var real_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ chunk.template.placeholder.dir = try resolve_path.relativeAlloc(this.allocator, this.resolver.opts.root_dir, try bun.getFdPath(dir.fd, &real_path_buf));
} else {
chunk.template = PathTemplate.chunk;
if (this.resolver.opts.chunk_naming.len > 0)
@@ -8653,7 +8662,8 @@ const LinkerContext = struct {
// TODO: enforceNoCyclicChunkImports()
{
-
+ var path_names_map = bun.StringHashMap(void).init(c.allocator);
+ defer path_names_map.deinit();
// Compute the final hashes of each chunk. This can technically be done in
// parallel but it probably doesn't matter so much because we're not hashing
// that much data.
@@ -8661,7 +8671,13 @@ const LinkerContext = struct {
// TODO: non-isolated-hash
chunk.template.placeholder.hash = chunk.isolated_hash;
- chunk.final_rel_path = std.fmt.allocPrint(c.allocator, "{any}", .{chunk.template}) catch unreachable;
+ const rel_path = std.fmt.allocPrint(c.allocator, "{any}", .{chunk.template}) catch unreachable;
+ if ((try path_names_map.getOrPut(rel_path)).found_existing) {
+ try c.log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "Multiple files share the same output path: {s}", .{rel_path});
+ return error.DuplicateOutputPath;
+ }
+
+ chunk.final_rel_path = rel_path;
}
}
@@ -8962,43 +8978,6 @@ const LinkerContext = struct {
return err;
};
defer root_dir.close();
- const from_path: []const u8 = brk: {
- var all_paths = c.allocator.alloc(
- []const u8,
- chunks.len +
- @as(
- usize,
- @boolToInt(
- react_client_components_manifest.len > 0,
- ),
- ) +
- c.parse_graph.additional_output_files.items.len,
- ) catch unreachable;
- defer c.allocator.free(all_paths);
-
- var remaining_paths = all_paths;
-
- for (all_paths[0..chunks.len], chunks) |*dest, src| {
- dest.* = src.final_rel_path;
- }
- remaining_paths = remaining_paths[chunks.len..];
-
- if (react_client_components_manifest.len > 0) {
- remaining_paths[0] = components_manifest_path;
- remaining_paths = remaining_paths[1..];
- }
-
- for (remaining_paths, c.parse_graph.additional_output_files.items) |*dest, output_file| {
- dest.* = output_file.input.text;
- }
-
- remaining_paths = remaining_paths[c.parse_graph.additional_output_files.items.len..];
-
- std.debug.assert(remaining_paths.len == 0);
-
- break :brk resolve_path.longestCommonPath(all_paths);
- };
-
// Optimization: when writing to disk, we can re-use the memory
var max_heap_allocator: bun.MaxHeapAllocator = undefined;
defer max_heap_allocator.deinit();
@@ -9023,19 +9002,16 @@ const LinkerContext = struct {
defer max_heap_allocator.reset();
var rel_path = chunk.final_rel_path;
- if (rel_path.len > from_path.len) {
- rel_path = resolve_path.relative(from_path, rel_path);
- if (std.fs.path.dirname(rel_path)) |parent| {
- if (parent.len > root_path.len) {
- root_dir.dir.makePath(parent) catch |err| {
- c.log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "{s} creating outdir {} while saving chunk {}", .{
- @errorName(err),
- bun.fmt.quote(parent),
- bun.fmt.quote(chunk.final_rel_path),
- }) catch unreachable;
- return err;
- };
- }
+ if (std.fs.path.dirname(rel_path)) |rel_parent| {
+ if (rel_parent.len > 0) {
+ root_dir.dir.makePath(rel_parent) catch |err| {
+ c.log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "{s} creating outdir {} while saving chunk {}", .{
+ @errorName(err),
+ bun.fmt.quote(rel_parent),
+ bun.fmt.quote(chunk.final_rel_path),
+ }) catch unreachable;
+ return err;
+ };
}
}
diff --git a/src/cli.zig b/src/cli.zig
index ff4c847c6..ca8208aa2 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -196,6 +196,7 @@ pub const Arguments = struct {
clap.parseParam("--format <STR> Specifies the module format to build to. Only esm is supported.") catch unreachable,
clap.parseParam("--outdir <STR> Default to \"dist\" if multiple files") catch unreachable,
clap.parseParam("--outfile <STR> Write to a file") catch unreachable,
+ clap.parseParam("--root <STR> Root directory used for multiple entry points") catch unreachable,
clap.parseParam("--splitting Enable code splitting") catch unreachable,
// clap.parseParam("--manifest <STR> Write JSON manifest") catch unreachable,
// clap.parseParam("--public-path <STR> A prefix to be appended to any import paths in bundled code") catch unreachable,
@@ -489,6 +490,12 @@ pub const Arguments = struct {
}
}
+ if (args.option("--root")) |root_dir| {
+ if (root_dir.len > 0) {
+ ctx.bundler_options.root_dir = root_dir;
+ }
+ }
+
if (args.option("--format")) |format_str| {
const format = options.Format.fromString(format_str) orelse {
Output.prettyErrorln("<r><red>error<r>: Invalid format - must be esm, cjs, or iife", .{});
@@ -936,7 +943,8 @@ pub const Command = struct {
pub const BundlerOptions = struct {
outdir: []const u8 = "",
outfile: []const u8 = "",
- entry_naming: []const u8 = "./[name].[ext]",
+ root_dir: []const u8 = "",
+ entry_naming: []const u8 = "[dir]/[name].[ext]",
chunk_naming: []const u8 = "./[name]-[hash].[ext]",
asset_naming: []const u8 = "./[name]-[hash].[ext]",
react_server_components: bool = false,
diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig
index 5c9507a00..354c481cc 100644
--- a/src/cli/build_command.zig
+++ b/src/cli/build_command.zig
@@ -59,10 +59,10 @@ pub const BuildCommand = struct {
this_bundler.resolver.opts.entry_naming = ctx.bundler_options.entry_naming;
this_bundler.resolver.opts.chunk_naming = ctx.bundler_options.chunk_naming;
this_bundler.resolver.opts.asset_naming = ctx.bundler_options.asset_naming;
- this_bundler.options.output_dir = ctx.bundler_options.outdir;
- this_bundler.resolver.opts.output_dir = ctx.bundler_options.outdir;
+
this_bundler.options.react_server_components = ctx.bundler_options.react_server_components;
this_bundler.resolver.opts.react_server_components = ctx.bundler_options.react_server_components;
+
this_bundler.options.code_splitting = ctx.bundler_options.code_splitting;
this_bundler.resolver.opts.code_splitting = ctx.bundler_options.code_splitting;
@@ -84,6 +84,33 @@ pub const BuildCommand = struct {
this_bundler.options.output_dir = ctx.bundler_options.outdir;
this_bundler.resolver.opts.output_dir = ctx.bundler_options.outdir;
+ var src_root_dir_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const src_root_dir: string = brk1: {
+ const path = brk2: {
+ if (ctx.bundler_options.root_dir.len > 0) {
+ break :brk2 ctx.bundler_options.root_dir;
+ }
+
+ if (this_bundler.options.entry_points.len == 1) {
+ break :brk2 std.fs.path.dirname(this_bundler.options.entry_points[0]) orelse ".";
+ }
+
+ break :brk2 resolve_path.getIfExistsLongestCommonPath(this_bundler.options.entry_points) orelse ".";
+ };
+
+ var dir = std.fs.cwd().openDir(path, .{}) catch |err| {
+ Output.prettyErrorln("<r>error<r>: {s}: failed to open root directory: {s}", .{ @errorName(err), path });
+ Global.exit(1);
+ return;
+ };
+ defer dir.close();
+
+ break :brk1 try bun.getFdPath(dir.fd, &src_root_dir_buf);
+ };
+
+ this_bundler.options.root_dir = src_root_dir;
+ this_bundler.resolver.opts.root_dir = src_root_dir;
+
this_bundler.options.react_server_components = ctx.bundler_options.react_server_components;
this_bundler.resolver.opts.react_server_components = ctx.bundler_options.react_server_components;
this_bundler.options.code_splitting = ctx.bundler_options.code_splitting;
diff --git a/src/options.zig b/src/options.zig
index 0c0a7372a..f30594516 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -1390,6 +1390,7 @@ pub const BundleOptions = struct {
output_dir_handle: ?Dir = null,
output_dir: string = "out",
+ root_dir: string = "",
node_modules_bundle_url: string = "",
node_modules_bundle_pretty_path: string = "",
@@ -2776,7 +2777,7 @@ pub const PathTemplate = struct {
};
switch (field) {
- .dir => try writer.writeAll(self.placeholder.dir),
+ .dir => try writer.writeAll(if (self.placeholder.dir.len > 0) self.placeholder.dir else "."),
.name => try writer.writeAll(self.placeholder.name),
.ext => try writer.writeAll(self.placeholder.ext),
.hash => {
diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig
index da21f3ec1..c5ffdc626 100644
--- a/src/resolver/resolve_path.zig
+++ b/src/resolver/resolve_path.zig
@@ -36,6 +36,145 @@ inline fn @"is ../"(slice: []const u8) bool {
return strings.hasPrefixComptime(slice, "../");
}
+pub fn getIfExistsLongestCommonPathGeneric(input: []const []const u8, comptime separator: u8, comptime isPathSeparator: IsSeparatorFunc) ?[]const u8 {
+ var min_length: usize = std.math.maxInt(usize);
+ for (input) |str| {
+ min_length = @min(str.len, min_length);
+ }
+
+ var index: usize = 0;
+ var last_common_separator: ?usize = null;
+
+ // try to use an unrolled version of this loop
+ switch (input.len) {
+ 0 => {
+ return "";
+ },
+ 1 => {
+ return input[0];
+ },
+ 2 => {
+ while (index < min_length) : (index += 1) {
+ if (input[0][index] != input[1][index]) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 3 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(3, index, input)) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 4 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(4, index, input)) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 5 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(5, index, input)) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 6 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(6, index, input)) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 7 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(7, index, input)) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 8 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(8, index, input)) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ else => {
+ var string_index: usize = 1;
+ while (string_index < input.len) : (string_index += 1) {
+ while (index < min_length) : (index += 1) {
+ if (input[0][index] != input[string_index][index]) {
+ if (last_common_separator == null) return null;
+ break;
+ }
+ }
+ if (index == min_length) index -= 1;
+ if (@call(.always_inline, isPathSeparator, .{input[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ }
+
+ if (index == 0) {
+ return &([_]u8{separator});
+ }
+
+ if (last_common_separator == null) {
+ return &([_]u8{'.'});
+ }
+
+ // The above won't work for a case like this:
+ // /app/public/index.js
+ // /app/public
+ // It will return:
+ // /app/
+ // It should return:
+ // /app/public/
+ // To detect /app/public is actually a folder, we do one more loop through the strings
+ // and say, "do one of you have a path separator after what we thought was the end?"
+ for (input) |str| {
+ if (str.len > index) {
+ if (@call(.always_inline, isPathSeparator, .{str[index]})) {
+ return str[0 .. index + 1];
+ }
+ }
+ }
+
+ return input[0][0 .. last_common_separator.? + 1];
+}
+
// TODO: is it faster to determine longest_common_separator in the while loop
// or as an extra step at the end?
// only boether to check if this function appears in benchmarking
@@ -170,6 +309,10 @@ pub fn longestCommonPath(input: []const []const u8) []const u8 {
return longestCommonPathGeneric(input, '/', isSepAny);
}
+pub fn getIfExistsLongestCommonPath(input: []const []const u8) ?[]const u8 {
+ return getIfExistsLongestCommonPathGeneric(input, '/', isSepAny);
+}
+
pub fn longestCommonPathWindows(input: []const []const u8) []const u8 {
return longestCommonPathGeneric(input, std.fs.path.sep_windows, isSepWin32);
}