mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 11:29:02 +00:00
router almost works
Former-commit-id: a8b9d27bd0946f9c48bd8e4b5b5c2031aa434f28
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -98,7 +98,7 @@
|
||||
"name": "Demo Serve",
|
||||
"program": "${workspaceFolder}/build/debug/macos-x86_64/esdev",
|
||||
"args": [
|
||||
"./src/index.tsx",
|
||||
"pages",
|
||||
"--resolve=lazy",
|
||||
"--outdir=public",
|
||||
"--framework=framework.tsx",
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import ReactDOMServer from "react-dom/server.browser";
|
||||
|
||||
addEventListener("fetch", async (event: FetchEvent) => {
|
||||
const { Base } = await import("./src/index");
|
||||
var route = Wundle.match(event);
|
||||
const { default: PageComponent } = await import(route.filepath);
|
||||
// const router = Wundle.Router.match(event);
|
||||
// console.log("Route", router.name);
|
||||
|
||||
// const { Base: Page } = await router.import();
|
||||
|
||||
const response = new Response(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./src/index.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
crossorigin="anonymous"
|
||||
@@ -15,7 +21,9 @@ addEventListener("fetch", async (event: FetchEvent) => {
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="./src/index.css" />
|
||||
<div id="reactroot">${ReactDOMServer.renderToString(<Base />)}</div>
|
||||
<div id="reactroot">${ReactDOMServer.renderToString(
|
||||
<PageComponent />
|
||||
)}</div>
|
||||
|
||||
<script src="./src/index.tsx" async type="module"></script>
|
||||
</body>
|
||||
|
||||
@@ -394,22 +394,22 @@ pub const PathBuilder = struct {
|
||||
}
|
||||
|
||||
fn load(this: *PathBuilder) void {
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.load, .{this.builder});
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.load, .{&this.builder});
|
||||
}
|
||||
|
||||
pub fn append(this: *PathBuilder, str: string) void {
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.append, .{ this.builder, str });
|
||||
pub fn append(this: *PathBuilder, _str: string) void {
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.append, .{ &this.builder, _str });
|
||||
}
|
||||
|
||||
pub fn pop(this: *PathBuilder, count: usize) void {
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.pop, .{ this.builder, count });
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.pop, .{ &this.builder, count });
|
||||
}
|
||||
|
||||
pub fn str(this: *PathBuilder) string {
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.str, .{this.builder});
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.str, .{&this.builder});
|
||||
}
|
||||
|
||||
pub fn reset(this: *PathBuilder) void {
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.reset, .{this.builder});
|
||||
return @call(.{ .modifier = .always_inline }, StringBuilderType.reset, .{&this.builder});
|
||||
}
|
||||
};
|
||||
|
||||
38
src/http.zig
38
src/http.zig
@@ -117,6 +117,7 @@ pub const URLPath = struct {
|
||||
};
|
||||
|
||||
const path = if (question_mark_i < 0) raw_path[1..] else raw_path[1..@intCast(usize, question_mark_i)];
|
||||
|
||||
const first_segment = raw_path[1..std.math.min(@intCast(usize, first_segment_end), raw_path.len)];
|
||||
|
||||
return URLPath{
|
||||
@@ -197,6 +198,7 @@ pub const RequestContext = struct {
|
||||
matched_route: ?Router.Match = null,
|
||||
|
||||
full_url: [:0]const u8 = "",
|
||||
match_file_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined,
|
||||
res_headers_count: usize = 0,
|
||||
|
||||
pub const bundle_prefix = "__speedy";
|
||||
@@ -213,6 +215,13 @@ pub const RequestContext = struct {
|
||||
return this.full_url;
|
||||
}
|
||||
|
||||
pub fn handleRedirect(this: *RequestContext, url: string) !void {
|
||||
this.appendHeader("Location", url);
|
||||
defer this.done();
|
||||
try this.writeStatus(302);
|
||||
try this.flushHeaders();
|
||||
}
|
||||
|
||||
pub fn header(ctx: *RequestContext, comptime name: anytype) ?Header {
|
||||
if (name.len < 17) {
|
||||
for (ctx.request.headers) |head| {
|
||||
@@ -1685,22 +1694,19 @@ pub const Server = struct {
|
||||
req_ctx.keep_alive = false;
|
||||
}
|
||||
|
||||
if (req_ctx.url.extname.len == 0 and !RequestContext.JavaScriptHandler.javascript_disabled) {
|
||||
if (server.bundler.options.framework != null) {
|
||||
server.javascript_enabled = true;
|
||||
|
||||
// We want to start this on the main thread
|
||||
if (server.watcher.watchloop_handle == null) {
|
||||
server.watcher.start() catch {};
|
||||
if (server.bundler.router) |*router| {
|
||||
router.match(server, RequestContext, &req_ctx) catch |err| {
|
||||
switch (err) {
|
||||
error.ModuleNotFound => {
|
||||
req_ctx.sendNotFound() catch {};
|
||||
},
|
||||
else => {
|
||||
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
RequestContext.JavaScriptHandler.enqueue(&req_ctx, server) catch {
|
||||
server.javascript_enabled = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!req_ctx.controlled) {
|
||||
};
|
||||
} else {
|
||||
req_ctx.handleRequest() catch |err| {
|
||||
switch (err) {
|
||||
error.ModuleNotFound => {
|
||||
@@ -1745,7 +1751,7 @@ pub const Server = struct {
|
||||
};
|
||||
server.bundler = try Bundler.init(allocator, &server.log, options, null);
|
||||
server.bundler.configureLinker();
|
||||
// try server.bundler.configureRouter();
|
||||
try server.bundler.configureRouter();
|
||||
|
||||
try server.initWatcher();
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ const Api = @import("../../../api/schema.zig").Api;
|
||||
const FilesystemRouter = @import("../../../router.zig");
|
||||
const http = @import("../../../http.zig");
|
||||
const JavaScript = @import("../javascript.zig");
|
||||
|
||||
usingnamespace @import("../bindings/bindings.zig");
|
||||
usingnamespace @import("../webcore/response.zig");
|
||||
const Router = @This();
|
||||
|
||||
const VirtualMachine = JavaScript.VirtualMachine;
|
||||
|
||||
route: *const FilesystemRouter.Match,
|
||||
file_path_str: js.JSStringRef = null,
|
||||
pathname_str: js.JSStringRef = null,
|
||||
@@ -20,16 +22,13 @@ pub fn importRoute(
|
||||
arguments: []const js.JSValueRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSObjectRef {
|
||||
return JavaScript.VirtualMachine.instance.require(
|
||||
ctx,
|
||||
std.fs.path.dirname(this.route.file_path).?,
|
||||
this.route.file_path,
|
||||
exception,
|
||||
);
|
||||
const prom = JSModuleLoader.loadAndEvaluateModule(VirtualMachine.vm.global, ZigString.init(this.route.file_path));
|
||||
VirtualMachine.vm.global.vm().drainMicrotasks();
|
||||
return prom.result(VirtualMachine.vm.global.vm()).asRef();
|
||||
}
|
||||
|
||||
pub fn match(
|
||||
obj: *c_void,
|
||||
obj: void,
|
||||
ctx: js.JSContextRef,
|
||||
function: js.JSObjectRef,
|
||||
thisObject: js.JSObjectRef,
|
||||
@@ -96,10 +95,10 @@ fn createRouteObject(ctx: js.JSContextRef, req: *const http.RequestContext, exce
|
||||
.route = route,
|
||||
};
|
||||
|
||||
return Class.new(ctx, router);
|
||||
return Instance.make(ctx, router);
|
||||
}
|
||||
|
||||
const match_type_definition = &[_]d.ts{
|
||||
pub const match_type_definition = &[_]d.ts{
|
||||
.{
|
||||
.tsdoc = "Match a {@link https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent FetchEvent} to a `Route` from the local filesystem. Returns `null` if there is no match.",
|
||||
.args = &[_]d.ts.arg{
|
||||
@@ -132,29 +131,6 @@ const match_type_definition = &[_]d.ts{
|
||||
},
|
||||
};
|
||||
|
||||
pub const Class = NewClass(
|
||||
c_void,
|
||||
.{
|
||||
.name = "Router",
|
||||
.read_only = true,
|
||||
.ts = .{
|
||||
.module = .{
|
||||
.path = "speedy.js/router",
|
||||
.tsdoc = "Filesystem Router supporting dynamic routes, exact routes, catch-all routes, and optional catch-all routes. Implemented in native code and only available with Speedy.js.",
|
||||
},
|
||||
},
|
||||
},
|
||||
.{
|
||||
.match = .{
|
||||
.rfn = match,
|
||||
.ts = match_type_definition,
|
||||
},
|
||||
},
|
||||
.{
|
||||
.Route = Instance.GetClass(c_void){},
|
||||
},
|
||||
);
|
||||
|
||||
pub const Instance = NewClass(
|
||||
Router,
|
||||
.{
|
||||
@@ -249,16 +225,11 @@ pub fn getFilePath(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
if (this.file_path_str == null) {
|
||||
this.file_path_str = js.JSStringCreateWithUTF8CString(this.route.file_path.ptr);
|
||||
}
|
||||
|
||||
return js.JSValueMakeString(ctx, this.file_path_str);
|
||||
return ZigString.init(this.route.file_path).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
pub fn finalize(
|
||||
this: *Router,
|
||||
ctx: js.JSObjectRef,
|
||||
) void {
|
||||
// this.deinit();
|
||||
}
|
||||
@@ -270,11 +241,7 @@ pub fn getPathname(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
if (this.pathname_str == null) {
|
||||
this.pathname_str = js.JSStringCreateWithUTF8CString(this.route.pathname.ptr);
|
||||
}
|
||||
|
||||
return js.JSValueMakeString(ctx, this.pathname_str);
|
||||
return ZigString.init(this.route.pathname).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
pub fn getRoute(
|
||||
@@ -284,9 +251,28 @@ pub fn getRoute(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
return js.JSValueMakeString(ctx, Properties.Refs.default);
|
||||
return ZigString.init(this.route.name).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
const KindEnum = struct {
|
||||
pub const exact = "exact";
|
||||
pub const catch_all = "catch-all";
|
||||
pub const optional_catch_all = "optional-catch-all";
|
||||
pub const dynamic = "dynamic";
|
||||
|
||||
pub fn init(name: string) ZigString {
|
||||
if (strings.contains(name, "[[...")) {
|
||||
return ZigString.init(optional_catch_all);
|
||||
} else if (strings.contains(name, "[...")) {
|
||||
return ZigString.init(catch_all);
|
||||
} else if (strings.contains(name, "[")) {
|
||||
return ZigString.init(dynamic);
|
||||
} else {
|
||||
return ZigString.init(exact);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn getKind(
|
||||
this: *Router,
|
||||
ctx: js.JSContextRef,
|
||||
@@ -294,7 +280,7 @@ pub fn getKind(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
return js.JSValueMakeString(ctx, Properties.Refs.default);
|
||||
return KindEnum.init(this.route.name).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
pub fn getQuery(
|
||||
@@ -304,5 +290,5 @@ pub fn getQuery(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
return js.JSValueMakeString(ctx, Properties.Refs.default);
|
||||
return ZigString.init(this.route.query_string).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const std = @import("std");
|
||||
pub usingnamespace @import("../../global.zig");
|
||||
usingnamespace @import("./javascript.zig");
|
||||
usingnamespace @import("./webcore/response.zig");
|
||||
const Router = @import("./api/router.zig");
|
||||
|
||||
const TaggedPointerTypes = @import("../../tagged_pointer.zig");
|
||||
const TaggedPointerUnion = TaggedPointerTypes.TaggedPointerUnion;
|
||||
@@ -10,6 +11,11 @@ const TaggedPointerUnion = TaggedPointerTypes.TaggedPointerUnion;
|
||||
pub const ExceptionValueRef = [*c]js.JSValueRef;
|
||||
pub const JSValueRef = js.JSValueRef;
|
||||
|
||||
fn ObjectPtrType(comptime Type: type) type {
|
||||
if (Type == void) return Type;
|
||||
return *Type;
|
||||
}
|
||||
|
||||
pub const To = struct {
|
||||
pub const JS = struct {
|
||||
pub inline fn str(ref: anytype, val: anytype) js.JSStringRef {
|
||||
@@ -18,11 +24,11 @@ pub const To = struct {
|
||||
|
||||
pub fn functionWithCallback(
|
||||
comptime ZigContextType: type,
|
||||
zig: *ZigContextType,
|
||||
zig: ObjectPtrType(ZigContextType),
|
||||
name: js.JSStringRef,
|
||||
ctx: js.JSContextRef,
|
||||
comptime callback: fn (
|
||||
obj: *ZigContextType,
|
||||
obj: ObjectPtrType(ZigContextType),
|
||||
ctx: js.JSContextRef,
|
||||
function: js.JSObjectRef,
|
||||
thisObject: js.JSObjectRef,
|
||||
@@ -41,7 +47,7 @@ pub const To = struct {
|
||||
pub fn Finalize(
|
||||
comptime ZigContextType: type,
|
||||
comptime ctxfn: fn (
|
||||
this: *ZigContextType,
|
||||
this: ObjectPtrType(ZigContextType),
|
||||
) void,
|
||||
) type {
|
||||
return struct {
|
||||
@@ -109,7 +115,7 @@ pub const To = struct {
|
||||
pub fn Callback(
|
||||
comptime ZigContextType: type,
|
||||
comptime ctxfn: fn (
|
||||
obj: *ZigContextType,
|
||||
obj: ObjectPtrType(ZigContextType),
|
||||
ctx: js.JSContextRef,
|
||||
function: js.JSObjectRef,
|
||||
thisObject: js.JSObjectRef,
|
||||
@@ -137,6 +143,15 @@ pub const To = struct {
|
||||
if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{},
|
||||
exception,
|
||||
);
|
||||
} else if (comptime ZigContextType == void) {
|
||||
return ctxfn(
|
||||
void{},
|
||||
ctx,
|
||||
function,
|
||||
thisObject,
|
||||
if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{},
|
||||
exception,
|
||||
);
|
||||
} else {
|
||||
return ctxfn(
|
||||
GetJSPrivateData(ZigContextType, function) orelse GetJSPrivateData(ZigContextType, thisObject) orelse return js.JSValueMakeUndefined(ctx),
|
||||
@@ -168,32 +183,6 @@ pub const To = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub const RuntimeImports = struct {
|
||||
pub fn resolve(ctx: js.JSContextRef, str: string) ?js.JSObjectRef {
|
||||
switch (ModuleList.Map.get(str) orelse ModuleList.none) {
|
||||
.Router => {
|
||||
const Router = @import("./api/router.zig");
|
||||
return js.JSObjectMake(ctx, Router.Class.get().*, null);
|
||||
},
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const ModuleList = enum(u8) {
|
||||
Router = 0,
|
||||
none = std.math.maxInt(u8),
|
||||
|
||||
pub const Map = std.ComptimeStringMap(
|
||||
ModuleList,
|
||||
.{
|
||||
.{ "speedy.js/router", ModuleList.Router },
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub const Properties = struct {
|
||||
pub const UTF8 = struct {
|
||||
pub const module: string = "module";
|
||||
@@ -751,6 +740,23 @@ pub fn NewClass(
|
||||
}
|
||||
};
|
||||
|
||||
pub fn throwInvalidConstructorError(ctx: js.JSContextRef, obj: js.JSObjectRef, c: usize, a: [*c]const js.JSValueRef, exception: js.ExceptionRef) callconv(.C) js.JSObjectRef {
|
||||
JSError(getAllocator(ctx), "" ++ name ++ " is not a constructor", .{}, ctx, exception);
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn throwInvalidFunctionError(
|
||||
ctx: js.JSContextRef,
|
||||
function: js.JSObjectRef,
|
||||
thisObject: js.JSObjectRef,
|
||||
argumentCount: usize,
|
||||
arguments: [*c]const js.JSValueRef,
|
||||
exception: js.ExceptionRef,
|
||||
) callconv(.C) js.JSValueRef {
|
||||
JSError(getAllocator(ctx), "" ++ name ++ " is not a function", .{}, ctx, exception);
|
||||
return null;
|
||||
}
|
||||
|
||||
pub const Constructor = ConstructorWrapper.rfn;
|
||||
|
||||
pub const static_value_count = static_properties.len;
|
||||
@@ -872,7 +878,7 @@ pub fn NewClass(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) callconv(.C) js.JSValueRef {
|
||||
var this = GetJSPrivateData(ZigType, obj) orelse return js.JSValueMakeUndefined(ctx);
|
||||
var this: ObjectPtrType(ZigType) = if (comptime ZigType == void) void{} else GetJSPrivateData(ZigType, obj) orelse return js.JSValueMakeUndefined(ctx);
|
||||
|
||||
var exc: js.JSValueRef = null;
|
||||
const Field = @TypeOf(@field(
|
||||
@@ -902,7 +908,7 @@ pub fn NewClass(
|
||||
|
||||
const Func = @typeInfo(@TypeOf(func));
|
||||
const WithPropFn = fn (
|
||||
*ZigType,
|
||||
ObjectPtrType(ZigType),
|
||||
js.JSContextRef,
|
||||
js.JSObjectRef,
|
||||
js.JSStringRef,
|
||||
@@ -910,7 +916,7 @@ pub fn NewClass(
|
||||
) js.JSValueRef;
|
||||
|
||||
const WithoutPropFn = fn (
|
||||
*ZigType,
|
||||
ObjectPtrType(ZigType),
|
||||
js.JSContextRef,
|
||||
js.JSObjectRef,
|
||||
js.ExceptionRef,
|
||||
@@ -1292,7 +1298,7 @@ pub fn NewClass(
|
||||
const ctxfn = CtxField.rfn;
|
||||
const Func: std.builtin.TypeInfo.Fn = @typeInfo(@TypeOf(ctxfn)).Fn;
|
||||
|
||||
const PointerType = std.meta.Child(Func.args[0].arg_type.?);
|
||||
const PointerType = if (Func.args[0].arg_type.? == void) void else std.meta.Child(Func.args[0].arg_type.?);
|
||||
|
||||
var callback = if (Func.calling_convention == .C) ctxfn else To.JS.Callback(
|
||||
PointerType,
|
||||
@@ -1361,6 +1367,14 @@ pub fn NewClass(
|
||||
def.className = class_name_str;
|
||||
// def.getProperty = getPropertyCallback;
|
||||
|
||||
if (def.callAsConstructor == null) {
|
||||
def.callAsConstructor = throwInvalidConstructorError;
|
||||
}
|
||||
|
||||
if (def.callAsFunction == null) {
|
||||
def.callAsFunction = throwInvalidFunctionError;
|
||||
}
|
||||
|
||||
if (!singleton)
|
||||
def.hasInstance = customHasInstance;
|
||||
return def;
|
||||
@@ -1422,6 +1436,7 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
|
||||
FetchEvent,
|
||||
Headers,
|
||||
Body,
|
||||
Router,
|
||||
});
|
||||
|
||||
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {
|
||||
|
||||
@@ -91,16 +91,18 @@ pub const ZigGlobalObject = extern struct {
|
||||
return @call(.{ .modifier = .always_inline }, Interface.onCrash, .{});
|
||||
}
|
||||
|
||||
pub const Export = shim.exportFunctions(.{
|
||||
.@"import" = import,
|
||||
.@"resolve" = resolve,
|
||||
.@"fetch" = fetch,
|
||||
// .@"eval" = eval,
|
||||
.@"promiseRejectionTracker" = promiseRejectionTracker,
|
||||
.@"reportUncaughtException" = reportUncaughtException,
|
||||
.@"createImportMetaProperties" = createImportMetaProperties,
|
||||
.@"onCrash" = onCrash,
|
||||
});
|
||||
pub const Export = shim.exportFunctions(
|
||||
.{
|
||||
.@"import" = import,
|
||||
.@"resolve" = resolve,
|
||||
.@"fetch" = fetch,
|
||||
// .@"eval" = eval,
|
||||
.@"promiseRejectionTracker" = promiseRejectionTracker,
|
||||
.@"reportUncaughtException" = reportUncaughtException,
|
||||
.@"createImportMetaProperties" = createImportMetaProperties,
|
||||
.@"onCrash" = onCrash,
|
||||
},
|
||||
);
|
||||
|
||||
pub const Extern = [_][]const u8{ "create", "getModuleRegistryMap", "resetModuleRegistryMap" };
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ usingnamespace @import("./webcore/response.zig");
|
||||
usingnamespace @import("./config.zig");
|
||||
usingnamespace @import("./bindings/exports.zig");
|
||||
usingnamespace @import("./bindings/bindings.zig");
|
||||
|
||||
const Runtime = @import("../../runtime.zig");
|
||||
const Router = @import("./api/router.zig");
|
||||
|
||||
pub const GlobalClasses = [_]type{
|
||||
Request.Class,
|
||||
@@ -28,6 +28,34 @@ pub const GlobalClasses = [_]type{
|
||||
EventListenerMixin.addEventListener(VirtualMachine),
|
||||
BuildError.Class,
|
||||
ResolveError.Class,
|
||||
Wundle.Class,
|
||||
};
|
||||
|
||||
pub const Wundle = struct {
|
||||
top_level_dir: string,
|
||||
|
||||
pub const Class = NewClass(
|
||||
void,
|
||||
.{
|
||||
.name = "Wundle",
|
||||
.read_only = true,
|
||||
.ts = .{
|
||||
.module = .{
|
||||
.path = "speedy.js/router",
|
||||
.tsdoc = "Filesystem Router supporting dynamic routes, exact routes, catch-all routes, and optional catch-all routes. Implemented in native code and only available with Speedy.js.",
|
||||
},
|
||||
},
|
||||
},
|
||||
.{
|
||||
.match = .{
|
||||
.rfn = Router.match,
|
||||
.ts = Router.match_type_definition,
|
||||
},
|
||||
},
|
||||
.{
|
||||
.Route = Router.Instance.GetClass(void){},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const LazyClasses = [_]type{};
|
||||
@@ -317,6 +345,7 @@ pub const VirtualMachine = struct {
|
||||
|
||||
pub fn resolve(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString) void {
|
||||
var result = ResolveFunctionResult{ .path = "", .result = null };
|
||||
|
||||
_resolve(&result, global, specifier.slice(), source.slice()) catch |err| {
|
||||
// This should almost always just apply to dynamic imports
|
||||
|
||||
|
||||
@@ -171,6 +171,20 @@ pub fn loadRoutes(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime isDebug) {
|
||||
if (comptime is_root) {
|
||||
var i: usize = 0;
|
||||
Output.prettyln("Routes:", .{});
|
||||
while (i < this.routes.routes.len) : (i += 1) {
|
||||
const route = this.routes.routes.get(i);
|
||||
|
||||
Output.prettyln(" {s}: {s}", .{ route.name, route.path });
|
||||
}
|
||||
Output.prettyln(" {d} routes", .{this.routes.routes.len});
|
||||
Output.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TinyPtr = packed struct {
|
||||
@@ -322,10 +336,12 @@ pub const RouteMap = struct {
|
||||
head_i: u16,
|
||||
segment_i: u16,
|
||||
) ?Match {
|
||||
var match = this._matchDynamicRoute(head_i, segment_i) orelse return null;
|
||||
if (this.segments.len == 0) return null;
|
||||
|
||||
const _match = this._matchDynamicRoute(head_i, segment_i) orelse return null;
|
||||
this.matched_route_name.append("/");
|
||||
this.matched_route_name.append(match.name);
|
||||
return match;
|
||||
this.matched_route_name.append(_match.name);
|
||||
return _match;
|
||||
}
|
||||
|
||||
fn _matchDynamicRoute(
|
||||
@@ -335,8 +351,8 @@ pub const RouteMap = struct {
|
||||
) ?Match {
|
||||
const start_len = this.params.len;
|
||||
var head = this.map.routes.get(head_i);
|
||||
const segment = this.segments[segment_i];
|
||||
const remaining = this.segments[segment_i..];
|
||||
const segment: string = this.segments[segment_i];
|
||||
const remaining: []string = this.segments[segment_i..];
|
||||
|
||||
if (remaining.len > 0 and head.children.len == 0) {
|
||||
return null;
|
||||
@@ -373,18 +389,20 @@ pub const RouteMap = struct {
|
||||
this.params.shrinkRetainingCapacity(start_len);
|
||||
return null;
|
||||
} else {
|
||||
match_result = Match{
|
||||
.path = head.path,
|
||||
.name = head.name,
|
||||
.params = this.params,
|
||||
.hash = head.full_hash,
|
||||
.query_string = this.url_path.query_string,
|
||||
.pathname = this.url_path.pathname,
|
||||
};
|
||||
|
||||
if (Fs.FileSystem.DirEntry.EntryStore.instance.at(head.entry_index)) |entry| {
|
||||
var parts = [_]string{ entry.dir, entry.base };
|
||||
match_result.file_path = Fs.FileSystem.instance.absBuf(&parts, this.matched_route_buf);
|
||||
|
||||
match_result = Match{
|
||||
.path = head.path,
|
||||
.name = head.name,
|
||||
.params = this.params,
|
||||
.hash = head.full_hash,
|
||||
.query_string = this.url_path.query_string,
|
||||
.pathname = this.url_path.pathname,
|
||||
.file_path = Fs.FileSystem.instance.absBuf(&parts, &this.matched_route_buf),
|
||||
.basename = entry.base,
|
||||
};
|
||||
|
||||
this.matched_route_buf[match_result.file_path.len] = 0;
|
||||
}
|
||||
}
|
||||
@@ -410,7 +428,7 @@ pub const RouteMap = struct {
|
||||
|
||||
// This makes many passes over the list of routes
|
||||
// However, most of those passes are basically array.indexOf(number) and then smallerArray.indexOf(number)
|
||||
pub fn matchPage(this: *RouteMap, url_path: URLPath, params: *Param.List) ?Match {
|
||||
pub fn matchPage(this: *RouteMap, file_path_buf: []u8, url_path: URLPath, params: *Param.List) ?Match {
|
||||
// Trim trailing slash
|
||||
var path = url_path.path;
|
||||
var redirect = false;
|
||||
@@ -435,12 +453,22 @@ pub const RouteMap = struct {
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(path, ".")) {
|
||||
path = "";
|
||||
redirect = false;
|
||||
}
|
||||
|
||||
if (path.len == 0) {
|
||||
if (this.index) |index| {
|
||||
const entry = Fs.FileSystem.DirEntry.EntryStore.instance.at(this.routes.items(.entry_index)[index]).?;
|
||||
const parts = [_]string{ entry.dir, entry.base };
|
||||
|
||||
return Match{
|
||||
.params = params,
|
||||
.name = "index",
|
||||
.path = this.routes.items(.path)[index],
|
||||
.file_path = Fs.FileSystem.instance.absBuf(&parts, file_path_buf),
|
||||
.basename = entry.base,
|
||||
.pathname = url_path.pathname,
|
||||
.hash = index_route_hash,
|
||||
.query_string = url_path.query_string,
|
||||
@@ -462,12 +490,17 @@ pub const RouteMap = struct {
|
||||
const children = this.routes.items(.hash)[route.children.offset .. route.children.offset + route.children.len];
|
||||
for (children) |child_hash, i| {
|
||||
if (child_hash == index_route_hash) {
|
||||
const entry = Fs.FileSystem.DirEntry.EntryStore.instance.at(this.routes.items(.entry_index)[i + route.children.offset]).?;
|
||||
const parts = [_]string{ entry.dir, entry.base };
|
||||
|
||||
return Match{
|
||||
.params = params,
|
||||
.name = this.routes.items(.name)[i],
|
||||
.path = this.routes.items(.path)[i],
|
||||
.pathname = url_path.pathname,
|
||||
.basename = entry.base,
|
||||
.hash = child_hash,
|
||||
.file_path = Fs.FileSystem.instance.absBuf(&parts, file_path_buf),
|
||||
.query_string = url_path.query_string,
|
||||
};
|
||||
}
|
||||
@@ -475,14 +508,18 @@ pub const RouteMap = struct {
|
||||
// It's an exact route, there are no params
|
||||
// /foo/bar => /foo/bar.js
|
||||
} else {
|
||||
const entry = Fs.FileSystem.DirEntry.EntryStore.instance.at(route.entry_index).?;
|
||||
const parts = [_]string{ entry.dir, entry.base };
|
||||
return Match{
|
||||
.params = params,
|
||||
.name = route.name,
|
||||
.path = route.path,
|
||||
.redirect_path = if (redirect) path else null,
|
||||
.hash = full_hash,
|
||||
.basename = entry.base,
|
||||
.pathname = url_path.pathname,
|
||||
.query_string = url_path.query_string,
|
||||
.file_path = Fs.FileSystem.instance.absBuf(&parts, file_path_buf),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -517,7 +554,8 @@ pub const RouteMap = struct {
|
||||
.url_path = url_path,
|
||||
};
|
||||
|
||||
if (ctx.matchDynamicRoute(0, 0)) |dynamic_route| {
|
||||
if (ctx.matchDynamicRoute(0, 0)) |_dynamic_route| {
|
||||
var dynamic_route = _dynamic_route;
|
||||
dynamic_route.name = ctx.matched_route_name.str();
|
||||
return dynamic_route;
|
||||
}
|
||||
@@ -593,7 +631,7 @@ pub const RoutePart = packed struct {
|
||||
};
|
||||
|
||||
threadlocal var params_list: Param.List = undefined;
|
||||
pub fn match(app: *Router, comptime RequestContextType: type, ctx: *RequestContextType) !void {
|
||||
pub fn match(app: *Router, server: anytype, comptime RequestContextType: type, ctx: *RequestContextType) !void {
|
||||
// If there's an extname assume it's an asset and not a page
|
||||
switch (ctx.url.extname.len) {
|
||||
0 => {},
|
||||
@@ -611,7 +649,7 @@ pub fn match(app: *Router, comptime RequestContextType: type, ctx: *RequestConte
|
||||
}
|
||||
|
||||
params_list.shrinkRetainingCapacity(0);
|
||||
if (app.routes.matchPage(0, ctx.url, ¶ms_list)) |route| {
|
||||
if (app.routes.matchPage(&ctx.match_file_path_buf, ctx.url, ¶ms_list)) |route| {
|
||||
if (route.redirect_path) |redirect| {
|
||||
try ctx.handleRedirect(redirect);
|
||||
return;
|
||||
@@ -620,10 +658,20 @@ pub fn match(app: *Router, comptime RequestContextType: type, ctx: *RequestConte
|
||||
std.debug.assert(route.path.len > 0);
|
||||
|
||||
// ??? render javascript ??
|
||||
try ctx.handleRoute(route);
|
||||
|
||||
if (server.watcher.watchloop_handle == null) {
|
||||
server.watcher.start() catch {};
|
||||
}
|
||||
|
||||
ctx.matched_route = route;
|
||||
RequestContextType.JavaScriptHandler.enqueue(ctx, server) catch {
|
||||
server.javascript_enabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
try ctx.handleRequest();
|
||||
if (!ctx.controlled) {
|
||||
try ctx.handleRequest();
|
||||
}
|
||||
}
|
||||
|
||||
pub const Match = struct {
|
||||
|
||||
@@ -1 +1 @@
|
||||
37ed98fe5c132c2f
|
||||
94d347c63505744e
|
||||
@@ -379,10 +379,12 @@ var __HMRModule, __FastRefreshModule, __HMRClient;
|
||||
this.connect();
|
||||
|
||||
// Explicitly send a socket close event so the thread doesn't have to wait for a timeout
|
||||
var origUnload = globalThis.onunload;
|
||||
globalThis.onunload = (ev: Event) => {
|
||||
var origUnload = globalThis.onbeforeunload;
|
||||
globalThis.onbeforeunload = (ev: Event) => {
|
||||
this.disableReconnect = true;
|
||||
|
||||
if (this.socket && this.socket.readyState === this.socket.OPEN) {
|
||||
this.socket.close(0, "unload");
|
||||
this.socket.close(4990, "unload");
|
||||
}
|
||||
origUnload && origUnload.call(globalThis, [ev]);
|
||||
};
|
||||
@@ -690,8 +692,10 @@ var __HMRModule, __FastRefreshModule, __HMRClient;
|
||||
}
|
||||
};
|
||||
|
||||
disableReconnect = false;
|
||||
|
||||
handleClose = (event: CloseEvent) => {
|
||||
if (this.reconnect !== 0) {
|
||||
if (this.reconnect !== 0 || this.disableReconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ pub fn NewStringBuilder(comptime size: usize) type {
|
||||
this.remain = (&this.buffer)[0..size];
|
||||
}
|
||||
|
||||
pub fn append(this: *This, str: string) void {
|
||||
std.mem.copy(u8, this.remain, str);
|
||||
this.remain = this.remain[str.len..];
|
||||
pub fn append(this: *This, _str: string) void {
|
||||
std.mem.copy(u8, this.remain, _str);
|
||||
this.remain = this.remain[_str.len..];
|
||||
}
|
||||
|
||||
pub fn str(this: *This) string {
|
||||
|
||||
Reference in New Issue
Block a user