aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-25 01:34:44 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-25 01:34:44 -0700
commitea10dacc92ed40fd7729d06a961117d25c78b9a8 (patch)
treedb90d29f04a6a5ea2e13d751d7e70e206426e0e3
parentf234456bd0ba80696881eb99fd3c64370cc973c5 (diff)
downloadbun-ea10dacc92ed40fd7729d06a961117d25c78b9a8.tar.gz
bun-ea10dacc92ed40fd7729d06a961117d25c78b9a8.tar.zst
bun-ea10dacc92ed40fd7729d06a961117d25c78b9a8.zip
relative path
Former-commit-id: 06fbc24b11f12d6635496a6d251324c021f7af68
-rw-r--r--src/bundler.zig84
-rw-r--r--src/fs.zig63
-rw-r--r--src/global.zig6
-rw-r--r--src/http.zig140
-rw-r--r--src/http/mime_type.zig24
-rw-r--r--src/resolver/package_json.zig3
-rw-r--r--src/resolver/resolve_path.zig480
-rw-r--r--src/resolver/resolver.zig20
-rw-r--r--src/test/tester.zig8
9 files changed, 719 insertions, 109 deletions
diff --git a/src/bundler.zig b/src/bundler.zig
index 57909a49e..b089c0f09 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -116,8 +116,7 @@ pub const Bundler = struct {
switch (bundler.options.import_path_format) {
.relative => {
- const relative_path_temp = try std.fs.path.relative(&relative_path_allocator.allocator, source_dir, source_path);
- const relative_path = try relative_paths_list.append(relative_path_temp);
+ const relative_path = try relative_paths_list.append(bundler.fs.relativeTo(source_path));
return Fs.Path.init(relative_path);
},
@@ -126,16 +125,26 @@ pub const Bundler = struct {
relative_path_allocator_buf_loaded = true;
relative_path_allocator = std.heap.FixedBufferAllocator.init(&relative_path_allocator_buf);
}
- const relative_path_temp = try std.fs.path.relative(&relative_path_allocator.allocator, bundler.fs.top_level_dir, source_path);
- const relative_path = try relative_paths_list.append(try std.fmt.allocPrint(&relative_path_allocator.allocator, "{s}{s}", .{ bundler.options.public_url, relative_path_temp }));
- return Fs.Path.init(relative_path);
+ const relative_path = try relative_paths_list.append(bundler.fs.relativeTo(source_path));
+ const absolute_url = try relative_paths_list.append(
+ try std.fmt.allocPrint(
+ &relative_path_allocator.allocator,
+ "{s}{s}",
+ .{
+ bundler.options.public_url,
+ relative_path,
+ },
+ ),
+ );
+
+ return Fs.Path.initWithPretty(absolute_url, relative_path);
},
}
}
pub fn processImportRecord(bundler: *Bundler, source_dir: string, import_record: *ImportRecord) !void {
- var resolve_result = (bundler.resolver.resolve(source_dir, import_record.path.text, import_record.kind) catch null) orelse return;
+ var resolve_result = try bundler.resolver.resolve(source_dir, import_record.path.text, import_record.kind);
// extremely naive.
resolve_result.is_from_node_modules = strings.contains(resolve_result.path_pair.primary.text, "/node_modules");
@@ -169,7 +178,11 @@ pub const Bundler = struct {
}
// Step 1. Parse & scan
- const result = bundler.parse(resolve_result.path_pair.primary, bundler.options.loaders.get(resolve_result.path_pair.primary.text) orelse .file) orelse return null;
+ const loader = bundler.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
+ var file_path = resolve_result.path_pair.primary;
+ file_path.pretty = relative_paths_list.append(bundler.fs.relativeTo(file_path.text)) catch unreachable;
+
+ var result = bundler.parse(file_path, loader) orelse return null;
switch (result.loader) {
.jsx, .js, .ts, .tsx => {
@@ -177,9 +190,34 @@ pub const Bundler = struct {
for (ast.import_records) |*import_record| {
bundler.processImportRecord(
- std.fs.path.dirname(resolve_result.path_pair.primary.text) orelse resolve_result.path_pair.primary.text,
+ std.fs.path.dirname(file_path.text) orelse file_path.text,
import_record,
- ) catch continue;
+ ) catch |err| {
+ switch (err) {
+ error.ModuleNotFound => {
+ if (Resolver.Resolver.isPackagePath(import_record.path.text)) {
+ try bundler.log.addRangeErrorFmt(
+ &result.source,
+ import_record.range,
+ bundler.allocator,
+ "Could not resolve: \"{s}\". Maybe you need to run \"npm install\" (or yarn/pnpm)?",
+ .{import_record.path.text},
+ );
+ } else {
+ try bundler.log.addRangeErrorFmt(
+ &result.source,
+ import_record.range,
+ bundler.allocator,
+ "Could not resolve: \"{s}\" relative to \"{s}\"",
+ .{ import_record.path.text, bundler.fs.relativeTo(result.source.key_path.text) },
+ );
+ }
+ },
+ else => {
+ continue;
+ },
+ }
+ };
}
},
else => {},
@@ -195,9 +233,9 @@ pub const Bundler = struct {
result: ParseResult,
) !options.OutputFile {
var allocator = bundler.allocator;
- const relative_path = try std.fs.path.relative(bundler.allocator, bundler.fs.top_level_dir, result.source.path.text);
+ const relative_path = bundler.fs.relativeTo(result.source.path.text);
var out_parts = [_]string{ bundler.options.output_dir, relative_path };
- const out_path = try std.fs.path.join(bundler.allocator, &out_parts);
+ const out_path = try bundler.fs.joinAlloc(bundler.allocator, &out_parts);
const ast = result.ast;
@@ -386,11 +424,18 @@ pub const Bundler = struct {
}
}
- const initial_loader = bundler.options.loaders.get(extension);
+ // We make some things faster in theory by using absolute paths instead of relative paths
+ const absolute_path = resolve_path.normalizeAndJoinStringBuf(
+ bundler.fs.top_level_dir,
+ &tmp_buildfile_buf,
+ &([_][]const u8{relative_path}),
+ .auto,
+ );
- const resolved = (try bundler.resolver.resolve(bundler.fs.top_level_dir, relative_path, .entry_point)) orelse return error.NotFound;
+ const resolved = (try bundler.resolver.resolve(bundler.fs.top_level_dir, absolute_path, .entry_point));
- const output = switch (bundler.options.loaders.get(resolved.path_pair.primary.text) orelse .file) {
+ const loader = bundler.options.loaders.get(resolved.path_pair.primary.name.ext) orelse .file;
+ const output = switch (loader) {
.js, .jsx, .ts, .tsx, .json => ServeResult.Value{
.build = (try bundler.buildWithResolveResult(resolved)) orelse return error.BuildFailed,
},
@@ -402,7 +447,7 @@ pub const Bundler = struct {
return ServeResult{
.value = output,
- .mime_type = MimeType.byExtension(extension),
+ .mime_type = MimeType.byLoader(loader, resolved.path_pair.primary.name.ext),
};
}
@@ -470,7 +515,7 @@ pub const Bundler = struct {
const result = bundler.resolver.resolve(bundler.fs.top_level_dir, entry, .entry_point) catch {
continue;
- } orelse continue;
+ };
const key = result.path_pair.primary.text;
if (bundler.resolve_results.contains(key)) {
continue;
@@ -620,10 +665,9 @@ pub const Transformer = struct {
var _source = &source;
const res = _transform(chosen_alloc, allocator, __log, parser_opts, loader, define, _source) catch continue;
- const relative_path = try std.fs.path.relative(chosen_alloc, cwd, absolutePath);
- var out_parts = [_]string{ output_dir, relative_path };
- const out_path = try std.fs.path.join(allocator, &out_parts);
- try output_files.append(options.OutputFile{ .path = out_path, .contents = res.js });
+ const relative_path = resolve_path.relative(cwd, absolutePath);
+ const out_path = resolve_path.normalizeAndJoin2(cwd, .auto, absolutePath, relative_path);
+ try output_files.append(options.OutputFile{ .path = allocator.dupe(u8, out_path) catch continue, .contents = res.js });
}
return try options.TransformResult.init(output_files.toOwnedSlice(), log, allocator);
diff --git a/src/fs.zig b/src/fs.zig
index 4828a7cb3..fb0e2c3ce 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -41,7 +41,19 @@ pub const FileSystem = struct {
};
pub fn init1(allocator: *std.mem.Allocator, top_level_dir: ?string, enable_watcher: bool) !*FileSystem {
- const _top_level_dir = top_level_dir orelse (if (isBrowser) "/project" else try std.process.getCwdAlloc(allocator));
+ var _top_level_dir = top_level_dir orelse (if (isBrowser) "/project/" else try std.process.getCwdAlloc(allocator));
+
+ // Ensure there's a trailing separator in the top level directory
+ // This makes path resolution more reliable
+ if (!std.fs.path.isSep(_top_level_dir[_top_level_dir.len - 1])) {
+ const tld = try allocator.alloc(u8, _top_level_dir.len + 1);
+ std.mem.copy(u8, tld, _top_level_dir);
+ tld[tld.len - 1] = std.fs.path.sep;
+ if (!isBrowser) {
+ allocator.free(_top_level_dir);
+ }
+ _top_level_dir = tld;
+ }
instance = FileSystem{
.allocator = allocator,
@@ -245,11 +257,54 @@ pub const FileSystem = struct {
});
}
+ pub fn relative(f: *@This(), from: string, to: string) string {
+ return @call(.{ .modifier = .always_inline }, path_handler.relative, .{
+ from,
+ to,
+ });
+ }
+
+ pub fn relativeAlloc(f: *@This(), allocator: *std.mem.Allocator, from: string, to: string) string {
+ return @call(.{ .modifier = .always_inline }, path_handler.relativeAlloc, .{
+ alloc,
+ from,
+ to,
+ });
+ }
+
+ pub fn relativeTo(f: *@This(), to: string) string {
+ return @call(.{ .modifier = .always_inline }, path_handler.relative, .{
+ f.top_level_dir,
+ to,
+ });
+ }
+
+ pub fn relativeToAlloc(f: *@This(), allocator: *std.mem.Allocator, to: string) string {
+ return @call(.{ .modifier = .always_inline }, path_handler.relativeAlloc, .{
+ allocator,
+ f.top_level_dir,
+ to,
+ });
+ }
+
pub fn joinAlloc(f: *@This(), allocator: *std.mem.Allocator, parts: anytype) !string {
const joined = f.join(parts);
return try allocator.dupe(u8, joined);
}
+ threadlocal var realpath_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ pub fn resolveAlloc(f: *@This(), allocator: *std.mem.Allocator, parts: anytype) !string {
+ const joined = f.join(parts);
+
+ const realpath = try std.fs.realpath(joined, (&realpath_buffer));
+
+ return try allocator.dupe(u8, realpath);
+ }
+
+ pub fn resolvePath(f: *@This(), part: string) ![]u8 {
+ return try std.fs.realpath(part, (&realpath_buffer).ptr);
+ }
+
pub const RealFS = struct {
entries_mutex: Mutex = Mutex.init(),
entries: *EntriesOption.Map,
@@ -639,7 +694,7 @@ pub const FileSystem = struct {
// doNotCacheEntries bool
};
- pub const Implementation = comptime {
+ pub const Implementation = {
switch (build_target) {
.wasi, .native => return RealFS,
.wasm => return WasmFS,
@@ -742,6 +797,10 @@ pub const Path = struct {
return Path{ .pretty = text, .text = text, .namespace = "file", .name = PathName.init(text) };
}
+ pub fn initWithPretty(text: string, pretty: string) Path {
+ return Path{ .pretty = pretty, .text = text, .namespace = "file", .name = PathName.init(text) };
+ }
+
pub fn initWithNamespace(text: string, namespace: string) Path {
return Path{ .pretty = text, .text = text, .namespace = namespace, .name = PathName.init(text) };
}
diff --git a/src/global.zig b/src/global.zig
index a188d1e4b..fb96bcb3b 100644
--- a/src/global.zig
+++ b/src/global.zig
@@ -21,6 +21,8 @@ pub const isWindows = std.Target.current.os.tag == .windows;
pub const FeatureFlags = struct {
pub const strong_etags_for_built_files = true;
+
+ pub const use_std_path_relative = false;
};
pub const enableTracing = true;
@@ -55,6 +57,10 @@ pub const Output = struct {
}
};
+ pub fn errorWriter() @typeInfo(@TypeOf(Source.StreamType.writer)).Fn.return_type.? {
+ return source.error_stream.writer();
+ }
+
pub fn printErrorable(comptime fmt: string, args: anytype) !void {
if (isWasm) {
try source.stream.seekTo(0);
diff --git a/src/http.zig b/src/http.zig
index f7d1e9fb1..50e4d7dcb 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -26,6 +26,13 @@ const SOCKET_FLAGS = os.SOCK_CLOEXEC;
threadlocal var req_headers_buf: [100]picohttp.Header = undefined;
threadlocal var res_headers_buf: [100]picohttp.Header = undefined;
+const ENABLE_LOGGER = false;
+pub fn println(comptime fmt: string, args: anytype) void {
+ // if (ENABLE_LOGGER) {
+ Output.println(fmt, args);
+ // }
+}
+
const HTTPStatusCode = u9;
pub const URLPath = struct {
@@ -157,6 +164,8 @@ pub const RequestContext = struct {
keep_alive: bool = true,
status: ?HTTPStatusCode = null,
has_written_last_header: bool = false,
+ has_called_done: bool = false,
+ mime_type: MimeType = MimeType.other,
res_headers_count: usize = 0,
@@ -226,12 +235,21 @@ pub const RequestContext = struct {
}
pub fn writeSocket(ctx: *RequestContext, buf: anytype, flags: anytype) !usize {
- ctx.conn.client.setWriteBufferSize(@intCast(u32, buf.len)) catch {};
- return ctx.conn.client.write(buf, SOCKET_FLAGS);
+ // ctx.conn.client.setWriteBufferSize(@intCast(u32, buf.len)) catch {};
+ const written = ctx.conn.client.write(buf, SOCKET_FLAGS) catch |err| {
+ Output.printError("Write error: {s}", .{@errorName(err)});
+ return err;
+ };
+
+ if (written == 0) {
+ return error.SocketClosed;
+ }
+
+ return written;
}
- pub fn writeBodyBuf(ctx: *RequestContext, body: []const u8) void {
- _ = ctx.writeSocket(body, SOCKET_FLAGS) catch 0;
+ pub fn writeBodyBuf(ctx: *RequestContext, body: []const u8) !void {
+ _ = try ctx.writeSocket(body, SOCKET_FLAGS);
}
pub fn writeStatus(ctx: *RequestContext, comptime code: HTTPStatusCode) !void {
@@ -255,8 +273,8 @@ pub const RequestContext = struct {
return req.writeStatus(404);
}
- pub fn sendInternalError(ctx: *RequestContext, err: anytype) void {
- ctx.writeStatus(500) catch {};
+ pub fn sendInternalError(ctx: *RequestContext, err: anytype) !void {
+ try ctx.writeStatus(500);
const printed = std.fmt.bufPrint(&error_buf, "Error: {s}", .{@errorName(err)}) catch |err2| brk: {
if (isDebug or isTest) {
Global.panic("error while printing error: {s}", .{@errorName(err2)});
@@ -265,21 +283,21 @@ pub const RequestContext = struct {
break :brk "Internal error";
};
- ctx.prepareToSendBody(printed.len, false) catch {};
- ctx.writeBodyBuf(printed);
+ try ctx.prepareToSendBody(printed.len, false);
+ try ctx.writeBodyBuf(printed);
}
threadlocal var error_buf: [4096]u8 = undefined;
- pub fn sendNotModified(ctx: *RequestContext) void {
- ctx.writeStatus(304) catch {};
- ctx.flushHeaders() catch {};
+ pub fn sendNotModified(ctx: *RequestContext) !void {
+ try ctx.writeStatus(304);
+ try ctx.flushHeaders();
ctx.done();
}
- pub fn sendNoContent(ctx: *RequestContext) void {
- ctx.writeStatus(204) catch {};
- ctx.flushHeaders() catch {};
+ pub fn sendNoContent(ctx: *RequestContext) !void {
+ try ctx.writeStatus(204);
+ try ctx.flushHeaders();
ctx.done();
}
@@ -301,17 +319,20 @@ pub const RequestContext = struct {
threadlocal var weak_etag_tmp_buffer: [100]u8 = undefined;
pub fn done(ctx: *RequestContext) void {
+ std.debug.assert(!ctx.has_called_done);
ctx.conn.deinit();
+ ctx.has_called_done = true;
}
- pub fn sendBadRequest(ctx: *RequestContext) void {
- ctx.writeStatus(400) catch {};
+ pub fn sendBadRequest(ctx: *RequestContext) !void {
+ try ctx.writeStatus(400);
ctx.done();
}
pub fn handleGet(ctx: *RequestContext) !void {
const result = try ctx.bundler.buildFile(&ctx.log, ctx.allocator, ctx.url.path, ctx.url.extname);
+ ctx.mime_type = result.mime_type;
ctx.appendHeader("Content-Type", result.mime_type.value);
if (ctx.keep_alive) {
ctx.appendHeader("Connection", "keep-alive");
@@ -342,7 +363,7 @@ pub const RequestContext = struct {
.CharacterDevice,
=> {
ctx.log.addErrorFmt(null, logger.Loc.Empty, ctx.allocator, "Bad file type: {s}", .{@tagName(stat.kind)}) catch {};
- ctx.sendBadRequest();
+ try ctx.sendBadRequest();
return;
},
.SymLink => {
@@ -378,7 +399,7 @@ pub const RequestContext = struct {
if (ctx.header("If-None-Match")) |etag_header| {
if (strings.eql(complete_weak_etag, etag_header.value)) {
- ctx.sendNotModified();
+ try ctx.sendNotModified();
return;
}
}
@@ -388,7 +409,7 @@ pub const RequestContext = struct {
switch (stat.size) {
0 => {
- ctx.sendNoContent();
+ try ctx.sendNoContent();
return;
},
1...file_chunk_size - 1 => {
@@ -401,7 +422,7 @@ pub const RequestContext = struct {
}
const file_slice = file_chunk_slice[0..file_read];
- ctx.writeStatus(200) catch {};
+ try ctx.writeStatus(200);
try ctx.prepareToSendBody(file_read, false);
if (!send_body) return;
_ = try ctx.writeSocket(file_slice, SOCKET_FLAGS);
@@ -451,9 +472,9 @@ pub const RequestContext = struct {
// full chunk
} else {
if (pushed_chunk_count == 0) {
- ctx.writeStatus(200) catch {};
+ try ctx.writeStatus(200);
- ctx.prepareToSendBody(0, true) catch {};
+ try ctx.prepareToSendBody(0, true);
if (!send_body) return;
}
@@ -469,7 +490,6 @@ pub const RequestContext = struct {
}
},
.build => |output| {
- defer ctx.done();
defer ctx.bundler.allocator.free(output.contents);
if (FeatureFlags.strong_etags_for_built_files) {
const strong_etag = std.hash.Wyhash.hash(1, output.contents);
@@ -481,7 +501,7 @@ pub const RequestContext = struct {
if (etag_header.value.len == 8) {
const string_etag_as_u64 = std.mem.readIntSliceNative(u64, etag_header.value);
if (string_etag_as_u64 == strong_etag) {
- ctx.sendNotModified();
+ try ctx.sendNotModified();
return;
}
}
@@ -489,10 +509,11 @@ pub const RequestContext = struct {
}
if (output.contents.len == 0) {
- return ctx.sendNoContent();
+ return try ctx.sendNoContent();
}
- ctx.writeStatus(200) catch {};
+ defer ctx.done();
+ try ctx.writeStatus(200);
try ctx.prepareToSendBody(output.contents.len, false);
if (!send_body) return;
_ = try ctx.writeSocket(output.contents, SOCKET_FLAGS);
@@ -531,34 +552,55 @@ pub const Server = struct {
allocator: *std.mem.Allocator,
bundler: Bundler,
+ pub fn adjustUlimit() !void {
+ var limit = try std.os.getrlimit(.NOFILE);
+ if (limit.cur < limit.max) {
+ var new_limit = std.mem.zeroes(std.os.rlimit);
+ new_limit.cur = limit.max;
+ new_limit.max = limit.max;
+ try std.os.setrlimit(.NOFILE, new_limit);
+ }
+ }
+
+ pub fn onTCPConnection(server: *Server, conn: tcp.Connection) void {
+ conn.client.setNoDelay(true) catch {};
+ conn.client.setQuickACK(true) catch {};
+ conn.client.setLinger(1) catch {};
+
+ server.handleConnection(&conn);
+ }
+
fn run(server: *Server) !void {
+ adjustUlimit() catch {};
const listener = try tcp.Listener.init(.ip, SOCKET_FLAGS);
defer listener.deinit();
listener.setReuseAddress(true) catch {};
listener.setReusePort(true) catch {};
listener.setFastOpen(true) catch {};
+ // listener.setNoDelay(true) catch {};
+ // listener.setQuickACK(true) catch {};
// try listener.ack(true);
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 9000));
- try listener.listen(128);
+ try listener.listen(1280);
const addr = try listener.getLocalAddress();
Output.println("Started Speedy at http://{s}", .{addr});
+ // var listener_handle = try std.os.kqueue();
+ // var change_list = std.mem.zeroes([2]os.Kevent);
- // try listener.set(true);
+ // change_list[0].ident = @intCast(usize, listener.socket.fd);
+ // change_list[1].ident = @intCast(usize, listener.socket.fd);
+ // var eventlist: [128]os.Kevent = undefined;
while (true) {
- var conn = try listener.accept(SOCKET_FLAGS);
- conn.client.setNoDelay(true) catch {};
- conn.client.setQuickACK(true) catch {};
- // conn.client.setLinger(1) catch {};
+ var conn = listener.accept(SOCKET_FLAGS) catch |err| {
+ continue;
+ };
server.handleConnection(&conn);
- conn.client.getError() catch |err| {
- conn.client.deinit();
- };
}
}
@@ -566,16 +608,24 @@ pub const Server = struct {
try server.writeStatus(code, connection);
}
+ threadlocal var req_buf: [32_000]u8 = undefined;
+
pub fn handleConnection(server: *Server, conn: *tcp.Connection) void {
- errdefer conn.deinit();
+
// https://stackoverflow.com/questions/686217/maximum-on-http-header-values
- var req_buf: [std.mem.page_size]u8 = undefined;
var read_size = conn.client.read(&req_buf, SOCKET_FLAGS) catch |err| {
+ _ = conn.client.write(RequestContext.printStatusLine(400) ++ "\r\n\r\n", SOCKET_FLAGS) catch {};
return;
};
+
+ if (read_size == 0) {
+ // Actually, this was not a request.
+ return;
+ }
+
var req = picohttp.Request.parse(req_buf[0..read_size], &req_headers_buf) catch |err| {
+ _ = conn.client.write(RequestContext.printStatusLine(400) ++ "\r\n\r\n", SOCKET_FLAGS) catch {};
Output.printErrorln("ERR: {s}", .{@errorName(err)});
-
return;
};
@@ -584,7 +634,6 @@ pub const Server = struct {
var req_ctx = RequestContext.init(req, &request_arena.allocator, conn, &server.bundler) catch |err| {
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
- conn.deinit();
return;
};
@@ -596,12 +645,11 @@ pub const Server = struct {
req_ctx.handleRequest() catch |err| {
switch (err) {
- error.NotFound => {
+ error.ModuleNotFound => {
req_ctx.sendNotFound() catch {};
},
else => {
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
- conn.deinit();
return;
},
}
@@ -609,7 +657,15 @@ pub const Server = struct {
const status = req_ctx.status orelse @intCast(HTTPStatusCode, 500);
- Output.println("{d} – {s} {s}", .{ status, @tagName(req_ctx.method), req.path });
+ if (req_ctx.log.msgs.items.len == 0) {
+ println("{d} – {s} {s} as {s}", .{ status, @tagName(req_ctx.method), req.path, req_ctx.mime_type.value });
+ } else {
+ println("{s} {s}", .{ @tagName(req_ctx.method), req.path });
+ for (req_ctx.log.msgs.items) |msg| {
+ msg.writeFormat(Output.errorWriter()) catch continue;
+ }
+ req_ctx.log.deinit();
+ }
}
pub fn start(allocator: *std.mem.Allocator, options: Api.TransformOptions) !void {
diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig
index 09227d47c..455e1561f 100644
--- a/src/http/mime_type.zig
+++ b/src/http/mime_type.zig
@@ -1,6 +1,7 @@
const std = @import("std");
usingnamespace @import("../global.zig");
+const Loader = @import("../options.zig").Loader;
const Two = strings.ExactSizeMatcher(2);
const Four = strings.ExactSizeMatcher(4);
const Eight = strings.ExactSizeMatcher(8);
@@ -25,6 +26,8 @@ pub const Category = enum {
};
pub const other = MimeType.init("application/octet-stream", .other);
+pub const css = MimeType.init("application/octet-stream", .other);
+pub const javascript = MimeType.init("text/javascript;charset=utf-8", .javascript);
fn init(comptime str: string, t: Category) MimeType {
return MimeType{
@@ -34,23 +37,38 @@ fn init(comptime str: string, t: Category) MimeType {
}
// TODO: improve this
+pub fn byLoader(loader: Loader, ext: string) MimeType {
+ switch (loader) {
+ .tsx, .ts, .js, .jsx => {
+ return javascript;
+ },
+ .css => {
+ return css;
+ },
+ else => {
+ return byExtension(ext);
+ },
+ }
+}
+
+// TODO: improve this
pub fn byExtension(ext: string) MimeType {
return switch (ext.len) {
2 => {
return switch (std.mem.readIntNative(u16, ext[0..2])) {
- Two.case("js") => MimeType.init("application/javascript;charset=utf-8", .javascript),
+ Two.case("js") => javascript,
else => MimeType.other,
};
},
3 => {
const four = [4]u8{ ext[0], ext[1], ext[2], 0 };
return switch (std.mem.readIntNative(u32, &four)) {
- Four.case("css") => MimeType.init("text/css;charset=utf-8", .css),
+ Four.case("css") => css,
Four.case("jpg") => MimeType.init("image/jpeg", .image),
Four.case("gif") => MimeType.init("image/gif", .image),
Four.case("png") => MimeType.init("image/png", .image),
Four.case("bmp") => MimeType.init("image/bmp", .image),
- Four.case("mjs") => MimeType.init("text/javascript;charset=utf-8", .javascript),
+ Four.case("jsx"), Four.case("mjs") => MimeType.javascript,
Four.case("wav") => MimeType.init("audio/wave", .audio),
Four.case("aac") => MimeType.init("audio/aic", .audio),
Four.case("mp4") => MimeType.init("video/mp4", .video),
diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig
index ea62c81cf..1594de702 100644
--- a/src/resolver/package_json.zig
+++ b/src/resolver/package_json.zig
@@ -46,8 +46,7 @@ pub const PackageJSON = struct {
pub fn parse(r: *resolver.Resolver, input_path: string) ?PackageJSON {
const parts = [_]string{ input_path, "package.json" };
- const package_json_path = std.fs.path.join(r.allocator, &parts) catch unreachable;
- errdefer r.allocator.free(package_json_path);
+ const package_json_path = r.fs.join(&parts);
const entry = r.caches.fs.readFile(r.fs, input_path) catch |err| {
if (err != error.IsDir) {
diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig
index 9cb3e635c..f56ed55c8 100644
--- a/src/resolver/resolve_path.zig
+++ b/src/resolver/resolve_path.zig
@@ -1,13 +1,352 @@
const tester = @import("../test/tester.zig");
+const FeatureFlags = @import("../global.zig").FeatureFlags;
const std = @import("std");
threadlocal var parser_join_input_buffer: [1024]u8 = undefined;
threadlocal var parser_buffer: [1024]u8 = undefined;
+inline fn nqlAtIndex(comptime string_count: comptime_int, index: usize, strings: []const []const u8) bool {
+ comptime var string_index = 1;
+
+ inline while (string_index < string_count) : (string_index += 1) {
+ if (strings[0][index] != strings[string_index][index]) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// 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
+pub fn longestCommonPathGeneric(strings: []const []const u8, comptime separator: u8, comptime isPathSeparator: anytype) []const u8 {
+ var min_length: usize = std.math.maxInt(usize);
+ for (strings) |str| {
+ min_length = std.math.min(str.len, min_length);
+ }
+
+ var last_common_separator_is_at_end = false;
+
+ var index: usize = 0;
+ var last_common_separator: usize = 0;
+
+ // try to use an unrolled version of this loop
+ switch (strings.len) {
+ 0 => {
+ return "";
+ },
+ 1 => {
+ return strings[0];
+ },
+ 2 => {
+ while (index < min_length) : (index += 1) {
+ if (strings[0][index] != strings[1][index]) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 3 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(3, index, strings)) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 4 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(4, index, strings)) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 5 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(5, index, strings)) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 6 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(6, index, strings)) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 7 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(7, index, strings)) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ 8 => {
+ while (index < min_length) : (index += 1) {
+ if (nqlAtIndex(8, index, strings)) {
+ break;
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ else => {
+ var string_index: usize = 1;
+ while (index < min_length) : (index += 1) {
+ while (string_index < strings.len) : (string_index += 1) {
+ if (strings[0][index] != strings[index][string_index]) {
+ break;
+ }
+ }
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) {
+ last_common_separator = index;
+ }
+ }
+ },
+ }
+
+ if (index == 0) {
+ return &([_]u8{separator});
+ }
+
+ // 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 (strings) |str| {
+ if (str.len > index) {
+ if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{str[index]})) {
+ return str[0 .. index + 2];
+ }
+ }
+ }
+
+ return strings[0][0 .. last_common_separator + 1];
+}
+
+pub fn longestCommonPath(strings: []const []const u8) []const u8 {
+ return longestCommonPathGeneric(strings, '/', isSepAny);
+}
+
+pub fn longestCommonPathWindows(strings: []const []const u8) []const u8 {
+ return longestCommonPathGeneric(strings, std.fs.path.sep_windows, isSepWin32);
+}
+
+pub fn longestCommonPathPosix(strings: []const []const u8) []const u8 {
+ return longestCommonPathGeneric(strings, std.fs.path.sep_posix, isSepPosix);
+}
+
+threadlocal var relative_to_common_path_buf: [4096]u8 = undefined;
+
+/// Find a relative path from a common path
+// Loosely based on Node.js' implementation of path.relative
+// https://github.com/nodejs/node/blob/9a7cbe25de88d87429a69050a1a1971234558d97/lib/path.js#L1250-L1259
+pub fn relativeToCommonPath(
+ _common_path: []const u8,
+ normalized_from: []const u8,
+ normalized_to: []const u8,
+ buf: []u8,
+ comptime separator: u8,
+ comptime always_copy: bool,
+) []const u8 {
+ const common_path = if (_common_path.len > 0 and _common_path[0] == separator) _common_path[1..] else _common_path;
+
+ var shortest = std.math.min(normalized_from.len, normalized_to.len);
+
+ var last_common_separator = std.math.max(common_path.len, 1) - 1;
+
+ if (shortest == common_path.len) {
+ if (normalized_to.len > normalized_from.len) {
+ if (common_path.len == 0) {
+ // We get here if `from` is the root
+ // For example: from='/'; to='/foo'
+ if (always_copy) {
+ std.mem.copy(u8, buf, normalized_to);
+ return buf[0..normalized_to.len];
+ } else {
+ return normalized_to;
+ }
+ }
+
+ if (normalized_to[common_path.len - 1] == separator) {
+ const slice = normalized_to[common_path.len..];
+
+ if (always_copy) {
+
+ // We get here if `from` is the exact base path for `to`.
+ // For example: from='/foo/bar'; to='/foo/bar/baz'
+ std.mem.copy(u8, buf, slice);
+ return buf[0..slice.len];
+ } else {
+ return slice;
+ }
+ }
+ }
+
+ if (normalized_from.len > normalized_to.len) {
+ // We get here if `to` is the exact base path for `from`.
+ // For example: from='/foo/bar/baz'; to='/foo/bar'
+ if (normalized_from[common_path.len - 1] == separator) {
+ last_common_separator = common_path.len - 1;
+ } else if (common_path.len == 0) {
+ // We get here if `to` is the root.
+ // For example: from='/foo/bar'; to='/'
+ last_common_separator = 0;
+ }
+ }
+ }
+
+ // Generate the relative path based on the path difference between `to`
+ // and `from`.
+
+ var out_slice: []u8 = buf[0..0];
+
+ if (normalized_from.len > 0) {
+ var i: usize = last_common_separator;
+
+ while (i <= normalized_from.len) : (i += 1) {
+ if (i == normalized_from.len or normalized_from[i] == separator) {
+ if (out_slice.len == 0) {
+ out_slice = buf[0 .. out_slice.len + 2];
+ out_slice[0] = '.';
+ out_slice[1] = '.';
+ } else {
+ var old_len = out_slice.len;
+ out_slice = buf[0 .. out_slice.len + 3];
+ out_slice[old_len] = separator;
+ old_len += 1;
+ out_slice[old_len] = '.';
+ old_len += 1;
+ out_slice[old_len] = '.';
+ }
+ }
+ }
+ }
+
+ if (normalized_to.len > last_common_separator + 1) {
+ const tail = normalized_to[last_common_separator + 1 ..];
+ const insert_leading_slash = tail[0] != separator;
+
+ if (insert_leading_slash) {
+ buf[out_slice.len] = separator;
+ out_slice = buf[0 .. out_slice.len + 1];
+ }
+
+ // Lastly, append the rest of the destination (`to`) path that comes after
+ // the common path parts.
+ const start = out_slice.len;
+ out_slice = buf[0 .. out_slice.len + tail.len];
+
+ std.mem.copy(u8, out_slice[start..], tail);
+ }
+
+ return buf[0 .. out_slice.len + 1];
+}
+
+pub fn relativeNormalized(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 {
+ const two = [_][]const u8{ from, to };
+ const common_path = longestCommonPathGeneric(&two, comptime platform.separator(), platform.isSeparator);
+ return relativeToCommonPath(common_path, from, to, &relative_to_common_path_buf, comptime platform.separator(), always_copy);
+}
+
+threadlocal var relative_from_buf: [4096]u8 = undefined;
+threadlocal var relative_to_buf: [4096]u8 = undefined;
+pub fn relative(from: []const u8, to: []const u8) []const u8 {
+ if (FeatureFlags.use_std_path_relative) {
+ var relative_allocator = std.heap.FixedBufferAllocator.init(&relative_from_buf);
+ return relativeAlloc(&relative_allocator.allocator, from, to) catch unreachable;
+ } else {
+ return relativePlatform(from, to, .auto, false);
+ }
+}
+
+pub fn relativePlatform(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 {
+ const normalized_from = normalizeStringBuf(from, &relative_from_buf, true, platform, true);
+ const normalized_to = normalizeStringBuf(to, &relative_to_buf, true, platform, true);
+
+ return relativeNormalized(normalized_from, normalized_to, platform, always_copy);
+}
+
+pub fn relativeAlloc(allocator: *std.mem.Allocator, from: []const u8, to: []const u8) ![]const u8 {
+ if (FeatureFlags.use_std_path_relative) {
+ return try std.fs.path.relative(allocator, from, to);
+ } else {
+ const result = relativePlatform(from, to, Platform.current, false);
+ return try allocator.dupe(u8, result);
+ }
+}
+
+test "relative" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
+
+ const strs = [_][]const u8{
+ "/var/boo/foo/",
+ "/var/boo/foo/baz/",
+ "/var/boo/foo/beep/",
+ "/var/boo/foo/beep/bleep",
+ "/bar/baz",
+ "/bar/not-related",
+ "/bar/file.txt",
+ };
+ _ = t.expect("var/foo", try relativeAlloc(std.heap.c_allocator, "/", "/var/foo/"), @src());
+ _ = t.expect("index.js", try relativeAlloc(std.heap.c_allocator, "/app/public/", "/app/public/index.js"), @src());
+ _ = t.expect("..", try relativeAlloc(std.heap.c_allocator, "/app/public/index.js", "/app/public/"), @src());
+ _ = t.expect("../../src/bacon.ts", try relativeAlloc(std.heap.c_allocator, "/app/public/index.html", "/app/src/bacon.ts"), @src());
+}
+
+test "longestCommonPath" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
+
+ const strs = [_][]const u8{
+ "/var/boo/foo/",
+ "/var/boo/foo/baz/",
+ "/var/boo/foo/beep/",
+ "/var/boo/foo/beep/bleep",
+ "/bar/baz",
+ "/bar/not-related",
+ "/bar/file.txt",
+ };
+ _ = t.expect("/var/boo/foo/", longestCommonPath(strs[0..2]), @src());
+ _ = t.expect("/var/boo/foo/", longestCommonPath(strs[0..4]), @src());
+ _ = t.expect("/var/boo/foo/beep/", longestCommonPath(strs[2..3]), @src());
+ _ = t.expect("/bar/", longestCommonPath(strs[5..strs.len]), @src());
+ _ = t.expect("/", longestCommonPath(&strs), @src());
+
+ const more = [_][]const u8{ "/app/public/index.html", "/app/public/index.js", "/app/public", "/app/src/bacon.ts" };
+ _ = t.expect("/app/", longestCommonPath(&more), @src());
+ _ = t.expect("/app/public/", longestCommonPath(more[0..2]), @src());
+}
+
// This function is based on Node.js' path.normalize function.
// https://github.com/nodejs/node/blob/36bb31be5f0b85a0f6cbcb36b64feb3a12c60984/lib/path.js#L66
-pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime separator: u8, comptime isPathSeparator: anytype, lastIndexOfSeparator: anytype) []u8 {
+pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime separator: u8, comptime isPathSeparator: anytype, lastIndexOfSeparator: anytype, comptime preserve_trailing_slash: bool) []u8 {
var i: usize = 0;
var last_segment_length: i32 = 0;
var last_slash: i32 = -1;
@@ -69,8 +408,9 @@ pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_r
written_len += 1;
}
- const slice = str[@intCast(usize, @intCast(usize, last_slash + 1))..i];
- std.mem.copy(u8, buf[written_len .. written_len + slice.len], slice);
+ const slice = str[@intCast(usize, last_slash + 1)..i];
+ const base = buf[written_len..];
+ std.mem.copy(u8, base[0..slice.len], slice);
written_len += slice.len;
last_segment_length = @intCast(i32, i) - last_slash - 1;
}
@@ -84,6 +424,14 @@ pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_r
}
}
+ if (preserve_trailing_slash) {
+ // Was there a trailing slash? Let's keep it.
+ if (stop_len == last_slash + 1 and last_segment_length > 0) {
+ buf[written_len] = separator;
+ written_len += 1;
+ }
+ }
+
return buf[0..written_len];
}
@@ -93,6 +441,19 @@ pub const Platform = enum {
windows,
posix,
+ pub fn separator(comptime platform: Platform) u8 {
+ return comptime switch (platform) {
+ .auto => platform.resolve().separator(),
+ .loose, .posix => std.fs.path.sep_posix,
+ .windows => std.fs.path.sep_windows,
+ };
+ }
+
+ pub const current = switch (std.Target.current.os.tag) {
+ .windows => .windows,
+ else => .posix,
+ };
+
pub fn isSeparator(comptime _platform: Platform, char: u8) bool {
const platform = _platform.resolve();
switch (platform) {
@@ -170,24 +531,39 @@ pub const Platform = enum {
};
pub fn normalizeString(str: []const u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 {
- return normalizeStringBuf(str, &parser_buffer, allow_above_root, _platform);
+ return normalizeStringBuf(str, &parser_buffer, allow_above_root, _platform, false);
}
-pub fn normalizeStringBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 {
- comptime const platform = _platform.resolve();
+pub fn normalizeStringBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform, comptime preserve_trailing_slash: anytype) []u8 {
+ const platform = _platform.resolve();
switch (platform) {
.auto => unreachable,
.windows => {
- return normalizeStringWindowsBuf(str, buf, allow_above_root);
+ return normalizeStringWindowsBuf(
+ str,
+ buf,
+ allow_above_root,
+ preserve_trailing_slash,
+ );
},
.posix => {
- return normalizeStringPosixBuf(str, buf, allow_above_root);
+ return normalizeStringPosixBuf(
+ str,
+ buf,
+ allow_above_root,
+ preserve_trailing_slash,
+ );
},
.loose => {
- return normalizeStringLooseBuf(str, buf, allow_above_root);
+ return normalizeStringLooseBuf(
+ str,
+ buf,
+ allow_above_root,
+ preserve_trailing_slash,
+ );
},
}
}
@@ -214,7 +590,12 @@ pub fn normalizeAndJoin(_cwd: []const u8, comptime _platform: Platform, part: an
// without querying the filesystem
// This is the equivalent of
pub fn normalizeAndJoinString(_cwd: []const u8, parts: anytype, comptime _platform: Platform) []const u8 {
- return normalizeAndJoinStringBuf(_cwd, &parser_join_input_buffer, parts, _platform);
+ return normalizeAndJoinStringBuf(
+ _cwd,
+ &parser_join_input_buffer,
+ parts,
+ _platform,
+ );
}
pub fn normalizeAndJoinStringBuf(_cwd: []const u8, buf: []u8, parts: anytype, comptime _platform: Platform) []const u8 {
@@ -271,7 +652,7 @@ pub fn normalizeAndJoinStringBuf(_cwd: []const u8, buf: []u8, parts: anytype, co
}
// One last normalization, to remove any ../ added
- const result = normalizeStringBuf(buf[0..out], parser_buffer[leading_separator.len..parser_buffer.len], false, _platform);
+ const result = normalizeStringBuf(buf[0..out], parser_buffer[leading_separator.len..parser_buffer.len], false, _platform, false);
std.mem.copy(u8, buf[0..leading_separator.len], leading_separator);
std.mem.copy(u8, buf[leading_separator.len .. result.len + leading_separator.len], result);
@@ -302,28 +683,81 @@ pub fn lastIndexOfSeparatorLoose(slice: []const u8) ?usize {
return std.mem.lastIndexOfAny(u8, slice, "/\\");
}
-pub fn normalizeStringPosix(str: []const u8, comptime allow_above_root: bool) []u8 {
- return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix);
+pub fn normalizeStringPosix(str: []const u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 {
+ return normalizeStringGenericBuf(
+ str,
+ &parser_buffer,
+ allow_above_root,
+ std.fs.path.sep_posix,
+ isSepPosix,
+ lastIndexOfSeparatorPosix,
+ preserve_trailing_slash,
+ );
}
-pub fn normalizeStringPosixBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
- return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix);
+pub fn normalizeStringPosixBuf(
+ str: []const u8,
+ buf: []u8,
+ comptime allow_above_root: bool,
+ comptime preserve_trailing_slash: bool,
+) []u8 {
+ return normalizeStringGeneric(
+ str,
+ buf,
+ allow_above_root,
+ std.fs.path.sep_posix,
+ isSepPosix,
+ lastIndexOfSeparatorPosix,
+ preserve_trailing_slash,
+ );
}
-pub fn normalizeStringWindows(str: []const u8, comptime allow_above_root: bool) []u8 {
- return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows);
+pub fn normalizeStringWindows(str: []const u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 {
+ return normalizeStringGenericBuf(
+ str,
+ &parser_buffer,
+ allow_above_root,
+ std.fs.path.sep_windows,
+ isSepWin32,
+ lastIndexOfSeparatorWindows,
+ preserve_trailing_slash,
+ );
}
-pub fn normalizeStringWindowsBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
- return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows);
+pub fn normalizeStringWindowsBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 {
+ return normalizeStringGeneric(
+ str,
+ buf,
+ allow_above_root,
+ std.fs.path.sep_windows,
+ isSepWin32,
+ lastIndexOfSeparatorWindows,
+ preserve_trailing_slash,
+ );
}
-pub fn normalizeStringLoose(str: []const u8, comptime allow_above_root: bool) []u8 {
- return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose);
+pub fn normalizeStringLoose(str: []const u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 {
+ return normalizeStringGenericBuf(
+ str,
+ &parser_buffer,
+ allow_above_root,
+ std.fs.path.sep_posix,
+ isSepAny,
+ lastIndexOfSeparatorLoose,
+ preserve_trailing_slash,
+ );
}
-pub fn normalizeStringLooseBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
- return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose);
+pub fn normalizeStringLooseBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 {
+ return normalizeStringGeneric(
+ str,
+ buf,
+ allow_above_root,
+ std.fs.path.sep_posix,
+ isSepAny,
+ lastIndexOfSeparatorLoose,
+ preserve_trailing_slash,
+ );
}
test "normalizeAndJoinStringPosix" {
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 41da0d261..ed501c711 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -313,7 +313,7 @@ pub const Resolver = struct {
}
var tracing_start: i128 = if (enableTracing) 0 else undefined;
- pub fn resolve(r: *Resolver, source_dir: string, import_path: string, kind: ast.ImportKind) !?Result {
+ pub fn resolve(r: *Resolver, source_dir: string, import_path: string, kind: ast.ImportKind) !Result {
if (enableTracing) {
tracing_start = std.time.nanoTimestamp();
}
@@ -387,7 +387,7 @@ pub const Resolver = struct {
debug.addNote("Cannot resolve this path without a directory") catch {};
}
r.flushDebugLogs(.fail) catch {};
- return null;
+ return error.MissingResolveDir;
}
r.mutex.lock();
@@ -395,7 +395,7 @@ pub const Resolver = struct {
var result = try r.resolveWithoutSymlinks(source_dir, import_path, kind);
- return result;
+ return result orelse error.ModuleNotFound;
}
pub fn resolveWithoutSymlinks(r: *Resolver, source_dir: string, import_path: string, kind: ast.ImportKind) !?Result {
@@ -487,7 +487,7 @@ pub const Resolver = struct {
if (import_dir_info.package_json) |pkg| {
const pkg_json_dir = std.fs.path.dirname(pkg.source.key_path.text) orelse unreachable;
- const rel_path = try std.fs.path.relative(r.allocator, pkg_json_dir, abs_path);
+ const rel_path = r.fs.relative(pkg_json_dir, abs_path);
if (r.checkBrowserMap(pkg, rel_path)) |remap| {
// Is the path disabled?
if (remap.len == 0) {
@@ -602,11 +602,10 @@ pub const Resolver = struct {
const base_dir_info = ((r.dirInfoCached(dirname) catch null)) orelse continue;
const dir_info = base_dir_info.getEnclosingBrowserScope() orelse continue;
const pkg_json = dir_info.package_json orelse continue;
- const rel_path = std.fs.path.relative(r.allocator, pkg_json.source.key_path.text, path.text) catch continue;
+ const rel_path = r.fs.relative(pkg_json.source.key_path.text, path.text);
result.module_type = pkg_json.module_type;
if (r.checkBrowserMap(pkg_json, rel_path)) |remapped| {
if (remapped.len == 0) {
- r.allocator.free(rel_path);
path.is_disabled = true;
} else if (r.resolveWithoutRemapping(dir_info, remapped, kind)) |remapped_result| {
result.is_from_node_modules = remapped_result.is_node_module;
@@ -618,12 +617,7 @@ pub const Resolver = struct {
result.path_pair.secondary = remapped_result.path_pair.primary;
},
}
- } else {
- r.allocator.free(rel_path);
- return null;
}
- } else {
- r.allocator.free(rel_path);
}
}
@@ -1272,7 +1266,7 @@ pub const Resolver = struct {
pub fn loadAsIndexWithBrowserRemapping(r: *Resolver, dir_info: *DirInfo, path: string, extension_order: []const string) ?MatchResult {
if (dir_info.getEnclosingBrowserScope()) |browser_scope| {
- comptime const field_rel_path = "index";
+ const field_rel_path = comptime "index";
if (browser_scope.package_json) |browser_json| {
if (r.checkBrowserMap(browser_json, field_rel_path)) |remap| {
// Is the path disabled?
@@ -1519,7 +1513,7 @@ pub const Resolver = struct {
const segment = base[0..last_dot];
std.mem.copy(u8, &TemporaryBuffer.ExtensionPathBuf, segment);
- comptime const exts = [_]string{ ".ts", ".tsx" };
+ const exts = comptime [_]string{ ".ts", ".tsx" };
for (exts) |ext_to_replace| {
var buffer = TemporaryBuffer.ExtensionPathBuf[0 .. segment.len + ext_to_replace.len];
diff --git a/src/test/tester.zig b/src/test/tester.zig
index 81e1c8ea2..ac5ba57e5 100644
--- a/src/test/tester.zig
+++ b/src/test/tester.zig
@@ -85,7 +85,7 @@ pub const Tester = struct {
fail,
};
- pub fn expect(tester: *Tester, expected: string, result: string, src: std.builtin.SourceLocation) callconv(.Inline) bool {
+ pub inline fn expect(tester: *Tester, expected: string, result: string, src: std.builtin.SourceLocation) bool {
var expectation = Expectation.init(expected, result, src);
switch (expectation.evaluate_outcome()) {
.pass => {
@@ -137,12 +137,12 @@ pub const Tester = struct {
.pass => {
std.fmt.format(stderr.writer(), "{s}All {d} expectations passed.{s}\n", .{ GREEN, tester.pass.items.len, GREEN }) catch unreachable;
std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable;
- std.testing.expect(true);
+ std.testing.expect(true) catch std.debug.panic("Test failure", .{});
},
.fail => {
std.fmt.format(stderr.writer(), "{s}All {d} expectations failed.{s}\n\n", .{ RED, tester.fail.items.len, RED }) catch unreachable;
std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable;
- std.testing.expect(false);
+ std.testing.expect(false) catch std.debug.panic("Test failure", .{});
},
.some_fail => {
std.fmt.format(stderr.writer(), "{s}{d} failed{s} and {s}{d} passed{s} of {d} expectations{s}\n\n", .{
@@ -156,7 +156,7 @@ pub const Tester = struct {
RESET,
}) catch unreachable;
std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable;
- std.testing.expect(false);
+ std.testing.expect(false) catch std.debug.panic("Test failure", .{});
},
}
}