diff options
author | 2021-06-12 19:10:08 -0700 | |
---|---|---|
committer | 2021-06-12 19:10:08 -0700 | |
commit | 43380a4d68d57f3d78f5b1e00962a59461140967 (patch) | |
tree | 1b038a16e0324191f0627dd2b9c3881b37d0d6c2 | |
parent | f93472101aa7338b3cdfc9db5c936d010f4cda82 (diff) | |
download | bun-43380a4d68d57f3d78f5b1e00962a59461140967.tar.gz bun-43380a4d68d57f3d78f5b1e00962a59461140967.tar.zst bun-43380a4d68d57f3d78f5b1e00962a59461140967.zip |
I think thats the JS part of HMR
-rw-r--r-- | src/api/schema.d.ts | 77 | ||||
-rw-r--r-- | src/api/schema.js | 280 | ||||
-rw-r--r-- | src/api/schema.peechy | 65 | ||||
-rw-r--r-- | src/api/schema.zig | 1833 | ||||
-rw-r--r-- | src/bundler.zig | 1 | ||||
-rw-r--r-- | src/js_parser/imports.zig | 5 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 41 | ||||
-rw-r--r-- | src/linker.zig | 12 | ||||
-rw-r--r-- | src/options.zig | 3 | ||||
-rw-r--r-- | src/runtime.zig | 7 | ||||
-rw-r--r-- | src/runtime/hmr.ts | 391 | ||||
-rw-r--r-- | src/runtime/index.ts | 2 |
12 files changed, 1853 insertions, 864 deletions
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index b3eefd8dd..43c8bc47f 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -119,6 +119,29 @@ type uint32 = number; 4: "debug", debug: "debug" } + export enum WebsocketMessageKind { + welcome = 1, + file_change_notification = 2, + build_success = 3, + build_fail = 4 + } + export const WebsocketMessageKindKeys = { + 1: "welcome", + welcome: "welcome", + 2: "file_change_notification", + file_change_notification: "file_change_notification", + 3: "build_success", + build_success: "build_success", + 4: "build_fail", + build_fail: "build_fail" + } + export enum WebsocketCommandKind { + build = 1 + } + export const WebsocketCommandKindKeys = { + 1: "build", + build: "build" + } export interface JSX { factory: string; runtime: JSXRuntime; @@ -262,6 +285,46 @@ type uint32 = number; msgs: Message[]; } + export interface WebsocketMessage { + timestamp: uint32; + kind: WebsocketMessageKind; + } + + export interface WebsocketMessageWelcome { + epoch: uint32; + } + + export interface WebsocketMessageFileChangeNotification { + id: uint32; + loader: Loader; + } + + export interface WebsocketCommand { + kind: WebsocketCommandKind; + timestamp: uint32; + } + + export interface WebsocketCommandBuild { + id: uint32; + } + + export interface WebsocketMessageBuildSuccess { + id: uint32; + from_timestamp: uint32; + loader: Loader; + module_path: alphanumeric; + log: Log; + bytes: Uint8Array; + } + + export interface WebsocketMessageBuildFailure { + id: uint32; + from_timestamp: uint32; + loader: Loader; + module_path: alphanumeric; + log: Log; + } + export declare function encodeJSX(message: JSX, bb: ByteBuffer): void; export declare function decodeJSX(buffer: ByteBuffer): JSX; export declare function encodeStringPointer(message: StringPointer, bb: ByteBuffer): void; @@ -300,3 +363,17 @@ type uint32 = number; export declare function decodeMessage(buffer: ByteBuffer): Message; export declare function encodeLog(message: Log, bb: ByteBuffer): void; export declare function decodeLog(buffer: ByteBuffer): Log; + export declare function encodeWebsocketMessage(message: WebsocketMessage, bb: ByteBuffer): void; + export declare function decodeWebsocketMessage(buffer: ByteBuffer): WebsocketMessage; + export declare function encodeWebsocketMessageWelcome(message: WebsocketMessageWelcome, bb: ByteBuffer): void; + export declare function decodeWebsocketMessageWelcome(buffer: ByteBuffer): WebsocketMessageWelcome; + export declare function encodeWebsocketMessageFileChangeNotification(message: WebsocketMessageFileChangeNotification, bb: ByteBuffer): void; + export declare function decodeWebsocketMessageFileChangeNotification(buffer: ByteBuffer): WebsocketMessageFileChangeNotification; + export declare function encodeWebsocketCommand(message: WebsocketCommand, bb: ByteBuffer): void; + export declare function decodeWebsocketCommand(buffer: ByteBuffer): WebsocketCommand; + export declare function encodeWebsocketCommandBuild(message: WebsocketCommandBuild, bb: ByteBuffer): void; + export declare function decodeWebsocketCommandBuild(buffer: ByteBuffer): WebsocketCommandBuild; + export declare function encodeWebsocketMessageBuildSuccess(message: WebsocketMessageBuildSuccess, bb: ByteBuffer): void; + export declare function decodeWebsocketMessageBuildSuccess(buffer: ByteBuffer): WebsocketMessageBuildSuccess; + export declare function encodeWebsocketMessageBuildFailure(message: WebsocketMessageBuildFailure, bb: ByteBuffer): void; + export declare function decodeWebsocketMessageBuildFailure(buffer: ByteBuffer): WebsocketMessageBuildFailure; diff --git a/src/api/schema.js b/src/api/schema.js index e72112e32..71fde7436 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -1236,6 +1236,266 @@ function encodeLog(message, bb) { } } +const WebsocketMessageKind = { + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "welcome": 1, + "file_change_notification": 2, + "build_success": 3, + "build_fail": 4 +}; +const WebsocketMessageKindKeys = { + "1": "welcome", + "2": "file_change_notification", + "3": "build_success", + "4": "build_fail", + "welcome": "welcome", + "file_change_notification": "file_change_notification", + "build_success": "build_success", + "build_fail": "build_fail" +}; +const WebsocketCommandKind = { + "1": 1, + "build": 1 +}; +const WebsocketCommandKindKeys = { + "1": "build", + "build": "build" +}; + +function decodeWebsocketMessage(bb) { + var result = {}; + + result["timestamp"] = bb.readUint32(); + result["kind"] = WebsocketMessageKind[bb.readByte()]; + return result; +} + +function encodeWebsocketMessage(message, bb) { + + var value = message["timestamp"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"timestamp\""); + } + + var value = message["kind"]; + if (value != null) { + var encoded = WebsocketMessageKind[value]; +if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + " for enum \"WebsocketMessageKind\""); +bb.writeByte(encoded); + } else { + throw new Error("Missing required field \"kind\""); + } + +} + +function decodeWebsocketMessageWelcome(bb) { + var result = {}; + + result["epoch"] = bb.readUint32(); + return result; +} + +function encodeWebsocketMessageWelcome(message, bb) { + + var value = message["epoch"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"epoch\""); + } + +} + +function decodeWebsocketMessageFileChangeNotification(bb) { + var result = {}; + + result["id"] = bb.readUint32(); + result["loader"] = Loader[bb.readByte()]; + return result; +} + +function encodeWebsocketMessageFileChangeNotification(message, bb) { + + var value = message["id"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"id\""); + } + + var value = message["loader"]; + if (value != null) { + var encoded = Loader[value]; +if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + " for enum \"Loader\""); +bb.writeByte(encoded); + } else { + throw new Error("Missing required field \"loader\""); + } + +} + +function decodeWebsocketCommand(bb) { + var result = {}; + + result["kind"] = WebsocketCommandKind[bb.readByte()]; + result["timestamp"] = bb.readUint32(); + return result; +} + +function encodeWebsocketCommand(message, bb) { + + var value = message["kind"]; + if (value != null) { + var encoded = WebsocketCommandKind[value]; +if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + " for enum \"WebsocketCommandKind\""); +bb.writeByte(encoded); + } else { + throw new Error("Missing required field \"kind\""); + } + + var value = message["timestamp"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"timestamp\""); + } + +} + +function decodeWebsocketCommandBuild(bb) { + var result = {}; + + result["id"] = bb.readUint32(); + return result; +} + +function encodeWebsocketCommandBuild(message, bb) { + + var value = message["id"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"id\""); + } + +} + +function decodeWebsocketMessageBuildSuccess(bb) { + var result = {}; + + result["id"] = bb.readUint32(); + result["from_timestamp"] = bb.readUint32(); + result["loader"] = Loader[bb.readByte()]; + result["module_path"] = bb.readAlphanumeric(); + result["log"] = decodeLog(bb); + result["bytes"] = bb.readByteArray(); + return result; +} + +function encodeWebsocketMessageBuildSuccess(message, bb) { + + var value = message["id"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"id\""); + } + + var value = message["from_timestamp"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"from_timestamp\""); + } + + var value = message["loader"]; + if (value != null) { + var encoded = Loader[value]; +if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + " for enum \"Loader\""); +bb.writeByte(encoded); + } else { + throw new Error("Missing required field \"loader\""); + } + + var value = message["module_path"]; + if (value != null) { + bb.writeAlphanumeric(value); + } else { + throw new Error("Missing required field \"module_path\""); + } + + var value = message["log"]; + if (value != null) { + encodeLog(value, bb); + } else { + throw new Error("Missing required field \"log\""); + } + + var value = message["bytes"]; + if (value != null) { + bb.writeByteArray(value); + } else { + throw new Error("Missing required field \"bytes\""); + } + +} + +function decodeWebsocketMessageBuildFailure(bb) { + var result = {}; + + result["id"] = bb.readUint32(); + result["from_timestamp"] = bb.readUint32(); + result["loader"] = Loader[bb.readByte()]; + result["module_path"] = bb.readAlphanumeric(); + result["log"] = decodeLog(bb); + return result; +} + +function encodeWebsocketMessageBuildFailure(message, bb) { + + var value = message["id"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"id\""); + } + + var value = message["from_timestamp"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"from_timestamp\""); + } + + var value = message["loader"]; + if (value != null) { + var encoded = Loader[value]; +if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + " for enum \"Loader\""); +bb.writeByte(encoded); + } else { + throw new Error("Missing required field \"loader\""); + } + + var value = message["module_path"]; + if (value != null) { + bb.writeAlphanumeric(value); + } else { + throw new Error("Missing required field \"module_path\""); + } + + var value = message["log"]; + if (value != null) { + encodeLog(value, bb); + } else { + throw new Error("Missing required field \"log\""); + } + +} export { Loader } export { LoaderKeys } @@ -1290,4 +1550,22 @@ export { encodeMessageData } export { decodeMessage } export { encodeMessage } export { decodeLog } -export { encodeLog }
\ No newline at end of file +export { encodeLog } +export { WebsocketMessageKind } +export { WebsocketMessageKindKeys } +export { WebsocketCommandKind } +export { WebsocketCommandKindKeys } +export { decodeWebsocketMessage } +export { encodeWebsocketMessage } +export { decodeWebsocketMessageWelcome } +export { encodeWebsocketMessageWelcome } +export { decodeWebsocketMessageFileChangeNotification } +export { encodeWebsocketMessageFileChangeNotification } +export { decodeWebsocketCommand } +export { encodeWebsocketCommand } +export { decodeWebsocketCommandBuild } +export { encodeWebsocketCommandBuild } +export { decodeWebsocketMessageBuildSuccess } +export { encodeWebsocketMessageBuildSuccess } +export { decodeWebsocketMessageBuildFailure } +export { encodeWebsocketMessageBuildFailure }
\ No newline at end of file diff --git a/src/api/schema.peechy b/src/api/schema.peechy index 257fafd0a..6fa6912ef 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -243,12 +243,65 @@ struct Log { // From a server perspective, this means the filesystem watching thread can send the same WebSocket message // to every client, which is good for performance. It means if you have 5 tabs open it won't really be different than one tab // The clients can just ignore files they don't care about -smol WebsocketMessageType { - file_change = 1, - build_success = 2, - build_fail = 3, +smol WebsocketMessageKind { + welcome = 1; + file_change_notification = 2; + build_success = 3; + build_fail = 4; } -struct WeboscketMessage { +smol WebsocketCommandKind { + build = 1; +} + +// Each websocket message has two messages in it! +// This is the first. +struct WebsocketMessage { + uint32 timestamp; + WebsocketMessageKind kind; +} + +// This is the first. +struct WebsocketMessageWelcome { + uint32 epoch; +} + +struct WebsocketMessageFileChangeNotification { + uint32 id; + Loader loader; +} + +struct WebsocketCommand { + WebsocketCommandKind kind; + uint32 timestamp; +} + +// The timestamp is used for client-side deduping +struct WebsocketCommandBuild { + uint32 id; +} + +// We copy the module_path here incase they don't already have it +struct WebsocketMessageBuildSuccess { + uint32 id; + uint32 from_timestamp; + + Loader loader; + alphanumeric module_path; + + Log log; + byte[] bytes; +} + +struct WebsocketMessageBuildFailure { + uint32 id; + uint32 from_timestamp; + Loader loader; + + alphanumeric module_path; + Log log; +} + + + -}
\ No newline at end of file diff --git a/src/api/schema.zig b/src/api/schema.zig index 319bc9b6f..106815345 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -1,4 +1,3 @@ - const std = @import("std"); pub const Reader = struct { @@ -280,1057 +279,1201 @@ pub fn Writer(comptime WritableStream: type) type { pub const ByteWriter = Writer(std.io.FixedBufferStream([]u8)); pub const FileWriter = Writer(std.fs.File); +pub const Api = struct { + pub const Loader = enum(u8) { + _none, + /// jsx + jsx, + /// js + js, + /// ts + ts, -pub const Api = struct { + /// tsx + tsx, -pub const Loader = enum(u8) { + /// css + css, -_none, - /// jsx - jsx, + /// file + file, - /// js - js, + /// json + json, - /// ts - ts, + _, - /// tsx - tsx, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; - /// css - css, + pub const ResolveMode = enum(u8) { + _none, + /// disable + disable, - /// file - file, + /// lazy + lazy, - /// json - json, + /// dev + dev, -_, + /// bundle + bundle, - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); - } + _, - -}; + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; -pub const ResolveMode = enum(u8) { + pub const Platform = enum(u8) { + _none, + /// browser + browser, -_none, - /// disable - disable, + /// node + node, - /// lazy - lazy, + _, - /// dev - dev, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; - /// bundle - bundle, + pub const JsxRuntime = enum(u8) { + _none, + /// automatic + automatic, -_, + /// classic + classic, - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); - } + _, - -}; + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; -pub const Platform = enum(u8) { + pub const Jsx = struct { + /// factory + factory: []const u8, -_none, - /// browser - browser, + /// runtime + runtime: JsxRuntime, - /// node - node, + /// fragment + fragment: []const u8, -_, + /// development + development: bool = false, - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); - } + /// import_source + import_source: []const u8, - -}; + /// react_fast_refresh + react_fast_refresh: bool = false, -pub const JsxRuntime = enum(u8) { + pub fn decode(reader: anytype) anyerror!Jsx { + var this = std.mem.zeroes(Jsx); -_none, - /// automatic - automatic, + this.factory = try reader.readValue([]const u8); + this.runtime = try reader.readValue(JsxRuntime); + this.fragment = try reader.readValue([]const u8); + this.development = try reader.readValue(bool); + this.import_source = try reader.readValue([]const u8); + this.react_fast_refresh = try reader.readValue(bool); + return this; + } - /// classic - classic, + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.factory); + try writer.writeEnum(this.runtime); + try writer.writeValue(this.fragment); + try writer.writeInt(@intCast(u8, @boolToInt(this.development))); + try writer.writeValue(this.import_source); + try writer.writeInt(@intCast(u8, @boolToInt(this.react_fast_refresh))); + } + }; -_, + pub const StringPointer = packed struct { + /// offset + offset: u32 = 0, - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); - } + /// length + length: u32 = 0, - -}; + pub fn decode(reader: anytype) anyerror!StringPointer { + var this = std.mem.zeroes(StringPointer); -pub const Jsx = struct { -/// factory -factory: []const u8, + this.offset = try reader.readValue(u32); + this.length = try reader.readValue(u32); + return this; + } -/// runtime -runtime: JsxRuntime, + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.offset); + try writer.writeInt(this.length); + } + }; -/// fragment -fragment: []const u8, + pub const JavascriptBundledModule = struct { + /// path + path: StringPointer, -/// development -development: bool = false, + /// code + code: StringPointer, -/// import_source -import_source: []const u8, + /// package_id + package_id: u32 = 0, -/// react_fast_refresh -react_fast_refresh: bool = false, + /// id + id: u32 = 0, + /// path_extname_length + path_extname_length: u8 = 0, -pub fn decode(reader: anytype) anyerror!Jsx { - var this = std.mem.zeroes(Jsx); + pub fn decode(reader: anytype) anyerror!JavascriptBundledModule { + var this = std.mem.zeroes(JavascriptBundledModule); - this.factory = try reader.readValue([]const u8); - this.runtime = try reader.readValue(JsxRuntime); - this.fragment = try reader.readValue([]const u8); - this.development = try reader.readValue(bool); - this.import_source = try reader.readValue([]const u8); - this.react_fast_refresh = try reader.readValue(bool); - return this; -} + this.path = try reader.readValue(StringPointer); + this.code = try reader.readValue(StringPointer); + this.package_id = try reader.readValue(u32); + this.id = try reader.readValue(u32); + this.path_extname_length = try reader.readValue(u8); + return this; + } -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(this.factory); - try writer.writeEnum(this.runtime); - try writer.writeValue(this.fragment); - try writer.writeInt(@intCast(u8, @boolToInt(this.development))); - try writer.writeValue(this.import_source); - try writer.writeInt(@intCast(u8, @boolToInt(this.react_fast_refresh))); -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.path); + try writer.writeValue(this.code); + try writer.writeInt(this.package_id); + try writer.writeInt(this.id); + try writer.writeInt(this.path_extname_length); + } + }; -}; + pub const JavascriptBundledPackage = struct { + /// name + name: StringPointer, -pub const StringPointer = packed struct { -/// offset -offset: u32 = 0, + /// version + version: StringPointer, -/// length -length: u32 = 0, + /// hash + hash: u32 = 0, + /// modules_offset + modules_offset: u32 = 0, -pub fn decode(reader: anytype) anyerror!StringPointer { - var this = std.mem.zeroes(StringPointer); + /// modules_length + modules_length: u32 = 0, - this.offset = try reader.readValue(u32); - this.length = try reader.readValue(u32); - return this; -} + pub fn decode(reader: anytype) anyerror!JavascriptBundledPackage { + var this = std.mem.zeroes(JavascriptBundledPackage); -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeInt(this.offset); - try writer.writeInt(this.length); -} + this.name = try reader.readValue(StringPointer); + this.version = try reader.readValue(StringPointer); + this.hash = try reader.readValue(u32); + this.modules_offset = try reader.readValue(u32); + this.modules_length = try reader.readValue(u32); + return this; + } -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.name); + try writer.writeValue(this.version); + try writer.writeInt(this.hash); + try writer.writeInt(this.modules_offset); + try writer.writeInt(this.modules_length); + } + }; -pub const JavascriptBundledModule = struct { -/// path -path: StringPointer, + pub const JavascriptBundle = struct { + /// modules + modules: []const JavascriptBundledModule, -/// code -code: StringPointer, + /// packages + packages: []const JavascriptBundledPackage, -/// package_id -package_id: u32 = 0, + /// etag + etag: []const u8, -/// id -id: u32 = 0, + /// generated_at + generated_at: u32 = 0, -/// path_extname_length -path_extname_length: u8 = 0, + /// app_package_json_dependencies_hash + app_package_json_dependencies_hash: []const u8, + /// import_from_name + import_from_name: []const u8, -pub fn decode(reader: anytype) anyerror!JavascriptBundledModule { - var this = std.mem.zeroes(JavascriptBundledModule); + /// manifest_string + manifest_string: []const u8, - this.path = try reader.readValue(StringPointer); - this.code = try reader.readValue(StringPointer); - this.package_id = try reader.readValue(u32); - this.id = try reader.readValue(u32); - this.path_extname_length = try reader.readValue(u8); - return this; -} + pub fn decode(reader: anytype) anyerror!JavascriptBundle { + var this = std.mem.zeroes(JavascriptBundle); -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(this.path); - try writer.writeValue(this.code); - try writer.writeInt(this.package_id); - try writer.writeInt(this.id); - try writer.writeInt(this.path_extname_length); -} + this.modules = try reader.readArray(JavascriptBundledModule); + this.packages = try reader.readArray(JavascriptBundledPackage); + this.etag = try reader.readArray(u8); + this.generated_at = try reader.readValue(u32); + this.app_package_json_dependencies_hash = try reader.readArray(u8); + this.import_from_name = try reader.readArray(u8); + this.manifest_string = try reader.readArray(u8); + return this; + } -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeArray(JavascriptBundledModule, this.modules); + try writer.writeArray(JavascriptBundledPackage, this.packages); + try writer.writeArray(u8, this.etag); + try writer.writeInt(this.generated_at); + try writer.writeArray(u8, this.app_package_json_dependencies_hash); + try writer.writeArray(u8, this.import_from_name); + try writer.writeArray(u8, this.manifest_string); + } + }; -pub const JavascriptBundledPackage = struct { -/// name -name: StringPointer, + pub const JavascriptBundleContainer = struct { + /// bundle_format_version + bundle_format_version: ?u32 = null, -/// version -version: StringPointer, + /// bundle + bundle: ?JavascriptBundle = null, -/// hash -hash: u32 = 0, + /// code_length + code_length: ?u32 = null, -/// modules_offset -modules_offset: u32 = 0, + pub fn decode(reader: anytype) anyerror!JavascriptBundleContainer { + var this = std.mem.zeroes(JavascriptBundleContainer); -/// modules_length -modules_length: u32 = 0, + while (true) { + switch (try reader.readByte()) { + 0 => { + return this; + }, + 1 => { + this.bundle_format_version = try reader.readValue(u32); + }, + 2 => { + this.bundle = try reader.readValue(JavascriptBundle); + }, + 3 => { + this.code_length = try reader.readValue(u32); + }, + else => { + return error.InvalidMessage; + }, + } + } + unreachable; + } -pub fn decode(reader: anytype) anyerror!JavascriptBundledPackage { - var this = std.mem.zeroes(JavascriptBundledPackage); + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + if (this.bundle_format_version) |bundle_format_version| { + try writer.writeFieldID(1); + try writer.writeInt(bundle_format_version); + } + if (this.bundle) |bundle| { + try writer.writeFieldID(2); + try writer.writeValue(bundle); + } + if (this.code_length) |code_length| { + try writer.writeFieldID(3); + try writer.writeInt(code_length); + } + try writer.endMessage(); + } + }; - this.name = try reader.readValue(StringPointer); - this.version = try reader.readValue(StringPointer); - this.hash = try reader.readValue(u32); - this.modules_offset = try reader.readValue(u32); - this.modules_length = try reader.readValue(u32); - return this; -} + pub const ScanDependencyMode = enum(u8) { + _none, + /// app + app, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(this.name); - try writer.writeValue(this.version); - try writer.writeInt(this.hash); - try writer.writeInt(this.modules_offset); - try writer.writeInt(this.modules_length); -} + /// all + all, -}; + _, -pub const JavascriptBundle = struct { -/// modules -modules: []const JavascriptBundledModule, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; -/// packages -packages: []const JavascriptBundledPackage, + pub const ModuleImportType = enum(u8) { + _none, + /// import + import, -/// etag -etag: []const u8, + /// require + require, -/// generated_at -generated_at: u32 = 0, + _, -/// app_package_json_dependencies_hash -app_package_json_dependencies_hash: []const u8, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; -/// import_from_name -import_from_name: []const u8, + pub const ModuleImportRecord = struct { + /// kind + kind: ModuleImportType, -/// manifest_string -manifest_string: []const u8, + /// path + path: []const u8, + /// dynamic + dynamic: bool = false, -pub fn decode(reader: anytype) anyerror!JavascriptBundle { - var this = std.mem.zeroes(JavascriptBundle); + pub fn decode(reader: anytype) anyerror!ModuleImportRecord { + var this = std.mem.zeroes(ModuleImportRecord); - this.modules = try reader.readArray(JavascriptBundledModule); - this.packages = try reader.readArray(JavascriptBundledPackage); - this.etag = try reader.readArray(u8); - this.generated_at = try reader.readValue(u32); - this.app_package_json_dependencies_hash = try reader.readArray(u8); - this.import_from_name = try reader.readArray(u8); - this.manifest_string = try reader.readArray(u8); - return this; -} + this.kind = try reader.readValue(ModuleImportType); + this.path = try reader.readValue([]const u8); + this.dynamic = try reader.readValue(bool); + return this; + } -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeArray(JavascriptBundledModule, this.modules); - try writer.writeArray(JavascriptBundledPackage, this.packages); - try writer.writeArray(u8, this.etag); - try writer.writeInt(this.generated_at); - try writer.writeArray(u8, this.app_package_json_dependencies_hash); - try writer.writeArray(u8, this.import_from_name); - try writer.writeArray(u8, this.manifest_string); -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeEnum(this.kind); + try writer.writeValue(this.path); + try writer.writeInt(@intCast(u8, @boolToInt(this.dynamic))); + } + }; -}; + pub const Module = struct { + /// path + path: []const u8, -pub const JavascriptBundleContainer = struct { -/// bundle_format_version -bundle_format_version: ?u32 = null, - -/// bundle -bundle: ?JavascriptBundle = null, - -/// code_length -code_length: ?u32 = null, - - -pub fn decode(reader: anytype) anyerror!JavascriptBundleContainer { - var this = std.mem.zeroes(JavascriptBundleContainer); - - while(true) { - switch (try reader.readByte()) { - 0 => { return this; }, - - 1 => { - this.bundle_format_version = try reader.readValue(u32); -}, - 2 => { - this.bundle = try reader.readValue(JavascriptBundle); -}, - 3 => { - this.code_length = try reader.readValue(u32); -}, - else => { - return error.InvalidMessage; - }, - } - } -unreachable; -} + /// imports + imports: []const ModuleImportRecord, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { -if (this.bundle_format_version) |bundle_format_version| { - try writer.writeFieldID(1); - try writer.writeInt(bundle_format_version); -} -if (this.bundle) |bundle| { - try writer.writeFieldID(2); - try writer.writeValue(bundle); -} -if (this.code_length) |code_length| { - try writer.writeFieldID(3); - try writer.writeInt(code_length); -} -try writer.endMessage(); -} + pub fn decode(reader: anytype) anyerror!Module { + var this = std.mem.zeroes(Module); -}; + this.path = try reader.readValue([]const u8); + this.imports = try reader.readArray(ModuleImportRecord); + return this; + } -pub const ScanDependencyMode = enum(u8) { + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.path); + try writer.writeArray(ModuleImportRecord, this.imports); + } + }; -_none, - /// app - app, + pub const StringMap = struct { + /// keys + keys: []const []const u8, - /// all - all, + /// values + values: []const []const u8, -_, + pub fn decode(reader: anytype) anyerror!StringMap { + var this = std.mem.zeroes(StringMap); - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); - } + this.keys = try reader.readArray([]const u8); + this.values = try reader.readArray([]const u8); + return this; + } - -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeArray([]const u8, this.keys); + try writer.writeArray([]const u8, this.values); + } + }; + + pub const LoaderMap = struct { + /// extensions + extensions: []const []const u8, + + /// loaders + loaders: []const Loader, + + pub fn decode(reader: anytype) anyerror!LoaderMap { + var this = std.mem.zeroes(LoaderMap); + + this.extensions = try reader.readArray([]const u8); + this.loaders = try reader.readArray(Loader); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeArray([]const u8, this.extensions); + try writer.writeArray(Loader, this.loaders); + } + }; + + pub const TransformOptions = struct { + /// jsx + jsx: ?Jsx = null, + + /// tsconfig_override + tsconfig_override: ?[]const u8 = null, + + /// resolve + resolve: ?ResolveMode = null, + + /// public_url + public_url: ?[]const u8 = null, + + /// absolute_working_dir + absolute_working_dir: ?[]const u8 = null, + + /// define + define: ?StringMap = null, + + /// preserve_symlinks + preserve_symlinks: ?bool = null, + + /// entry_points + entry_points: []const []const u8, + + /// write + write: ?bool = null, -pub const ModuleImportType = enum(u8) { + /// inject + inject: []const []const u8, -_none, - /// import - import, + /// output_dir + output_dir: ?[]const u8 = null, - /// require - require, + /// external + external: []const []const u8, -_, + /// loaders + loaders: ?LoaderMap = null, - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); + /// main_fields + main_fields: []const []const u8, + + /// platform + platform: ?Platform = null, + + /// serve + serve: ?bool = null, + + /// extension_order + extension_order: []const []const u8, + + /// public_dir + public_dir: ?[]const u8 = null, + + /// only_scan_dependencies + only_scan_dependencies: ?ScanDependencyMode = null, + + /// generate_node_module_bundle + generate_node_module_bundle: ?bool = null, + + /// node_modules_bundle_path + node_modules_bundle_path: ?[]const u8 = null, + + pub fn decode(reader: anytype) anyerror!TransformOptions { + var this = std.mem.zeroes(TransformOptions); + + while (true) { + switch (try reader.readByte()) { + 0 => { + return this; + }, + + 1 => { + this.jsx = try reader.readValue(Jsx); + }, + 2 => { + this.tsconfig_override = try reader.readValue([]const u8); + }, + 3 => { + this.resolve = try reader.readValue(ResolveMode); + }, + 4 => { + this.public_url = try reader.readValue([]const u8); + }, + 5 => { + this.absolute_working_dir = try reader.readValue([]const u8); + }, + 6 => { + this.define = try reader.readValue(StringMap); + }, + 7 => { + this.preserve_symlinks = try reader.readValue(bool); + }, + 8 => { + this.entry_points = try reader.readArray([]const u8); + }, + 9 => { + this.write = try reader.readValue(bool); + }, + 10 => { + this.inject = try reader.readArray([]const u8); + }, + 11 => { + this.output_dir = try reader.readValue([]const u8); + }, + 12 => { + this.external = try reader.readArray([]const u8); + }, + 13 => { + this.loaders = try reader.readValue(LoaderMap); + }, + 14 => { + this.main_fields = try reader.readArray([]const u8); + }, + 15 => { + this.platform = try reader.readValue(Platform); + }, + 16 => { + this.serve = try reader.readValue(bool); + }, + 17 => { + this.extension_order = try reader.readArray([]const u8); + }, + 18 => { + this.public_dir = try reader.readValue([]const u8); + }, + 19 => { + this.only_scan_dependencies = try reader.readValue(ScanDependencyMode); + }, + 20 => { + this.generate_node_module_bundle = try reader.readValue(bool); + }, + 21 => { + this.node_modules_bundle_path = try reader.readValue([]const u8); + }, + else => { + return error.InvalidMessage; + }, } + } + unreachable; + } - -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + if (this.jsx) |jsx| { + try writer.writeFieldID(1); + try writer.writeValue(jsx); + } + if (this.tsconfig_override) |tsconfig_override| { + try writer.writeFieldID(2); + try writer.writeValue(tsconfig_override); + } + if (this.resolve) |resolve| { + try writer.writeFieldID(3); + try writer.writeEnum(resolve); + } + if (this.public_url) |public_url| { + try writer.writeFieldID(4); + try writer.writeValue(public_url); + } + if (this.absolute_working_dir) |absolute_working_dir| { + try writer.writeFieldID(5); + try writer.writeValue(absolute_working_dir); + } + if (this.define) |define| { + try writer.writeFieldID(6); + try writer.writeValue(define); + } + if (this.preserve_symlinks) |preserve_symlinks| { + try writer.writeFieldID(7); + try writer.writeInt(@intCast(u8, @boolToInt(preserve_symlinks))); + } + if (this.entry_points) |entry_points| { + try writer.writeFieldID(8); + try writer.writeArray([]const u8, entry_points); + } + if (this.write) |write| { + try writer.writeFieldID(9); + try writer.writeInt(@intCast(u8, @boolToInt(write))); + } + if (this.inject) |inject| { + try writer.writeFieldID(10); + try writer.writeArray([]const u8, inject); + } + if (this.output_dir) |output_dir| { + try writer.writeFieldID(11); + try writer.writeValue(output_dir); + } + if (this.external) |external| { + try writer.writeFieldID(12); + try writer.writeArray([]const u8, external); + } + if (this.loaders) |loaders| { + try writer.writeFieldID(13); + try writer.writeValue(loaders); + } + if (this.main_fields) |main_fields| { + try writer.writeFieldID(14); + try writer.writeArray([]const u8, main_fields); + } + if (this.platform) |platform| { + try writer.writeFieldID(15); + try writer.writeEnum(platform); + } + if (this.serve) |serve| { + try writer.writeFieldID(16); + try writer.writeInt(@intCast(u8, @boolToInt(serve))); + } + if (this.extension_order) |extension_order| { + try writer.writeFieldID(17); + try writer.writeArray([]const u8, extension_order); + } + if (this.public_dir) |public_dir| { + try writer.writeFieldID(18); + try writer.writeValue(public_dir); + } + if (this.only_scan_dependencies) |only_scan_dependencies| { + try writer.writeFieldID(19); + try writer.writeEnum(only_scan_dependencies); + } + if (this.generate_node_module_bundle) |generate_node_module_bundle| { + try writer.writeFieldID(20); + try writer.writeInt(@intCast(u8, @boolToInt(generate_node_module_bundle))); + } + if (this.node_modules_bundle_path) |node_modules_bundle_path| { + try writer.writeFieldID(21); + try writer.writeValue(node_modules_bundle_path); + } + try writer.endMessage(); + } + }; -pub const ModuleImportRecord = struct { -/// kind -kind: ModuleImportType, + pub const FileHandle = struct { + /// path + path: []const u8, -/// path -path: []const u8, + /// size + size: u32 = 0, -/// dynamic -dynamic: bool = false, + /// fd + fd: u32 = 0, + pub fn decode(reader: anytype) anyerror!FileHandle { + var this = std.mem.zeroes(FileHandle); -pub fn decode(reader: anytype) anyerror!ModuleImportRecord { - var this = std.mem.zeroes(ModuleImportRecord); + this.path = try reader.readValue([]const u8); + this.size = try reader.readValue(u32); + this.fd = try reader.readValue(u32); + return this; + } - this.kind = try reader.readValue(ModuleImportType); - this.path = try reader.readValue([]const u8); - this.dynamic = try reader.readValue(bool); - return this; -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.path); + try writer.writeInt(this.size); + try writer.writeInt(this.fd); + } + }; -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeEnum(this.kind); - try writer.writeValue(this.path); - try writer.writeInt(@intCast(u8, @boolToInt(this.dynamic))); -} + pub const Transform = struct { + /// handle + handle: ?FileHandle = null, -}; + /// path + path: ?[]const u8 = null, -pub const Module = struct { -/// path -path: []const u8, + /// contents + contents: []const u8, -/// imports -imports: []const ModuleImportRecord, + /// loader + loader: ?Loader = null, + /// options + options: ?TransformOptions = null, -pub fn decode(reader: anytype) anyerror!Module { - var this = std.mem.zeroes(Module); + pub fn decode(reader: anytype) anyerror!Transform { + var this = std.mem.zeroes(Transform); - this.path = try reader.readValue([]const u8); - this.imports = try reader.readArray(ModuleImportRecord); - return this; -} + while (true) { + switch (try reader.readByte()) { + 0 => { + return this; + }, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(this.path); - try writer.writeArray(ModuleImportRecord, this.imports); -} + 1 => { + this.handle = try reader.readValue(FileHandle); + }, + 2 => { + this.path = try reader.readValue([]const u8); + }, + 3 => { + this.contents = try reader.readArray(u8); + }, + 4 => { + this.loader = try reader.readValue(Loader); + }, + 5 => { + this.options = try reader.readValue(TransformOptions); + }, + else => { + return error.InvalidMessage; + }, + } + } + unreachable; + } -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + if (this.handle) |handle| { + try writer.writeFieldID(1); + try writer.writeValue(handle); + } + if (this.path) |path| { + try writer.writeFieldID(2); + try writer.writeValue(path); + } + if (this.contents) |contents| { + try writer.writeFieldID(3); + try writer.writeArray(u8, contents); + } + if (this.loader) |loader| { + try writer.writeFieldID(4); + try writer.writeEnum(loader); + } + if (this.options) |options| { + try writer.writeFieldID(5); + try writer.writeValue(options); + } + try writer.endMessage(); + } + }; -pub const StringMap = struct { -/// keys -keys: []const []const u8, + pub const TransformResponseStatus = enum(u32) { + _none, + /// success + success, -/// values -values: []const []const u8, + /// fail + fail, + _, -pub fn decode(reader: anytype) anyerror!StringMap { - var this = std.mem.zeroes(StringMap); + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; - this.keys = try reader.readArray([]const u8); - this.values = try reader.readArray([]const u8); - return this; -} + pub const OutputFile = struct { + /// data + data: []const u8, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeArray([]const u8, this.keys); - try writer.writeArray([]const u8, this.values); -} + /// path + path: []const u8, -}; + pub fn decode(reader: anytype) anyerror!OutputFile { + var this = std.mem.zeroes(OutputFile); -pub const LoaderMap = struct { -/// extensions -extensions: []const []const u8, + this.data = try reader.readArray(u8); + this.path = try reader.readValue([]const u8); + return this; + } -/// loaders -loaders: []const Loader, + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeArray(u8, this.data); + try writer.writeValue(this.path); + } + }; + pub const TransformResponse = struct { + /// status + status: TransformResponseStatus, -pub fn decode(reader: anytype) anyerror!LoaderMap { - var this = std.mem.zeroes(LoaderMap); + /// files + files: []const OutputFile, - this.extensions = try reader.readArray([]const u8); - this.loaders = try reader.readArray(Loader); - return this; -} + /// errors + errors: []const Message, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeArray([]const u8, this.extensions); - try writer.writeArray(Loader, this.loaders); -} + pub fn decode(reader: anytype) anyerror!TransformResponse { + var this = std.mem.zeroes(TransformResponse); -}; + this.status = try reader.readValue(TransformResponseStatus); + this.files = try reader.readArray(OutputFile); + this.errors = try reader.readArray(Message); + return this; + } -pub const TransformOptions = struct { -/// jsx -jsx: ?Jsx = null, - -/// tsconfig_override -tsconfig_override: ?[]const u8 = null, - -/// resolve -resolve: ?ResolveMode = null, - -/// public_url -public_url: ?[]const u8 = null, - -/// absolute_working_dir -absolute_working_dir: ?[]const u8 = null, - -/// define -define: ?StringMap = null, - -/// preserve_symlinks -preserve_symlinks: ?bool = null, - -/// entry_points -entry_points: []const []const u8, - -/// write -write: ?bool = null, - -/// inject -inject: []const []const u8, - -/// output_dir -output_dir: ?[]const u8 = null, - -/// external -external: []const []const u8, - -/// loaders -loaders: ?LoaderMap = null, - -/// main_fields -main_fields: []const []const u8, - -/// platform -platform: ?Platform = null, - -/// serve -serve: ?bool = null, - -/// extension_order -extension_order: []const []const u8, - -/// public_dir -public_dir: ?[]const u8 = null, - -/// only_scan_dependencies -only_scan_dependencies: ?ScanDependencyMode = null, - -/// generate_node_module_bundle -generate_node_module_bundle: ?bool = null, - -/// node_modules_bundle_path -node_modules_bundle_path: ?[]const u8 = null, - - -pub fn decode(reader: anytype) anyerror!TransformOptions { - var this = std.mem.zeroes(TransformOptions); - - while(true) { - switch (try reader.readByte()) { - 0 => { return this; }, - - 1 => { - this.jsx = try reader.readValue(Jsx); -}, - 2 => { - this.tsconfig_override = try reader.readValue([]const u8); -}, - 3 => { - this.resolve = try reader.readValue(ResolveMode); -}, - 4 => { - this.public_url = try reader.readValue([]const u8); -}, - 5 => { - this.absolute_working_dir = try reader.readValue([]const u8); -}, - 6 => { - this.define = try reader.readValue(StringMap); -}, - 7 => { - this.preserve_symlinks = try reader.readValue(bool); -}, - 8 => { - this.entry_points = try reader.readArray([]const u8); -}, - 9 => { - this.write = try reader.readValue(bool); -}, - 10 => { - this.inject = try reader.readArray([]const u8); -}, - 11 => { - this.output_dir = try reader.readValue([]const u8); -}, - 12 => { - this.external = try reader.readArray([]const u8); -}, - 13 => { - this.loaders = try reader.readValue(LoaderMap); -}, - 14 => { - this.main_fields = try reader.readArray([]const u8); -}, - 15 => { - this.platform = try reader.readValue(Platform); -}, - 16 => { - this.serve = try reader.readValue(bool); -}, - 17 => { - this.extension_order = try reader.readArray([]const u8); -}, - 18 => { - this.public_dir = try reader.readValue([]const u8); -}, - 19 => { - this.only_scan_dependencies = try reader.readValue(ScanDependencyMode); -}, - 20 => { - this.generate_node_module_bundle = try reader.readValue(bool); -}, - 21 => { - this.node_modules_bundle_path = try reader.readValue([]const u8); -}, - else => { - return error.InvalidMessage; - }, - } - } -unreachable; -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeEnum(this.status); + try writer.writeArray(OutputFile, this.files); + try writer.writeArray(Message, this.errors); + } + }; -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { -if (this.jsx) |jsx| { - try writer.writeFieldID(1); - try writer.writeValue(jsx); -} -if (this.tsconfig_override) |tsconfig_override| { - try writer.writeFieldID(2); - try writer.writeValue(tsconfig_override); -} -if (this.resolve) |resolve| { - try writer.writeFieldID(3); - try writer.writeEnum(resolve); -} -if (this.public_url) |public_url| { - try writer.writeFieldID(4); - try writer.writeValue(public_url); -} -if (this.absolute_working_dir) |absolute_working_dir| { - try writer.writeFieldID(5); - try writer.writeValue(absolute_working_dir); -} -if (this.define) |define| { - try writer.writeFieldID(6); - try writer.writeValue(define); -} -if (this.preserve_symlinks) |preserve_symlinks| { - try writer.writeFieldID(7); - try writer.writeInt(@intCast(u8, @boolToInt(preserve_symlinks))); -} -if (this.entry_points) |entry_points| { - try writer.writeFieldID(8); - try writer.writeArray([]const u8, entry_points); -} -if (this.write) |write| { - try writer.writeFieldID(9); - try writer.writeInt(@intCast(u8, @boolToInt(write))); -} -if (this.inject) |inject| { - try writer.writeFieldID(10); - try writer.writeArray([]const u8, inject); -} -if (this.output_dir) |output_dir| { - try writer.writeFieldID(11); - try writer.writeValue(output_dir); -} -if (this.external) |external| { - try writer.writeFieldID(12); - try writer.writeArray([]const u8, external); -} -if (this.loaders) |loaders| { - try writer.writeFieldID(13); - try writer.writeValue(loaders); -} -if (this.main_fields) |main_fields| { - try writer.writeFieldID(14); - try writer.writeArray([]const u8, main_fields); -} -if (this.platform) |platform| { - try writer.writeFieldID(15); - try writer.writeEnum(platform); -} -if (this.serve) |serve| { - try writer.writeFieldID(16); - try writer.writeInt(@intCast(u8, @boolToInt(serve))); -} -if (this.extension_order) |extension_order| { - try writer.writeFieldID(17); - try writer.writeArray([]const u8, extension_order); -} -if (this.public_dir) |public_dir| { - try writer.writeFieldID(18); - try writer.writeValue(public_dir); -} -if (this.only_scan_dependencies) |only_scan_dependencies| { - try writer.writeFieldID(19); - try writer.writeEnum(only_scan_dependencies); -} -if (this.generate_node_module_bundle) |generate_node_module_bundle| { - try writer.writeFieldID(20); - try writer.writeInt(@intCast(u8, @boolToInt(generate_node_module_bundle))); -} -if (this.node_modules_bundle_path) |node_modules_bundle_path| { - try writer.writeFieldID(21); - try writer.writeValue(node_modules_bundle_path); -} -try writer.endMessage(); -} + pub const MessageKind = enum(u32) { + _none, + /// err + err, -}; + /// warn + warn, -pub const FileHandle = struct { -/// path -path: []const u8, + /// note + note, -/// size -size: u32 = 0, + /// debug + debug, -/// fd -fd: u32 = 0, + _, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; -pub fn decode(reader: anytype) anyerror!FileHandle { - var this = std.mem.zeroes(FileHandle); + pub const Location = struct { + /// file + file: []const u8, - this.path = try reader.readValue([]const u8); - this.size = try reader.readValue(u32); - this.fd = try reader.readValue(u32); - return this; -} + /// namespace + namespace: []const u8, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(this.path); - try writer.writeInt(this.size); - try writer.writeInt(this.fd); -} + /// line + line: i32 = 0, -}; + /// column + column: i32 = 0, -pub const Transform = struct { -/// handle -handle: ?FileHandle = null, - -/// path -path: ?[]const u8 = null, - -/// contents -contents: []const u8, - -/// loader -loader: ?Loader = null, - -/// options -options: ?TransformOptions = null, - - -pub fn decode(reader: anytype) anyerror!Transform { - var this = std.mem.zeroes(Transform); - - while(true) { - switch (try reader.readByte()) { - 0 => { return this; }, - - 1 => { - this.handle = try reader.readValue(FileHandle); -}, - 2 => { - this.path = try reader.readValue([]const u8); -}, - 3 => { - this.contents = try reader.readArray(u8); -}, - 4 => { - this.loader = try reader.readValue(Loader); -}, - 5 => { - this.options = try reader.readValue(TransformOptions); -}, - else => { - return error.InvalidMessage; - }, - } - } -unreachable; -} + /// line_text + line_text: []const u8, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { -if (this.handle) |handle| { - try writer.writeFieldID(1); - try writer.writeValue(handle); -} -if (this.path) |path| { - try writer.writeFieldID(2); - try writer.writeValue(path); -} -if (this.contents) |contents| { - try writer.writeFieldID(3); - try writer.writeArray(u8, contents); -} -if (this.loader) |loader| { - try writer.writeFieldID(4); - try writer.writeEnum(loader); -} -if (this.options) |options| { - try writer.writeFieldID(5); - try writer.writeValue(options); -} -try writer.endMessage(); -} + /// suggestion + suggestion: []const u8, -}; + /// offset + offset: u32 = 0, + + pub fn decode(reader: anytype) anyerror!Location { + var this = std.mem.zeroes(Location); + + this.file = try reader.readValue([]const u8); + this.namespace = try reader.readValue([]const u8); + this.line = try reader.readValue(i32); + this.column = try reader.readValue(i32); + this.line_text = try reader.readValue([]const u8); + this.suggestion = try reader.readValue([]const u8); + this.offset = try reader.readValue(u32); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.file); + try writer.writeValue(this.namespace); + try writer.writeInt(this.line); + try writer.writeInt(this.column); + try writer.writeValue(this.line_text); + try writer.writeValue(this.suggestion); + try writer.writeInt(this.offset); + } + }; -pub const TransformResponseStatus = enum(u32) { + pub const MessageData = struct { + /// text + text: ?[]const u8 = null, -_none, - /// success - success, + /// location + location: ?Location = null, - /// fail - fail, + pub fn decode(reader: anytype) anyerror!MessageData { + var this = std.mem.zeroes(MessageData); -_, + while (true) { + switch (try reader.readByte()) { + 0 => { + return this; + }, - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); + 1 => { + this.text = try reader.readValue([]const u8); + }, + 2 => { + this.location = try reader.readValue(Location); + }, + else => { + return error.InvalidMessage; + }, } + } + unreachable; + } - -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + if (this.text) |text| { + try writer.writeFieldID(1); + try writer.writeValue(text); + } + if (this.location) |location| { + try writer.writeFieldID(2); + try writer.writeValue(location); + } + try writer.endMessage(); + } + }; -pub const OutputFile = struct { -/// data -data: []const u8, + pub const Message = struct { + /// kind + kind: MessageKind, -/// path -path: []const u8, + /// data + data: MessageData, + /// notes + notes: []const MessageData, -pub fn decode(reader: anytype) anyerror!OutputFile { - var this = std.mem.zeroes(OutputFile); + pub fn decode(reader: anytype) anyerror!Message { + var this = std.mem.zeroes(Message); - this.data = try reader.readArray(u8); - this.path = try reader.readValue([]const u8); - return this; -} + this.kind = try reader.readValue(MessageKind); + this.data = try reader.readValue(MessageData); + this.notes = try reader.readArray(MessageData); + return this; + } -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeArray(u8, this.data); - try writer.writeValue(this.path); -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeEnum(this.kind); + try writer.writeValue(this.data); + try writer.writeArray(MessageData, this.notes); + } + }; -}; + pub const Log = struct { + /// warnings + warnings: u32 = 0, -pub const TransformResponse = struct { -/// status -status: TransformResponseStatus, + /// errors + errors: u32 = 0, -/// files -files: []const OutputFile, + /// msgs + msgs: []const Message, -/// errors -errors: []const Message, + pub fn decode(reader: anytype) anyerror!Log { + var this = std.mem.zeroes(Log); + this.warnings = try reader.readValue(u32); + this.errors = try reader.readValue(u32); + this.msgs = try reader.readArray(Message); + return this; + } -pub fn decode(reader: anytype) anyerror!TransformResponse { - var this = std.mem.zeroes(TransformResponse); + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.warnings); + try writer.writeInt(this.errors); + try writer.writeArray(Message, this.msgs); + } + }; - this.status = try reader.readValue(TransformResponseStatus); - this.files = try reader.readArray(OutputFile); - this.errors = try reader.readArray(Message); - return this; -} + pub const WebsocketMessageKind = enum(u8) { + _none, + /// welcome + welcome, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeEnum(this.status); - try writer.writeArray(OutputFile, this.files); - try writer.writeArray(Message, this.errors); -} + /// file_change_notification + file_change_notification, -}; + /// build_success + build_success, -pub const MessageKind = enum(u32) { + /// build_fail + build_fail, -_none, - /// err - err, + _, - /// warn - warn, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; - /// note - note, + pub const WebsocketCommandKind = enum(u8) { + _none, + /// build + build, - /// debug - debug, + _, -_, + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; - pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(@tagName(self), opts, o); - } + pub const WebsocketMessage = struct { + /// timestamp + timestamp: u32 = 0, - -}; + /// kind + kind: WebsocketMessageKind, -pub const Location = struct { -/// file -file: []const u8, + pub fn decode(reader: anytype) anyerror!WebsocketMessage { + var this = std.mem.zeroes(WebsocketMessage); -/// namespace -namespace: []const u8, + this.timestamp = try reader.readValue(u32); + this.kind = try reader.readValue(WebsocketMessageKind); + return this; + } -/// line -line: i32 = 0, + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.timestamp); + try writer.writeEnum(this.kind); + } + }; -/// column -column: i32 = 0, + pub const WebsocketMessageWelcome = packed struct { + /// epoch + epoch: u32 = 0, -/// line_text -line_text: []const u8, + pub fn decode(reader: anytype) anyerror!WebsocketMessageWelcome { + var this = std.mem.zeroes(WebsocketMessageWelcome); -/// suggestion -suggestion: []const u8, + this.epoch = try reader.readValue(u32); + return this; + } -/// offset -offset: u32 = 0, + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.epoch); + } + }; + pub const WebsocketMessageFileChangeNotification = struct { + /// id + id: u32 = 0, -pub fn decode(reader: anytype) anyerror!Location { - var this = std.mem.zeroes(Location); + /// loader + loader: Loader, - this.file = try reader.readValue([]const u8); - this.namespace = try reader.readValue([]const u8); - this.line = try reader.readValue(i32); - this.column = try reader.readValue(i32); - this.line_text = try reader.readValue([]const u8); - this.suggestion = try reader.readValue([]const u8); - this.offset = try reader.readValue(u32); - return this; -} + pub fn decode(reader: anytype) anyerror!WebsocketMessageFileChangeNotification { + var this = std.mem.zeroes(WebsocketMessageFileChangeNotification); -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(this.file); - try writer.writeValue(this.namespace); - try writer.writeInt(this.line); - try writer.writeInt(this.column); - try writer.writeValue(this.line_text); - try writer.writeValue(this.suggestion); - try writer.writeInt(this.offset); -} + this.id = try reader.readValue(u32); + this.loader = try reader.readValue(Loader); + return this; + } -}; + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.id); + try writer.writeEnum(this.loader); + } + }; -pub const MessageData = struct { -/// text -text: ?[]const u8 = null, - -/// location -location: ?Location = null, - - -pub fn decode(reader: anytype) anyerror!MessageData { - var this = std.mem.zeroes(MessageData); - - while(true) { - switch (try reader.readByte()) { - 0 => { return this; }, - - 1 => { - this.text = try reader.readValue([]const u8); -}, - 2 => { - this.location = try reader.readValue(Location); -}, - else => { - return error.InvalidMessage; - }, - } - } -unreachable; -} + pub const WebsocketCommand = struct { + /// kind + kind: WebsocketCommandKind, -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { -if (this.text) |text| { - try writer.writeFieldID(1); - try writer.writeValue(text); -} -if (this.location) |location| { - try writer.writeFieldID(2); - try writer.writeValue(location); -} -try writer.endMessage(); -} + /// timestamp + timestamp: u32 = 0, -}; + pub fn decode(reader: anytype) anyerror!WebsocketCommand { + var this = std.mem.zeroes(WebsocketCommand); -pub const Message = struct { -/// kind -kind: MessageKind, + this.kind = try reader.readValue(WebsocketCommandKind); + this.timestamp = try reader.readValue(u32); + return this; + } -/// data -data: MessageData, + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeEnum(this.kind); + try writer.writeInt(this.timestamp); + } + }; -/// notes -notes: []const MessageData, + pub const WebsocketCommandBuild = packed struct { + /// id + id: u32 = 0, + pub fn decode(reader: anytype) anyerror!WebsocketCommandBuild { + var this = std.mem.zeroes(WebsocketCommandBuild); -pub fn decode(reader: anytype) anyerror!Message { - var this = std.mem.zeroes(Message); + this.id = try reader.readValue(u32); + return this; + } - this.kind = try reader.readValue(MessageKind); - this.data = try reader.readValue(MessageData); - this.notes = try reader.readArray(MessageData); - return this; -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.id); + } + }; -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeEnum(this.kind); - try writer.writeValue(this.data); - try writer.writeArray(MessageData, this.notes); -} + pub const WebsocketMessageBuildSuccess = struct { + /// id + id: u32 = 0, -}; + /// from_timestamp + from_timestamp: u32 = 0, -pub const Log = struct { -/// warnings -warnings: u32 = 0, + /// loader + loader: Loader, -/// errors -errors: u32 = 0, + /// module_path + module_path: []const u8, -/// msgs -msgs: []const Message, + /// log + log: Log, + /// bytes + bytes: []const u8, -pub fn decode(reader: anytype) anyerror!Log { - var this = std.mem.zeroes(Log); + pub fn decode(reader: anytype) anyerror!WebsocketMessageBuildSuccess { + var this = std.mem.zeroes(WebsocketMessageBuildSuccess); - this.warnings = try reader.readValue(u32); - this.errors = try reader.readValue(u32); - this.msgs = try reader.readArray(Message); - return this; -} + this.id = try reader.readValue(u32); + this.from_timestamp = try reader.readValue(u32); + this.loader = try reader.readValue(Loader); + this.module_path = try reader.readValue([]const u8); + this.log = try reader.readValue(Log); + this.bytes = try reader.readArray(u8); + return this; + } -pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeInt(this.warnings); - try writer.writeInt(this.errors); - try writer.writeArray(Message, this.msgs); -} + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.id); + try writer.writeInt(this.from_timestamp); + try writer.writeEnum(this.loader); + try writer.writeValue(this.module_path); + try writer.writeValue(this.log); + try writer.writeArray(u8, this.bytes); + } + }; -}; + pub const WebsocketMessageBuildFailure = struct { + /// id + id: u32 = 0, + /// from_timestamp + from_timestamp: u32 = 0, -}; + /// loader + loader: Loader, + + /// module_path + module_path: []const u8, + + /// log + log: Log, + pub fn decode(reader: anytype) anyerror!WebsocketMessageBuildFailure { + var this = std.mem.zeroes(WebsocketMessageBuildFailure); + + this.id = try reader.readValue(u32); + this.from_timestamp = try reader.readValue(u32); + this.loader = try reader.readValue(Loader); + this.module_path = try reader.readValue([]const u8); + this.log = try reader.readValue(Log); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.id); + try writer.writeInt(this.from_timestamp); + try writer.writeEnum(this.loader); + try writer.writeValue(this.module_path); + try writer.writeValue(this.log); + } + }; +}; const ExamplePackedStruct = packed struct { len: u32 = 0, diff --git a/src/bundler.zig b/src/bundler.zig index 3c1f5155e..cbdb5586a 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1091,6 +1091,7 @@ pub fn NewBundler(cache_files: bool) type { opts.enable_bundling = bundler.options.node_modules_bundle != null; opts.transform_require_to_import = true; opts.can_import_from_bundle = bundler.options.node_modules_bundle != null; + opts.features.hot_module_reloading = bundler.options.hot_module_reloading; const value = (bundler.resolver.caches.js.parse(allocator, opts, bundler.options.define, bundler.log, &source) catch null) orelse return null; return ParseResult{ .ast = value, diff --git a/src/js_parser/imports.zig b/src/js_parser/imports.zig index 9ab6a9cb2..e276d6260 100644 --- a/src/js_parser/imports.zig +++ b/src/js_parser/imports.zig @@ -7,7 +7,9 @@ pub const options = @import("../options.zig"); pub const alloc = @import("../alloc.zig"); pub const js_printer = @import("../js_printer.zig"); pub const renamer = @import("../renamer.zig"); -pub const RuntimeImports = @import("../runtime.zig").Runtime.Imports; +const _runtime = @import("../runtime.zig"); +pub const RuntimeImports = _runtime.Runtime.Imports; +pub const RuntimeFeatures = _runtime.Runtime.Features; pub const fs = @import("../fs.zig"); const _hash_map = @import("../hash_map.zig"); pub usingnamespace @import("../global.zig"); @@ -23,6 +25,7 @@ pub const ExprNodeIndex = js_ast.ExprNodeIndex; pub const ExprNodeList = js_ast.ExprNodeList; pub const StmtNodeList = js_ast.StmtNodeList; pub const BindingNodeList = js_ast.BindingNodeList; + pub const assert = std.debug.assert; pub const LocRef = js_ast.LocRef; diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 23e9cfad0..4033cdd8d 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -1552,6 +1552,8 @@ pub const Parser = struct { use_define_for_class_fields: bool = false, suppress_warnings_about_weird_code: bool = true, + features: RuntimeFeatures = RuntimeFeatures{}, + // Used when bundling node_modules enable_bundling: bool = false, transform_require_to_import: bool = true, @@ -1889,7 +1891,7 @@ pub const Parser = struct { jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls }, loc); - after.append(js_ast.Part{ + before.append(js_ast.Part{ .stmts = jsx_part_stmts, .declared_symbols = declared_symbols, .import_record_indices = import_records, @@ -1947,7 +1949,7 @@ pub const Parser = struct { }; } - after.append(js_ast.Part{ + before.append(js_ast.Part{ .stmts = p.cjs_import_stmts.items, .declared_symbols = declared_symbols, .import_record_indices = import_records, @@ -4419,6 +4421,41 @@ pub fn NewParser( }, loc); } + // For HMR, we must convert syntax like this: + // export function leftPad() { + // export const guy = GUY_FIERI_ASCII_ART; + // export class Bacon {} + // export default GuyFieriAsciiArt; + // export {Bacon}; + // export {Bacon as default}; + // to: + // var __hmr__module = new __hmr_HMRModule(file_id, import.meta); + // (__hmr__module._load = function() { + // __hmr__module.exports.leftPad = function () {}; + // __hmr__module.exports.npmProgressBar33 = true; + // __hmr__module.exports.Bacon = class {}; + // })(); + // export { __hmr__module.exports.leftPad as leftPad, __hmr__module.exports.npmProgressBar33 as npmProgressBar33, __hmr__module } + // + // + // + // At bottom of the file: + // - + // var __hmr__exports = new HMRModule({ + // leftPad: () => leftPad, + // npmProgressBar33 () => npmProgressBar33, + // default: () => GuyFieriAsciiArt, + // [__hmr_ModuleIDSymbol]: + //}); + // export { __hmr__exports.leftPad as leftPad, __hmr__ } + // - + // Then: + // if () { + // + // } + + // pub fn maybeRewriteExportSymbol(p: *P, ) + pub fn parseStmt(p: *P, opts: *ParseStatementOptions) anyerror!Stmt { var loc = p.lexer.loc(); diff --git a/src/linker.zig b/src/linker.zig index 15b2e82e6..71f954774 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -301,12 +301,12 @@ pub fn NewLinker(comptime BundlerType: type) type { // Change the import order so that any bundled imports appear last // This is to make it so the bundle (which should be quite large) is least likely to block rendering - if (needs_bundle) { - const sorter = ImportStatementSorter{ .import_records = result.ast.import_records }; - for (result.ast.parts) |*part, i| { - std.sort.sort(js_ast.Stmt, part.stmts, sorter, ImportStatementSorter.lessThan); - } - } + // if (needs_bundle) { + // const sorter = ImportStatementSorter{ .import_records = result.ast.import_records }; + // for (result.ast.parts) |*part, i| { + // std.sort.sort(js_ast.Stmt, part.stmts, sorter, ImportStatementSorter.lessThan); + // } + // } } const ImportPathsList = allocators.BSSStringList(512, 128); diff --git a/src/options.zig b/src/options.zig index 56c72ff1a..4b45acdf2 100644 --- a/src/options.zig +++ b/src/options.zig @@ -540,7 +540,9 @@ pub const BundleOptions = struct { loaders: std.StringHashMap(Loader), resolve_dir: string = "/", jsx: JSX.Pragma = JSX.Pragma{}, + react_fast_refresh: bool = false, + hot_module_reloading: bool = false, inject: ?[]string = null, public_url: string = "", public_dir: string = "public", @@ -685,6 +687,7 @@ pub const BundleOptions = struct { if (isWindows and opts.public_dir_handle != null) { opts.public_dir_handle.?.close(); } + opts.hot_module_reloading = true; } if (opts.write and opts.output_dir.len > 0) { diff --git a/src/runtime.zig b/src/runtime.zig index f74ed8067..f6a15da98 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -2,12 +2,12 @@ const options = @import("./options.zig"); usingnamespace @import("ast/base.zig"); usingnamespace @import("global.zig"); const std = @import("std"); -pub const ProdSourceContent = @embedFile("./runtime.js"); +pub const ProdSourceContent = @embedFile("./runtime.out.js"); pub const Runtime = struct { pub fn sourceContent() string { if (isDebug) { - var runtime_path = std.fs.path.join(std.heap.c_allocator, &[_]string{ std.fs.path.dirname(@src().file).?, "runtime.js" }) catch unreachable; + var runtime_path = std.fs.path.join(std.heap.c_allocator, &[_]string{ std.fs.path.dirname(@src().file).?, "runtime.out.js" }) catch unreachable; const file = std.fs.openFileAbsolute(runtime_path, .{}) catch unreachable; defer file.close(); return file.readToEndAlloc(std.heap.c_allocator, (file.stat() catch unreachable).size) catch unreachable; @@ -19,7 +19,8 @@ pub const Runtime = struct { pub fn version() string { return version_hash; } - pub const Features = packed struct { + + pub const Features = struct { react_fast_refresh: bool = false, hot_module_reloading: bool = false, keep_names_for_arrow_functions: bool = true, diff --git a/src/runtime/hmr.ts b/src/runtime/hmr.ts new file mode 100644 index 000000000..f6d540b5a --- /dev/null +++ b/src/runtime/hmr.ts @@ -0,0 +1,391 @@ +import { ByteBuffer } from "peechy/bb"; +import * as Schema from "../api/schema"; + +var runOnce = false; +var clientStartTime = 0; + +function formatDuration(duration: number) { + return Math.round(duration * 100000) / 100; +} + +export class Client { + socket: WebSocket; + hasWelcomed: boolean = false; + reconnect: number = 0; + // Server timestamps are relative to the time the server's HTTP server launched + // This so we can send timestamps as uint32 instead of 128-bit integers + epoch: number = 0; + + start() { + if (runOnce) { + console.warn( + "[speedy] Attempted to start HMR client multiple times. This may be a bug." + ); + return; + } + + runOnce = true; + this.connect(); + } + + connect() { + clientStartTime = performance.now(); + + this.socket = new WebSocket("/_api", ["speedy-hmr"]); + this.socket.binaryType = "arraybuffer"; + this.socket.onclose = this.handleClose; + this.socket.onopen = this.handleOpen; + this.socket.onmessage = this.handleMessage; + } + + // key: module id + // value: server-timestamp + builds = new Map<number, number>(); + + indexOfModuleId(id: number): number { + return Module.dependencies.graph.indexOf(id); + } + + handleBuildFailure(buffer: ByteBuffer, timestamp: number) { + // 0: ID + // 1: Timestamp + const header_data = new Uint32Array( + buffer._data.buffer, + buffer._data.byteOffset, + buffer._data.byteOffset + 8 + ); + const index = this.indexOfModuleId(header_data[0]); + // Ignore build failures of modules that are not loaded + if (index === -1) { + return; + } + + // Build failed for a module we didn't request? + const minTimestamp = this.builds.get(index); + if (!minTimestamp) { + return; + } + const fail = Schema.decodeWebsocketMessageBuildFailure(buffer); + // TODO: finish this. + console.error("[speedy] Build failed", fail.module_path); + } + + verbose = process.env.SPEEDY_HMR_VERBOSE; + + handleBuildSuccess(buffer: ByteBuffer, timestamp: number) { + // 0: ID + // 1: Timestamp + const header_data = new Uint32Array( + buffer._data.buffer, + buffer._data.byteOffset, + buffer._data.byteOffset + 8 + ); + const index = this.indexOfModuleId(header_data[0]); + // Ignore builds of modules that are not loaded + if (index === -1) { + if (this.verbose) { + console.debug( + `[speedy] Skipping reload for unknown module id:`, + header_data[0] + ); + } + + return; + } + + // Ignore builds of modules we expect a later version of + const currentVersion = this.builds.get(header_data[0]) || -Infinity; + if (currentVersion > header_data[1]) { + if (this.verbose) { + console.debug( + `[speedy] Ignoring module update for "${Module.dependencies.modules[index].url.pathname}" due to timestamp mismatch.\n Expected: >=`, + currentVersion, + `\n Received:`, + header_data[1] + ); + } + return; + } + + if (this.verbose) { + console.debug( + "[speedy] Preparing to reload", + Module.dependencies.modules[index].url.pathname + ); + } + + const build = Schema.decodeWebsocketMessageBuildSuccess(buffer); + var reload = new HotReload(header_data[0], index, build); + reload.timings.notify = timestamp - build.from_timestamp; + reload.run().then( + ([module, timings]) => { + console.log( + `[speedy] Reloaded in ${formatDuration(timings.total)}ms :`, + module.url.pathname + ); + }, + (err) => { + console.error("[speedy] Hot Module Reload failed!", err); + debugger; + } + ); + } + + handleFileChangeNotification(buffer: ByteBuffer, timestamp: number) { + const notification = + Schema.decodeWebsocketMessageFileChangeNotification(buffer); + const index = Module.dependencies.graph.indexOf(notification.id); + + if (index === -1) { + if (this.verbose) { + console.debug("[speedy] Unknown module changed, skipping"); + } + return; + } + + if ((this.builds.get(notification.id) || -Infinity) > timestamp) { + console.debug( + `[speedy] Received update for ${Module.dependencies.modules[index].url.pathname}` + ); + return; + } + + if (this.verbose) { + console.debug( + `[speedy] Requesting update for ${Module.dependencies.modules[index].url.pathname}` + ); + } + + this.builds.set(notification.id, timestamp); + this.buildCommandBuf[0] = Schema.WebsocketCommandKind.build; + this.buildCommandUArray[0] = timestamp; + this.buildCommandBuf.set(new Uint8Array(this.buildCommandUArray), 1); + this.buildCommandUArray[0] = notification.id; + this.buildCommandBuf.set(new Uint8Array(this.buildCommandUArray), 5); + this.socket.send(this.buildCommandBuf); + } + buildCommandBuf = new Uint8Array(9); + buildCommandUArray = new Uint32Array(1); + + handleOpen = (event: Event) => { + globalThis.clearInterval(this.reconnect); + this.reconnect = 0; + }; + + handleMessage = (event: MessageEvent) => { + const data = new Uint8Array(event.data); + const message_header_byte_buffer = new ByteBuffer(data); + const header = Schema.decodeWebsocketMessage(message_header_byte_buffer); + const buffer = new ByteBuffer( + data.subarray(message_header_byte_buffer._index) + ); + + switch (header.kind) { + case Schema.WebsocketMessageKind.build_fail: { + this.handleBuildFailure(buffer, header.timestamp); + break; + } + case Schema.WebsocketMessageKind.build_success: { + this.handleBuildSuccess(buffer, header.timestamp); + break; + } + case Schema.WebsocketMessageKind.file_change_notification: { + this.handleFileChangeNotification(buffer, header.timestamp); + break; + } + case Schema.WebsocketMessageKind.welcome: { + const now = performance.now(); + console.log( + "[speedy] HMR connected in", + formatDuration(now - clientStartTime), + "ms" + ); + clientStartTime = now; + this.hasWelcomed = true; + const welcome = Schema.decodeWebsocketMessageWelcome(buffer); + this.epoch = welcome.epoch; + if (!this.epoch) { + console.warn("[speedy] Internal HMR error"); + } + break; + } + } + }; + + handleClose = (event: CloseEvent) => { + if (this.reconnect !== 0) { + return; + } + + this.reconnect = setInterval(this.connect, 500) as any as number; + console.warn("[speedy] HMR disconnected. Attempting to reconnect."); + }; +} + +class HotReload { + module_id: number = 0; + module_index: number = 0; + build: Schema.WebsocketMessageBuildSuccess; + timings = { + notify: 0, + decode: 0, + import: 0, + callbacks: 0, + total: 0, + start: 0, + }; + + constructor( + module_id: HotReload["module_id"], + module_index: HotReload["module_index"], + build: HotReload["build"] + ) { + this.module_id = module_id; + this.module_index = module_index; + this.build = build; + } + + async run(): Promise<[Module, HotReload["timings"]]> { + const importStart = performance.now(); + let orig_deps = Module.dependencies; + Module.dependencies = orig_deps.fork(this.module_index); + var blobURL = null; + try { + const blob = new Blob([this.build.bytes], { type: "text/javascript" }); + blobURL = URL.createObjectURL(blob); + await import(blobURL); + this.timings.import = performance.now() - importStart; + } catch (exception) { + Module.dependencies = orig_deps; + URL.revokeObjectURL(blobURL); + throw exception; + } + + URL.revokeObjectURL(blobURL); + + if (process.env.SPEEDY_HMR_VERBOSE) { + console.debug( + "[speedy] Re-imported", + Module.dependencies.modules[this.module_index].url.pathname, + "in", + formatDuration(this.timings.import), + ". Running callbacks" + ); + } + + const callbacksStart = performance.now(); + try { + // ES Modules delay execution until all imports are parsed + // They execute depth-first + // If you load N modules and append each module ID to the array, 0 is the *last* module imported. + // modules.length - 1 is the first. + // Therefore, to reload all the modules in the correct order, we traverse the graph backwards + // This only works when the graph is up to date. + // If the import order changes, we need to regenerate the entire graph + // Which sounds expensive, until you realize that we are mostly talking about an array that will be typically less than 1024 elements + // Computers can do that in < 1ms easy! + for (let i = Module.dependencies.graph_used; i > this.module_index; i--) { + let handled = !Module.dependencies.modules[i].exports.__hmrDisable; + if (typeof Module.dependencies.modules[i].dispose === "function") { + Module.dependencies.modules[i].dispose(); + handled = true; + } + if (typeof Module.dependencies.modules[i].accept === "function") { + Module.dependencies.modules[i].accept(); + handled = true; + } + if (!handled) { + Module.dependencies.modules[i]._load(); + } + } + } catch (exception) { + Module.dependencies = orig_deps; + throw exception; + } + this.timings.callbacks = performance.now() - callbacksStart; + + if (process.env.SPEEDY_HMR_VERBOSE) { + console.debug( + "[speedy] Ran callbacks", + Module.dependencies.modules[this.module_index].url.pathname, + "in", + formatDuration(this.timings.callbacks), + "ms" + ); + } + + orig_deps = null; + this.timings.total = + this.timings.import + this.timings.callbacks + this.build.from_timestamp; + return Promise.resolve([ + Module.dependencies.modules[this.module_index], + this.timings, + ]); + } +} +var client: Client; +if ("SPEEDY_HMR_CLIENT" in globalThis) { + console.warn( + "[speedy] Attempted to load multiple copies of HMR. This may be a bug." + ); +} else if (process.env.SPEEDY_HMR_ENABLED) { + client = new Client(); + client.start(); + globalThis.SPEEDY_HMR_CLIENT = client; +} + +export class Module { + constructor(id: number, url: URL) { + // Ensure V8 knows this is a U32 + this.id = id | 0; + this.url = url; + + if (!Module._dependencies) { + Module.dependencies = Module._dependencies; + } + + this.graph_index = Module.dependencies.graph_used++; + + // Grow the dependencies graph + if (Module.dependencies.graph.length <= this.graph_index) { + const new_graph = new Uint32Array(Module.dependencies.graph.length * 4); + new_graph.set(Module.dependencies.graph); + Module.dependencies.graph = new_graph; + + // In-place grow. This creates a holey array, which is bad, but less bad than pushing potentially 1000 times + Module.dependencies.modules.length = new_graph.length; + } + + Module.dependencies.modules[this.graph_index] = this; + Module.dependencies.graph[this.graph_index] = this.id | 0; + } + additional_files = []; + + // When a module updates, we need to re-initialize each dependent, recursively + // To do so: + // 1. Track which modules are imported by which *at runtime* + // 2. When A updates, loop through each dependent of A in insertion order + // 3. For each old dependent, call .dispose() if exists + // 3. For each new dependent, call .accept() if exists + // 4. + static _dependencies = { + modules: new Array<Module>(32), + graph: new Uint32Array(32), + graph_used: 0, + + fork(offset: number) { + return { + modules: Module._dependencies.modules.slice(), + graph: Module._dependencies.graph.slice(), + graph_used: offset - 1, + }; + }, + }; + static dependencies: Module["_dependencies"]; + url: URL; + _load = function () {}; + id = 0; + graph_index = 0; + _exports = {}; + exports = {}; +} diff --git a/src/runtime/index.ts b/src/runtime/index.ts new file mode 100644 index 000000000..873666412 --- /dev/null +++ b/src/runtime/index.ts @@ -0,0 +1,2 @@ +export * from "./hmr"; +export * from "../runtime.js"; |