mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
77
src/api/schema.d.ts
vendored
77
src/api/schema.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -1235,6 +1235,266 @@ function encodeLog(message, bb) {
|
||||
throw new Error("Missing required field \"msgs\"");
|
||||
}
|
||||
|
||||
}
|
||||
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 }
|
||||
@@ -1290,4 +1550,22 @@ export { encodeMessageData }
|
||||
export { decodeMessage }
|
||||
export { encodeMessage }
|
||||
export { decodeLog }
|
||||
export { encodeLog }
|
||||
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 }
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
2169
src/api/schema.zig
2169
src/api/schema.zig
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
391
src/runtime/hmr.ts
Normal file
391
src/runtime/hmr.ts
Normal file
@@ -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 = {};
|
||||
}
|
||||
2
src/runtime/index.ts
Normal file
2
src/runtime/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./hmr";
|
||||
export * from "../runtime.js";
|
||||
Reference in New Issue
Block a user