diff options
-rw-r--r-- | .vscode/launch.json | 2 | ||||
-rw-r--r-- | demos/css-stress-test/framework.tsx | 12 | ||||
-rw-r--r-- | src/global.zig | 12 | ||||
-rw-r--r-- | src/http.zig | 38 | ||||
-rw-r--r-- | src/javascript/jsc/api/router.zig | 80 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 83 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/exports.zig | 22 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 31 | ||||
-rw-r--r-- | src/router.zig | 90 | ||||
-rw-r--r-- | src/runtime.version | 2 | ||||
-rw-r--r-- | src/runtime/hmr.ts | 12 | ||||
-rw-r--r-- | src/strings.zig | 6 |
12 files changed, 244 insertions, 146 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index 034d61941..7d212e588 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/demos/css-stress-test/framework.tsx b/demos/css-stress-test/framework.tsx index c07f032bc..47fa068cc 100644 --- a/demos/css-stress-test/framework.tsx +++ b/demos/css-stress-test/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> diff --git a/src/global.zig b/src/global.zig index 561bafde9..f7c9ddc9e 100644 --- a/src/global.zig +++ b/src/global.zig @@ -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}); } }; diff --git a/src/http.zig b/src/http.zig index d2fd7b8ed..86f27bbb4 100644 --- a/src/http.zig +++ b/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(); diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig index 84dad71ef..6e6fea896 100644 --- a/src/javascript/jsc/api/router.zig +++ b/src/javascript/jsc/api/router.zig @@ -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(); } diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 55ce93164..5c2b630e3 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -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 { diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index 89297aaf6..f1996ae0f 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -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" }; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index b681a4cee..1de47d46e 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -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 diff --git a/src/router.zig b/src/router.zig index 40deb4349..d122db0a6 100644 --- a/src/router.zig +++ b/src/router.zig @@ -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 { diff --git a/src/runtime.version b/src/runtime.version index 98fab6350..c31aa4171 100644 --- a/src/runtime.version +++ b/src/runtime.version @@ -1 +1 @@ -37ed98fe5c132c2f
\ No newline at end of file +94d347c63505744e
\ No newline at end of file diff --git a/src/runtime/hmr.ts b/src/runtime/hmr.ts index 8f661e6f9..22b3d1dea 100644 --- a/src/runtime/hmr.ts +++ b/src/runtime/hmr.ts @@ -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; } diff --git a/src/strings.zig b/src/strings.zig index 10fa432d6..cb54349d3 100644 --- a/src/strings.zig +++ b/src/strings.zig @@ -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 { |