Files
bun.sh/src/javascript/jsc/javascript.zig
Jarred Sumner fea9faaf4c alright
Former-commit-id: ab73c7b323
2021-07-13 10:32:57 -07:00

1687 lines
63 KiB
Zig

const std = @import("std");
const Fs = @import("../../fs.zig");
const resolver = @import("../../resolver/resolver.zig");
const ast = @import("../../import_record.zig");
const NodeModuleBundle = @import("../../node_module_bundle.zig").NodeModuleBundle;
const logger = @import("../../logger.zig");
const Api = @import("../../api/schema.zig").Api;
const options = @import("../../options.zig");
const Bundler = @import("../../bundler.zig").ServeBundler;
const js_printer = @import("../../js_printer.zig");
const hash_map = @import("../../hash_map.zig");
const http = @import("../../http.zig");
usingnamespace @import("./node_env_buf_map.zig");
usingnamespace @import("./base.zig");
usingnamespace @import("./webcore/response.zig");
const DefaultSpeedyDefines = struct {
pub const Keys = struct {
const window = "window";
};
pub const Values = struct {
const window = "undefined";
};
};
pub fn configureTransformOptionsForSpeedy(allocator: *std.mem.Allocator, _args: Api.TransformOptions) !Api.TransformOptions {
var args = _args;
args.platform = Api.Platform.speedy;
args.serve = false;
args.write = false;
args.resolve = Api.ResolveMode.lazy;
args.generate_node_module_bundle = false;
// We inline process.env.* at bundle time but process.env is a proxy object which will otherwise return undefined.
var env_map = try getNodeEnvMap(allocator);
var env_count = env_map.count();
if (args.define) |def| {
for (def.keys) |key| {
env_count += @boolToInt((env_map.get(key) == null));
}
}
var needs_node_env = env_map.get("NODE_ENV") == null;
var needs_window_undefined = true;
var needs_regenerate = args.define == null and env_count > 0;
if (args.define) |def| {
if (def.keys.len != env_count) {
needs_regenerate = true;
}
for (def.keys) |key| {
if (strings.eql(key, "process.env.NODE_ENV")) {
needs_node_env = false;
} else if (strings.eql(key, "window")) {
needs_window_undefined = false;
}
}
}
var extras_count = @intCast(usize, @boolToInt(needs_node_env)) + @intCast(usize, @boolToInt(needs_window_undefined));
if (needs_regenerate) {
var new_list = try allocator.alloc([]const u8, env_count * 2 + extras_count * 2);
var keys = new_list[0 .. new_list.len / 2];
var values = new_list[keys.len..];
var new_map = Api.StringMap{
.keys = keys,
.values = values,
};
var iter = env_map.iterator();
var last: usize = 0;
while (iter.next()) |entry| {
keys[last] = entry.key_ptr.*;
var value = entry.value_ptr.*;
if (value.len == 0 or value[0] != '"' or value[value.len - 1] != '"') {
value = try std.fmt.allocPrint(allocator, "\"{s}\"", .{value});
}
values[last] = value;
last += 1;
}
if (args.define) |def| {
var from_env = keys[0..last];
for (def.keys) |pre, i| {
if (env_map.get(pre) != null) {
for (from_env) |key, j| {
if (strings.eql(key, pre)) {
values[j] = def.values[i];
}
}
} else {
keys[last] = pre;
values[last] = def.values[i];
last += 1;
}
}
}
if (needs_node_env) {
keys[last] = options.DefaultUserDefines.NodeEnv.Key;
values[last] = options.DefaultUserDefines.NodeEnv.Value;
last += 1;
}
if (needs_window_undefined) {
keys[last] = DefaultSpeedyDefines.Keys.window;
values[last] = DefaultSpeedyDefines.Values.window;
last += 1;
}
args.define = new_map;
}
return args;
}
// If you read JavascriptCore/API/JSVirtualMachine.mm - https://github.com/WebKit/WebKit/blob/acff93fb303baa670c055cb24c2bad08691a01a0/Source/JavaScriptCore/API/JSVirtualMachine.mm#L101
// We can see that it's sort of like std.mem.Allocator but for JSGlobalContextRef, to support Automatic Reference Counting
// Its unavailable on Linux
pub const VirtualMachine = struct {
const RequireCacheType = std.AutoHashMap(u32, *Module);
// root: js.JSGlobalContextRef,
ctx: js.JSGlobalContextRef = undefined,
group: js.JSContextGroupRef,
allocator: *std.mem.Allocator,
require_cache: RequireCacheType,
node_module_list: ?*Module.NodeModuleList,
node_modules: ?*NodeModuleBundle = null,
node_modules_ref: js.JSObjectRef = null,
global: *GlobalObject,
bundler: Bundler,
log: *logger.Log,
watcher: ?*http.Watcher = null,
event_listeners: EventListenerMixin.Map,
pub threadlocal var instance: *VirtualMachine = undefined;
pub fn deinit(vm: *VirtualMachine) void {
js.JSGlobalContextRelease(vm.ctx);
js.JSContextGroupRelease(vm.group);
}
pub fn init(
allocator: *std.mem.Allocator,
_args: Api.TransformOptions,
existing_bundle: ?*NodeModuleBundle,
_log: ?*logger.Log,
) !*VirtualMachine {
var group = js.JSContextGroupRetain(js.JSContextGroupCreate());
var log: *logger.Log = undefined;
if (_log) |__log| {
log = __log;
} else {
log = try allocator.create(logger.Log);
}
var vm = try allocator.create(VirtualMachine);
var global = try allocator.create(GlobalObject);
vm.* = .{
.allocator = allocator,
.bundler = try Bundler.init(
allocator,
log,
try configureTransformOptionsForSpeedy(allocator, _args),
existing_bundle,
),
.node_module_list = null,
.log = log,
.group = group,
.event_listeners = EventListenerMixin.Map.init(allocator),
.require_cache = RequireCacheType.init(allocator),
.global = global,
};
vm.bundler.configureLinker();
global.* = GlobalObject{ .vm = vm };
try vm.global.boot();
vm.ctx = vm.global.ctx;
Module.boot(vm);
Properties.init();
if (vm.bundler.options.node_modules_bundle) |bundle| {
vm.node_modules = bundle;
vm.node_module_list = try allocator.create(Module.NodeModuleList);
try Module.NodeModuleList.create(vm, bundle, vm.node_module_list.?);
vm.global.ctx = vm.node_module_list.?.bundle_ctx;
}
return vm;
}
};
pub const Object = struct {
ref: js.jsObjectRef,
};
pub const String = struct {
ref: js.JSStringRef,
len: usize,
pub fn chars(this: *const String) []const js.JSChar {
return js.JSStringGetCharactersPtr(this.ref)[0..js.JSStringGetLength(this.ref)];
}
pub fn eql(this: *const String, str: [*c]const u8) bool {
return str.len == this.len and js.JSStringIsEqualToUTF8CString(this, str);
}
};
const GetterFn = fn (
this: anytype,
ctx: js.JSContextRef,
thisObject: js.JSValueRef,
prop: js.JSStringRef,
exception: js.ExceptionRef,
) js.JSValueRef;
const SetterFn = fn (
this: anytype,
ctx: js.JSContextRef,
thisObject: js.JSValueRef,
prop: js.JSStringRef,
value: js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef;
const JSProp = struct {
get: ?GetterFn = null,
set: ?SetterFn = null,
ro: bool = false,
};
pub const Module = struct {
path: Fs.Path,
ref: js.JSObjectRef,
id: js.JSValueRef = null,
exports: js.JSValueRef = null,
vm: *VirtualMachine,
require_func: js.JSObjectRef = null,
loaded: bool = false,
exports_function: js.JSValueRef = null,
// When the Watcher detects the source file changed, we bust the require cache
// However, we want to lazily bust the require cache.
// We don't want to actually reload the references until the code is next executed
// reload_pending should not be applied to bundled modules
reload_pending: bool = false,
pub var module_class: js.JSClassRef = undefined;
pub var module_global_class: js.JSClassRef = undefined;
pub var module_global_class_def: js.JSClassDefinition = undefined;
pub var module_class_def: js.JSClassDefinition = undefined;
pub const NodeModuleList = struct {
tempbuf: []u8,
property_names: [*]u8,
module_property_map: []u64,
static_functions: [1]js.JSStaticFunction,
property_getters: []js.JSObjectRef,
node_module_global_class: js.JSClassRef,
node_module_global_class_def: js.JSClassDefinition,
vm: *VirtualMachine,
// This is probably a mistake.
bundle_ctx: js.JSGlobalContextRef,
require_cache: []?*Module,
exports_function_call: js.JSObjectRef = null,
console: js.JSObjectRef = null,
const RequireBundleClassName = "requireFromBundle";
var require_bundle_class_def: js.JSClassDefinition = undefined;
var require_bundle_class_ref: js.JSClassRef = undefined;
var require_bundle_class_loaded = false;
pub fn loadBundledModuleById(node_module_list: *NodeModuleList, id: u32, call_ctx: js.JSContextRef) !*Module {
if (node_module_list.require_cache[id]) |mod| {
return mod;
}
var module = try node_module_list.vm.allocator.create(Module);
node_module_list.require_cache[id] = module;
errdefer node_module_list.vm.allocator.destroy(module);
try Module.NodeModuleList.Instance.evalBundledModule(
module,
node_module_list.vm.allocator,
node_module_list.vm,
node_module_list,
id,
call_ctx,
);
return module;
}
pub const Instance = struct {
module: Module,
node_module_list: *NodeModuleList,
threadlocal var source_code_buffer: MutableString = undefined;
threadlocal var source_code_buffer_loaded = false;
pub fn evalBundledModule(
module: *Module,
allocator: *std.mem.Allocator,
vm: *VirtualMachine,
node_module_list: *NodeModuleList,
id: u32,
call_ctx: js.JSContextRef,
) !void {
const bundled_module = &vm.node_modules.?.bundle.modules[id];
const total_length = bundled_module.code.length + 1;
if (!source_code_buffer_loaded) {
source_code_buffer = try MutableString.init(allocator, total_length);
source_code_buffer_loaded = true;
} else {
source_code_buffer.reset();
source_code_buffer.growIfNeeded(total_length) catch {};
}
source_code_buffer.list.resize(allocator, total_length) catch unreachable;
var node_module_file = std.fs.File{ .handle = vm.node_modules.?.fd };
const read = try node_module_file.pread(source_code_buffer.list.items, bundled_module.code.offset);
var buf = source_code_buffer.list.items[0..read];
const bundled_package = &vm.node_modules.?.bundle.packages[bundled_module.package_id];
// We want linear because we expect it to virtually always be at 0
// However, out of caution we check.
var start_at: usize = std.mem.indexOfPosLinear(u8, buf, 0, "export var $") orelse return error.FailedCorruptNodeModuleMissingExport;
start_at += "export var $".len;
// export var $fooo = $$m("packageName", "id", (module, exports) => {
// ^
start_at = std.mem.indexOfPosLinear(
u8,
buf,
start_at,
"\",",
) orelse return error.FailedCorruptNodeModuleMissingModuleWrapper;
start_at += 1;
// export var $fooo = $$m("packageName", "id", (module, exports) => {
// ^
start_at = std.mem.indexOfPosLinear(
u8,
buf,
start_at,
"\",",
) orelse return error.FailedCorruptNodeModuleMissingModuleWrapper;
start_at += 1;
start_at = std.mem.indexOfPosLinear(
u8,
buf,
start_at,
"=>",
) orelse return error.FailedCorruptNodeModuleMissingModuleWrapper;
start_at += 2;
// (module, exports) => {
// ^
start_at = std.mem.indexOfPosLinear(
u8,
buf,
start_at,
"{",
) orelse return error.FailedCorruptNodeModuleMissingModuleWrapper;
start_at += 1;
// (module, exports) => {
//
// ^
var curr_buf = buf[start_at..];
curr_buf = curr_buf[0 .. std.mem.lastIndexOfScalar(u8, curr_buf, ';') orelse return error.FailedCorruptNodeModuleMissingModuleWrapper];
curr_buf = curr_buf[0 .. std.mem.lastIndexOfScalar(u8, curr_buf, ')') orelse return error.FailedCorruptNodeModuleMissingModuleWrapper];
curr_buf = curr_buf[0 .. std.mem.lastIndexOfScalar(u8, curr_buf, '}') orelse return error.FailedCorruptNodeModuleMissingModuleWrapper];
curr_buf.ptr[curr_buf.len] = 0;
var source_buf = curr_buf.ptr[0..curr_buf.len :0];
var source_url_buf = try std.fmt.allocPrint(
allocator,
"{s}/{s}",
.{
vm.node_modules.?.str(bundled_package.name),
vm.node_modules.?.str(bundled_module.path),
},
);
errdefer allocator.free(source_url_buf);
var exception: js.JSValueRef = null;
try Module.load(
module,
vm,
allocator,
vm.log,
source_buf,
Fs.Path.initWithPretty(source_url_buf, source_url_buf),
node_module_list.bundle_ctx,
call_ctx,
call_ctx,
&exception,
false,
);
}
};
pub const RequireBundledModule = struct {
id: u32,
list: *NodeModuleList,
};
// key: hash of module.path
// value: index of module
const ModuleIDMap = hash_map.AutoHashMap(u64, u32);
pub fn initializeGlobal(ctx: JSContextRef, obj: JSObjectRef) callconv(.C) void {}
pub fn getRequireFromBundleProperty(
ctx: js.JSContextRef,
thisObject: js.JSObjectRef,
prop: js.JSStringRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSValueRef {
var thisPtr = js.JSObjectGetPrivate(thisObject);
if (thisPtr == null) return null;
var this = @ptrCast(
*NodeModuleList,
@alignCast(
@alignOf(
*NodeModuleList,
),
thisPtr.?,
),
);
std.mem.set(u8, this.tempbuf, 0);
const size = js.JSStringGetUTF8CString(prop, this.tempbuf.ptr, this.tempbuf.len);
const key = std.hash.Wyhash.hash(0, this.tempbuf);
const id = @intCast(u32, std.mem.indexOfScalar(u64, this.module_property_map, key) orelse return null);
if (this.property_getters[id] == null) {
if (!require_bundle_class_loaded) {
require_bundle_class_def = js.kJSClassDefinitionEmpty;
require_bundle_class_def.className = RequireBundleClassName[0.. :0];
require_bundle_class_def.callAsFunction = To.JS.Callback(RequireBundledModule, requireBundledModule).rfn;
require_bundle_class_ref = js.JSClassRetain(js.JSClassCreate(&require_bundle_class_def));
require_bundle_class_loaded = true;
}
// TODO: remove this allocation by ptr casting
var require_from_bundle = this.vm.allocator.create(RequireBundledModule) catch unreachable;
require_from_bundle.* = RequireBundledModule{
.list = this,
.id = id,
};
this.property_getters[id] = js.JSObjectMake(this.bundle_ctx, require_bundle_class_ref, require_from_bundle);
js.JSValueProtect(this.bundle_ctx, this.property_getters[id]);
}
return this.property_getters[id];
}
// this is what $aosdi123() inside a node_modules.jsb calls
pub fn requireBundledModule(
obj: *RequireBundledModule,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
const bundle = &obj.list.vm.node_modules.?.bundle;
const bundled_module = &bundle.modules[obj.id];
const bundled_pkg = &bundle.packages[bundled_module.package_id];
var module = loadBundledModuleById(obj.list, obj.id, obj.list.bundle_ctx) catch |err| {
Output.prettyErrorln("<r><red>RequireError<r>: <b>{s}<r> in \"<cyan>{s}/{s}<r>\"", .{
@errorName(err),
obj.list.vm.node_modules.?.str(bundled_pkg.name),
obj.list.vm.node_modules.?.str(bundled_module.path),
});
var message = std.fmt.allocPrintZ(obj.list.vm.allocator, "RequireError: {s} in \"{s}/{s}\"", .{
@errorName(err),
obj.list.vm.node_modules.?.str(bundled_pkg.name),
obj.list.vm.node_modules.?.str(bundled_module.path),
}) catch unreachable;
defer Output.flush();
defer obj.list.vm.allocator.free(message);
var args = obj.list.vm.allocator.alloc(js.JSStringRef, 1) catch unreachable;
args[0] = js.JSStringCreateWithUTF8CString(message.ptr);
exception.* = js.JSObjectMakeError(ctx, 1, args.ptr, null);
return js.JSValueMakeUndefined(ctx);
};
return module.internalGetExports(js.JSContextGetGlobalContext(ctx));
}
pub fn getConsole(
ctx: js.JSContextRef,
thisObject: js.JSObjectRef,
prop: js.JSStringRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSValueRef {
var this = @ptrCast(
*NodeModuleList,
@alignCast(@alignOf(*NodeModuleList), js.JSObjectGetPrivate(thisObject) orelse return null),
);
if (this.console == null) {
this.console = js.JSObjectMake(js.JSContextGetGlobalContext(ctx), this.vm.global.console_class, this.vm.global);
}
return this.console;
}
pub fn create(vm: *VirtualMachine, bundle: *const NodeModuleBundle, node_module_list: *NodeModuleList) !void {
var size: usize = 0;
var longest_size: usize = 0;
for (bundle.bundle.modules) |module, i| {
// Add one for null-terminated string offset
const this_size = std.fmt.count(
"${x}" ++ "\\x0",
.{
module.id,
},
);
size += this_size;
longest_size = std.math.max(this_size, longest_size);
}
var static_properties = try vm.allocator.alloc(js.JSStaticValue, bundle.bundle.modules.len + 1 + GlobalObject.GlobalClass.static_value_count);
var copied_static_values = static_properties[bundle.bundle.modules.len..];
copied_static_values = copied_static_values[0 .. copied_static_values.len - 1];
copied_static_values[0] = js.JSStaticValue{
.name = Properties.UTF8.console[0.. :0],
.getProperty = getConsole,
.setProperty = null,
.attributes = .kJSPropertyAttributeNone,
};
copied_static_values[1] = js.JSStaticValue{
.name = Response.Class.definition.className,
.getProperty = Response.Class.RawGetter(NodeModuleList).getter,
.setProperty = null,
.attributes = .kJSPropertyAttributeNone,
};
copied_static_values[2] = js.JSStaticValue{
.name = Headers.Class.definition.className,
.getProperty = Headers.Class.RawGetter(NodeModuleList).getter,
.setProperty = null,
.attributes = .kJSPropertyAttributeNone,
};
copied_static_values[3] = js.JSStaticValue{
.name = Request.Class.definition.className,
.getProperty = Request.Class.RawGetter(NodeModuleList).getter,
.setProperty = null,
.attributes = .kJSPropertyAttributeNone,
};
// copied_static_values must match GlobalObject.GlobalClass.static_value_count
std.debug.assert(copied_static_values.len == 4);
static_properties[static_properties.len - 1] = std.mem.zeroes(js.JSStaticValue);
var utf8 = try vm.allocator.alloc(u8, size + std.math.max(longest_size, 32));
std.mem.set(u8, utf8, 0);
var tempbuf = utf8[size..];
var names_buf = utf8[0..size];
var module_property_map = try vm.allocator.alloc(u64, bundle.bundle.modules.len);
for (bundle.bundle.modules) |module, i| {
var hasher = std.hash.Wyhash.init(0);
const hash = @truncate(
u32,
module.id,
);
// The variable name is the hash of the module path
var name = std.fmt.bufPrint(names_buf, "${x}", .{hash}) catch unreachable;
std.mem.set(u8, tempbuf, 0);
std.mem.copy(u8, tempbuf, name);
name.ptr[name.len] = 0;
// But we don't store that for the hash map. Instead, we store the hash of name.
// This lets us avoid storing pointers to the name in the hash table, so if we free it later
// or something it won't cause issues.
module_property_map[i] = std.hash.Wyhash.hash(0, tempbuf);
static_properties[i] = js.JSStaticValue{
.name = name.ptr,
.getProperty = getRequireFromBundleProperty,
.setProperty = null,
.attributes = .kJSPropertyAttributeReadOnly,
};
names_buf = names_buf[name.len + 1 ..];
}
var node_module_global_class_def = js.kJSClassDefinitionEmpty;
node_module_global_class_def.staticValues = static_properties.ptr;
node_module_global_class_def.className = node_module_global_class_name[0.. :0];
node_module_global_class_def.staticFunctions = GlobalObject.GlobalClass.definition.staticFunctions;
// node_module_global_class_def.parentClass = vm.global.global_class;
var property_getters = try vm.allocator.alloc(js.JSObjectRef, bundle.bundle.modules.len);
std.mem.set(js.JSObjectRef, property_getters, null);
node_module_list.* = NodeModuleList{
.module_property_map = module_property_map,
.node_module_global_class_def = node_module_global_class_def,
.vm = vm,
.tempbuf = tempbuf,
.property_names = utf8.ptr,
.bundle_ctx = undefined,
.property_getters = property_getters,
.node_module_global_class = undefined,
.static_functions = undefined,
.require_cache = try vm.allocator.alloc(?*Module, bundle.bundle.modules.len),
};
std.mem.set(?*Module, node_module_list.require_cache, null);
// node_module_list.staticFunctions[0] = js.JSStaticFunction{
// .name = Properties.UTF8.initialize_bundled_module[0.. :0],
// .callAsFunction = To.JS.Callback(NodeModuleList, initializeNodeModule),
// };
// node_module_global_class_def.staticFunctions = &node_module_list.static_functions;
node_module_list.node_module_global_class_def = node_module_global_class_def;
node_module_list.node_module_global_class = js.JSClassRetain(js.JSClassCreate(&node_module_list.node_module_global_class_def));
node_module_list.bundle_ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(vm.group, node_module_list.node_module_global_class));
_ = js.JSObjectSetPrivate(js.JSContextGetGlobalObject(node_module_list.bundle_ctx), node_module_list);
}
};
pub const node_module_global_class_name = "NodeModuleGlobal";
threadlocal var require_buf: MutableString = undefined;
threadlocal var require_buf_loaded: bool = false;
pub fn callExportsAsFunction(
this: *Module,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
if (js.JSObjectIsFunction(ctx, this.exports_function)) {
return js.JSObjectCallAsFunction(ctx, this.exports_function, this.ref, arguments.len, arguments.ptr, exception);
}
return this.exports;
}
pub fn require(
this: *Module,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
if (arguments.len != 1 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringGetMaximumUTF8CStringSize(arguments[0]) == 0) {
defer Output.flush();
if (arguments.len == 0) {
Output.prettyErrorln("<r><red>error<r>: <b>require<r> needs a string, e.g. require(\"left-pad\")", .{});
} else if (arguments.len > 1) {
Output.prettyErrorln("<r><red>error<r>: <b>require<r> only accepts one argument and it must be a string, e.g. require(\"left-pad\")", .{});
} else if (!js.JSValueIsString(ctx, arguments[0])) {
Output.prettyErrorln("<r><red>error<r>: <b>require<r> only supports a string, e.g. require(\"left-pad\")", .{});
} else {
Output.prettyErrorln("<r><red>error<r>: <b>require(\"\")<r> string cannot be empty.", .{});
}
exception.* = js.JSObjectMakeError(ctx, 0, null, null);
return null;
}
const len = js.JSStringGetLength(arguments[0]);
if (!require_buf_loaded) {
require_buf = MutableString.init(this.vm.allocator, len + 1) catch unreachable;
require_buf_loaded = true;
} else {
require_buf.reset();
require_buf.growIfNeeded(len + 1) catch {};
}
require_buf.list.resize(this.vm.allocator, len + 1) catch unreachable;
// var require_buf_ = this.vm.allocator.alloc(u8, len + 1) catch unreachable;
// var end = js.JSStringGetUTF8CString(arguments[0], require_buf_.ptr, require_buf_.len);
var end = js.JSStringGetUTF8CString(arguments[0], require_buf.list.items.ptr, require_buf.list.items.len);
var import_path = require_buf.list.items[0 .. end - 1];
var module = this;
if (this.vm.bundler.linker.resolver.resolve(module.path.name.dirWithTrailingSlash(), import_path, .require)) |resolved| {
var load_result = Module.loadFromResolveResult(this.vm, ctx, resolved, exception) catch |err| {
return null;
};
switch (load_result) {
.Module => |new_module| {
// if (isDebug) {
// Output.prettyln(
// "Input: {s}\nOutput: {s}",
// .{ import_path, load_result.Module.path.text },
// );
// Output.flush();
// }
return new_module.internalGetExports(js.JSContextGetGlobalContext(ctx));
},
.Path => |path| {
return js.JSStringCreateWithUTF8CString(path.text.ptr);
},
}
} else |err| {
Output.prettyErrorln(
"<r><red>RequireError<r>: Failed to load module <b>\"{s}\"<r> at \"{s}\": <red>{s}<r>",
.{ import_path, module.path.name.dirWithTrailingSlash(), @errorName(err) },
);
Output.flush();
JSError(
getAllocator(ctx),
"{s}: failed to load module \"{s}\" from \"{s}\"",
.{
@errorName(err),
import_path,
module.path.name.dirWithTrailingSlash(),
},
ctx,
exception,
);
return null;
}
}
pub fn requireFirst(
this: *Module,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
if (arguments.len == 0 or js.JSStringGetMaximumUTF8CStringSize(arguments[0]) == 0) {
defer Output.flush();
if (arguments.len == 0) {
Output.prettyErrorln("<r><red>error<r>: <b>requireFirst<r> needs a string, e.g. requireFirst(\"left-pad\")", .{});
} else {
Output.prettyErrorln("<r><red>error<r>: <b>requireFirst(\"\")<r> string cannot be empty.", .{});
}
return null;
}
var module = this;
var total_len: usize = 0;
for (arguments) |argument| {
const len = js.JSStringGetLength(argument);
if (!require_buf_loaded) {
require_buf = MutableString.init(this.vm.allocator, len + 1) catch unreachable;
require_buf_loaded = true;
} else {
require_buf.reset();
require_buf.growIfNeeded(len + 1) catch {};
}
require_buf.list.resize(this.vm.allocator, len + 1) catch unreachable;
const end = js.JSStringGetUTF8CString(argument, require_buf.list.items.ptr, require_buf.list.items.len);
total_len += end;
const import_path = require_buf.list.items[0 .. end - 1];
if (this.vm.bundler.linker.resolver.resolve(module.path.name.dirWithTrailingSlash(), import_path, .require)) |resolved| {
var load_result = Module.loadFromResolveResult(this.vm, ctx, resolved, exception) catch |err| {
return null;
};
switch (load_result) {
.Module => |new_module| {
// if (isDebug) {
// Output.prettyln(
// "Input: {s}\nOutput: {s}",
// .{ import_path, load_result.Module.path.text },
// );
// Output.flush();
// }
return new_module.internalGetExports(js.JSContextGetGlobalContext(ctx));
},
.Path => |path| {
return js.JSStringCreateWithUTF8CString(path.text.ptr);
},
}
} else |err| {
switch (err) {
error.ModuleNotFound => {},
else => {
JSError(
getAllocator(ctx),
"{s}: failed to resolve module \"{s}\" from \"{s}\"",
.{
@errorName(err),
import_path,
module.path.name.dirWithTrailingSlash(),
},
ctx,
exception,
);
return null;
},
}
}
}
require_buf.reset();
require_buf.growIfNeeded(total_len) catch {};
var used_len: usize = 0;
var remainder = require_buf.list.items;
for (arguments) |argument| {
const end = js.JSStringGetUTF8CString(argument, remainder.ptr, total_len - used_len);
used_len += end;
remainder[end - 1] = ',';
remainder = remainder[end..];
}
// If we get this far, it means there were no matches
JSError(
getAllocator(ctx),
"RequireError: failed to resolve modules \"{s}\" from \"{s}\"",
.{
require_buf.list.items[0..used_len],
module.path.name.dirWithTrailingSlash(),
},
ctx,
exception,
);
return null;
}
const ModuleClass = NewClass(
Module,
"Module",
.{
.@"require" = require,
.@"requireFirst" = requireFirst,
},
.{
.@"id" = .{
.get = getId,
.ro = true,
},
.@"loaded" = .{
.get = getLoaded,
.ro = true,
},
.@"exports" = .{
.get = getExports,
.set = setExports,
.ro = false,
},
},
false,
false,
);
const ExportsClassName = "module.exports";
var ExportsClass: js.JSClassDefinition = undefined;
var exports_class_ref: js.JSClassRef = undefined;
pub fn boot(vm: *VirtualMachine) void {
ExportsClass = std.mem.zeroes(js.JSClassDefinition);
ExportsClass.className = ExportsClassName[0.. :0];
ExportsClass.callAsFunction = To.JS.Callback(Module, callExportsAsFunction).rfn;
// ExportsClass.callAsConstructor = To.JS.Callback(Module, callExportsAsConstructor);
exports_class_ref = js.JSClassRetain(js.JSClassCreate(&ExportsClass));
module_class_def = ModuleClass.define();
module_class = js.JSClassRetain(js.JSClassCreate(&module_class_def));
}
pub const LoadResult = union(Tag) {
Module: *Module,
Path: Fs.Path,
pub const Tag = enum {
Module,
Path,
};
};
threadlocal var source_code_printer: js_printer.BufferPrinter = undefined;
threadlocal var source_code_printer_loaded: bool = false;
var require_module_params: [3]js.JSStringRef = undefined;
var require_module_params_loaded: bool = false;
threadlocal var module_wrapper_params: [2]js.JSStringRef = undefined;
threadlocal var module_wrapper_loaded = false;
pub fn load(module: *Module, vm: *VirtualMachine, allocator: *std.mem.Allocator, log: *logger.Log, source: [:0]u8, path: Fs.Path, global_ctx: js.JSContextRef, call_ctx: js.JSContextRef, function_ctx: js.JSContextRef, exception: js.ExceptionRef, comptime is_reload: bool) !void {
var source_code_ref = js.JSStringCreateWithUTF8CString(source.ptr);
defer js.JSStringRelease(source_code_ref);
var source_url = try allocator.dupeZ(u8, path.text);
defer allocator.free(source_url);
var source_url_ref = js.JSStringCreateWithUTF8CString(source_url.ptr);
defer js.JSStringRelease(source_url_ref);
if (isDebug) {
Output.print("// {s}\n{s}", .{ path.pretty, source });
Output.flush();
}
if (comptime !is_reload) {
module.* = Module{
.path = path,
.ref = undefined,
.vm = vm,
};
module.ref = js.JSObjectMake(global_ctx, Module.module_class, module);
js.JSValueProtect(global_ctx, module.ref);
} else {
js.JSValueUnprotect(global_ctx, module.exports.?);
}
// if (!module_wrapper_loaded) {
module_wrapper_params[0] = js.JSStringRetain(js.JSStringCreateWithUTF8CString(Properties.UTF8.module[0.. :0]));
module_wrapper_params[1] = js.JSStringRetain(js.JSStringCreateWithUTF8CString(Properties.UTF8.exports[0.. :0]));
// module_wrapper_loaded = true;
// }
var module_wrapper_args: [2]js.JSValueRef = undefined;
module_wrapper_args[0] = module.ref;
module_wrapper_args[1] = module.internalGetExports(global_ctx);
js.JSValueProtect(global_ctx, module_wrapper_args[1]);
var except: js.JSValueRef = null;
go: {
var commonjs_wrapper = js.JSObjectMakeFunction(
global_ctx,
null,
@truncate(c_uint, module_wrapper_params.len),
&module_wrapper_params,
source_code_ref,
null,
1,
&except,
);
js.JSValueProtect(global_ctx, commonjs_wrapper);
if (except != null) {
break :go;
}
// var module = {exports: {}}; ((module, exports) => {
_ = js.JSObjectCallAsFunction(call_ctx, commonjs_wrapper, null, 2, &module_wrapper_args, &except);
// module.exports = exports;
// })(module, module.exports);
// module.exports = module_wrapper_args[1];
js.JSValueUnprotect(global_ctx, commonjs_wrapper);
}
if (except != null) {
var message = js.JSValueToStringCopy(function_ctx, except.?, null);
defer js.JSStringRelease(message);
var message_str_size = js.JSStringGetMaximumUTF8CStringSize(message);
var message_str_buf = try allocator.alloc(u8, message_str_size);
defer allocator.free(message_str_buf);
var message_str_read = js.JSStringGetUTF8CString(message, message_str_buf.ptr, message_str_size);
defer Output.flush();
log.addErrorFmt(null, logger.Loc.Empty, allocator, "Error loading \"{s}\":\n{s}", .{
path.pretty,
message_str_buf[0..message_str_read],
}) catch {};
Output.prettyErrorln("<r>{s}\n--<r><red>error<r> loading <cyan>\"{s}\"<r>--", .{
message_str_buf[0..message_str_read],
path.pretty,
});
return error.FailedException;
}
module.loaded = true;
}
pub fn loadFromResolveResult(
vm: *VirtualMachine,
ctx: js.JSContextRef,
resolved: resolver.Result,
exception: js.ExceptionRef,
) !LoadResult {
const hash = http.Watcher.getHash(resolved.path_pair.primary.text);
var reload_pending = false;
if (vm.require_cache.get(hash)) |mod| {
// require_cache should only contain local modules, not bundled ones.
// so we don't need to check for node_modlues here
reload_pending = mod.reload_pending;
if (!reload_pending) {
return LoadResult{ .Module = mod };
}
}
const path = resolved.path_pair.primary;
const loader: options.Loader = brk: {
if (resolved.is_external) {
break :brk options.Loader.file;
}
break :brk vm.bundler.options.loaders.get(path.name.ext) orelse .file;
};
switch (loader) {
.js,
.jsx,
.ts,
.tsx,
.json,
=> {
if (vm.node_modules) |node_modules| {
const package_json_ = resolved.package_json orelse brk: {
// package_json is sometimes null when we're loading as an absolute path
if (resolved.isLikelyNodeModule()) {
break :brk vm.bundler.resolver.packageJSONForResolvedNodeModule(&resolved);
}
break :brk null;
};
if (package_json_) |package_json| {
if (package_json.hash > 0) {
if (node_modules.getPackageIDByName(package_json.name)) |possible_package_ids| {
const package_id: ?u32 = brk: {
for (possible_package_ids) |pid| {
const pkg = node_modules.bundle.packages[pid];
if (pkg.hash == package_json.hash) {
break :brk pid;
}
}
break :brk null;
};
if (package_id) |pid| {
const package_relative_path = vm.bundler.fs.relative(
package_json.source.path.name.dirWithTrailingSlash(),
path.text,
);
if (node_modules.findModuleIDInPackage(
&node_modules.bundle.packages[pid],
package_relative_path,
)) |id| {
var list = vm.node_module_list.?;
return LoadResult{ .Module = try list.loadBundledModuleById(id + node_modules.bundle.packages[pid].modules_offset, ctx) };
}
}
}
}
}
}
vm.bundler.resetStore();
var fd: ?StoredFileDescriptorType = null;
if (vm.watcher) |watcher| {
if (watcher.indexOf(hash)) |index| {
fd = watcher.watchlist.items(.fd)[index];
}
}
var parse_result = vm.bundler.parse(
vm.bundler.allocator,
path,
loader,
resolved.dirname_fd,
fd,
hash,
) orelse {
return error.ParseError;
};
if (!source_code_printer_loaded) {
var writer = try js_printer.BufferWriter.init(vm.allocator);
source_code_printer = js_printer.BufferPrinter.init(writer);
source_code_printer.ctx.append_null_byte = true;
source_code_printer_loaded = true;
}
source_code_printer.ctx.reset();
// We skip the linker here.
// var old_linker_allocator = vm.bundler.linker.allocator;
// defer vm.bundler.linker.allocator = old_linker_allocator;
// vm.bundler.linker.allocator = vm.allocator;
// // Always use absolute paths
// // This makes the resolver faster
// try vm.bundler.linker.link(
// Fs.Path.init(path.text),
// &parse_result,
// .absolute_path,
// );
var written = try vm.bundler.print(
parse_result,
@TypeOf(&source_code_printer),
&source_code_printer,
.speedy,
);
if (written == 0) {
return error.PrintingErrorWriteFailed;
}
var module: *Module = undefined;
if (reload_pending) {
module = vm.require_cache.get(hash).?;
} else {
module = try vm.allocator.create(Module);
try vm.require_cache.put(hash, module);
}
errdefer {
if (!reload_pending) {
vm.allocator.destroy(module);
}
}
if (reload_pending) {
try Module.load(
module,
vm,
vm.allocator,
vm.log,
source_code_printer.ctx.sentinel,
path,
js.JSContextGetGlobalContext(ctx),
ctx,
ctx,
exception,
true,
);
} else {
try Module.load(
module,
vm,
vm.allocator,
vm.log,
source_code_printer.ctx.sentinel,
path,
js.JSContextGetGlobalContext(ctx),
ctx,
ctx,
exception,
false,
);
}
return LoadResult{ .Module = module };
},
// Replace imports to non-executables with paths to those files.
// In SSR or on web, these become URLs.
// Otherwise, absolute file paths.
else => {
switch (vm.bundler.options.import_path_format) {
.absolute_path => {
return LoadResult{ .Path = path };
},
.absolute_url => {
var fs = vm.bundler.fs;
var base = fs.relativeTo(path.text);
if (strings.lastIndexOfChar(base, '.')) |dot| {
base = base[0..dot];
}
var dirname = std.fs.path.dirname(base) orelse "";
var basename = std.fs.path.basename(base);
const needs_slash = dirname.len > 0 and dirname[dirname.len - 1] != '/';
if (needs_slash) {
const absolute_url = try std.fmt.allocPrintZ(
vm.allocator,
"{s}{s}/{s}{s}",
.{
vm.bundler.options.public_url,
dirname,
basename,
path.name.ext,
},
);
return LoadResult{
.Path = Fs.Path.initWithPretty(absolute_url, absolute_url),
};
} else {
const absolute_url = try std.fmt.allocPrintZ(
vm.allocator,
"{s}{s}{s}{s}",
.{
vm.bundler.options.public_url,
dirname,
basename,
path.name.ext,
},
);
return LoadResult{
.Path = Fs.Path.initWithPretty(absolute_url, absolute_url),
};
}
},
else => unreachable,
}
},
}
}
pub fn getLoaded(
this: *Module,
ctx: js.JSContextRef,
thisObject: js.JSValueRef,
prop: js.JSStringRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSValueRef {
return js.JSValueMakeBoolean(ctx, this.loaded);
}
pub fn getId(
this: *Module,
ctx: js.JSContextRef,
thisObject: js.JSValueRef,
prop: js.JSStringRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSValueRef {
if (this.id == null) {
this.id = js.JSStringCreateWithUTF8CString(this.path.text.ptr);
}
return this.id;
}
pub fn getExports(
this: *Module,
ctx: js.JSContextRef,
thisObject: js.JSValueRef,
prop: js.JSStringRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSValueRef {
return this.exports;
}
pub fn internalGetExports(this: *Module, globalContext: js.JSContextRef) js.JSValueRef {
if (this.exports == null) {
this.exports = js.JSObjectMake(globalContext, exports_class_ref, this);
}
return this.exports;
}
pub fn internalGetRequire(this: *Module) js.JSValueRef {
if (this.require_func == null) {
this.require_func = To.JS.functionWithCallback(
Module,
this,
Properties.Refs.require,
this.vm.global.ctx,
require,
);
}
return this.require_func;
}
pub fn setExports(
this: *Module,
ctx: js.JSContextRef,
thisObject: js.JSValueRef,
prop: js.JSStringRef,
value: js.JSValueRef,
exception: js.ExceptionRef,
) bool {
if (this.exports != null) {
if (js.JSValueIsString(this.vm.global.ctx, this.exports)) {
js.JSStringRelease(this.exports);
}
}
switch (js.JSValueGetType(ctx, value)) {
.kJSTypeObject => {
if (js.JSValueIsObjectOfClass(ctx, value, exports_class_ref)) {
var other = @ptrCast(
*Module,
@alignCast(
@alignOf(
*Module,
),
js.JSObjectGetPrivate(value).?,
),
);
if (other != this) {
this.exports = other.exports;
}
return true;
} else {
if (js.JSObjectIsFunction(ctx, value)) {
this.exports_function = value;
}
}
},
else => {},
}
this.exports = value;
return true;
}
pub const RequireObject = struct {};
};
pub const EventListenerMixin = struct {
threadlocal var event_listener_names_buf: [128]u8 = undefined;
pub const List = std.ArrayList(js.JSObjectRef);
pub const Map = std.AutoHashMap(EventListenerMixin.EventType, EventListenerMixin.List);
pub const EventType = enum {
fetch,
err,
const SizeMatcher = strings.ExactSizeMatcher(8);
pub fn match(str: string) ?EventType {
return switch (SizeMatcher.match(str)) {
SizeMatcher.case("fetch") => EventType.fetch,
SizeMatcher.case("error") => EventType.err,
else => null,
};
}
};
pub fn emitFetchEventError(
request: *http.RequestContext,
comptime fmt: string,
args: anytype,
) void {
Output.prettyErrorln(fmt, args);
request.sendInternalError(error.FetchEventError) catch {};
}
pub fn emitFetchEvent(
vm: *VirtualMachine,
request_context: *http.RequestContext,
) !void {
var listeners = vm.event_listeners.get(EventType.fetch) orelse return emitFetchEventError(
request_context,
"Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?",
.{},
);
if (listeners.items.len == 0) return emitFetchEventError(
request_context,
"Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?",
.{},
);
var exception: js.JSValueRef = null;
// Rely on JS finalizer
var fetch_event = try vm.allocator.create(FetchEvent);
fetch_event.* = FetchEvent{
.request_context = request_context,
.request = Request{ .request_context = request_context },
};
var fetch_args: [1]js.JSObjectRef = undefined;
for (listeners.items) |listener| {
fetch_args[0] = js.JSObjectMake(
vm.ctx,
FetchEvent.Class.get().*,
fetch_event,
);
_ = js.JSObjectCallAsFunction(
vm.ctx,
listener,
js.JSContextGetGlobalObject(vm.ctx),
1,
&fetch_args,
&exception,
);
if (request_context.has_called_done) {
break;
}
}
if (exception != null) {
var message = js.JSValueToStringCopy(vm.ctx, exception, null);
defer js.JSStringRelease(message);
var buf = vm.allocator.alloc(u8, js.JSStringGetLength(message) + 1) catch unreachable;
defer vm.allocator.free(buf);
var note = buf[0 .. js.JSStringGetUTF8CString(message, buf.ptr, buf.len) - 1];
Output.prettyErrorln("<r><red>error<r>: <b>{s}<r>", .{note});
Output.flush();
if (!request_context.has_called_done) {
request_context.sendInternalError(error.JavaScriptError) catch {};
}
return;
}
if (!request_context.has_called_done) {
return emitFetchEventError(
request_context,
"\"fetch\" handler never called event.respondWith()",
.{},
);
}
}
pub fn addEventListener(
comptime Struct: type,
) type {
const Handler = struct {
pub fn addListener(
ptr: *Struct,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
if (arguments.len == 0 or arguments.len == 1 or !js.JSValueIsString(ctx, arguments[0]) or !js.JSValueIsObject(ctx, arguments[arguments.len - 1]) or !js.JSObjectIsFunction(ctx, arguments[arguments.len - 1])) {
return js.JSValueMakeUndefined(ctx);
}
const name_len = js.JSStringGetLength(arguments[0]);
if (name_len > event_listener_names_buf.len) {
return js.JSValueMakeUndefined(ctx);
}
const name_used_len = js.JSStringGetUTF8CString(arguments[0], &event_listener_names_buf, event_listener_names_buf.len);
const name = event_listener_names_buf[0 .. name_used_len - 1];
const event = EventType.match(name) orelse return js.JSValueMakeUndefined(ctx);
var entry = VirtualMachine.instance.event_listeners.getOrPut(event) catch unreachable;
if (!entry.found_existing) {
entry.value_ptr.* = List.initCapacity(VirtualMachine.instance.allocator, 1) catch unreachable;
}
var callback = arguments[arguments.len - 1];
js.JSValueProtect(ctx, callback);
entry.value_ptr.append(callback) catch unreachable;
return js.JSValueMakeUndefined(ctx);
}
};
return Handler;
}
};
pub const GlobalObject = struct {
ref: js.JSObjectRef = undefined,
vm: *VirtualMachine,
ctx: js.JSGlobalContextRef = undefined,
console_class: js.JSClassRef = undefined,
console: js.JSObjectRef = undefined,
console_definition: js.JSClassDefinition = undefined,
global_class_def: js.JSClassDefinition = undefined,
global_class: js.JSClassRef = undefined,
pub const ConsoleClass = NewClass(
GlobalObject,
"Console",
.{
.@"log" = stdout,
.@"info" = stdout,
.@"debug" = stdout,
.@"verbose" = stdout,
.@"error" = stderr,
.@"warn" = stderr,
},
.{},
// people sometimes modify console.log, let them.
false,
true,
);
pub const GlobalClass = NewClass(
GlobalObject,
"Global",
.{
.@"addEventListener" = EventListenerMixin.addEventListener(GlobalObject).addListener,
},
.{
.@"console" = getConsole,
.@"Request" = Request.Class.GetClass(GlobalObject).getter,
.@"Response" = Response.Class.GetClass(GlobalObject).getter,
.@"Headers" = Headers.Class.GetClass(GlobalObject).getter,
},
false,
false,
);
pub fn getConsole(
global: *GlobalObject,
ctx: js.JSContextRef,
obj: js.JSObjectRef,
exception: js.ExceptionRef,
) js.JSValueRef {
// if (global.console == null) {
// global.console = js.JSObjectMake(js.JSContextGetGlobalContext(ctx), global.console_class, global);
// js.JSValueProtect(js.JSContextGetGlobalContext(ctx), global.console);
// }
return js.JSObjectMake(js.JSContextGetGlobalContext(ctx), ConsoleClass.get().*, global);
}
pub fn boot(global: *GlobalObject) !void {
global.ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(global.vm.group, GlobalObject.GlobalClass.get().*));
std.debug.assert(js.JSObjectSetPrivate(js.JSContextGetGlobalObject(global.ctx), global));
if (!printer_buf_loaded) {
printer_buf_loaded = true;
printer_buf = try MutableString.init(global.vm.allocator, 4096);
}
}
threadlocal var printer_buf: MutableString = undefined;
threadlocal var printer_buf_loaded: bool = false;
fn valuePrinter(comptime ValueType: js.JSType, ctx: js.JSContextRef, arg: js.JSValueRef, writer: anytype) !void {
switch (ValueType) {
.kJSTypeUndefined => {
try writer.writeAll("undefined");
},
.kJSTypeNull => {
try writer.writeAll("null");
},
.kJSTypeBoolean => {
if (js.JSValueToBoolean(ctx, arg)) {
try writer.writeAll("true");
} else {
try writer.writeAll("false");
}
},
.kJSTypeNumber => {
try writer.print(
"{d}",
.{js.JSValueToNumber(ctx, arg, null)},
);
},
.kJSTypeString => {
printer_buf.reset();
var string_ref = js.JSValueToStringCopy(ctx, arg, null);
const len = js.JSStringGetMaximumUTF8CStringSize(string_ref) + 1;
printer_buf.growIfNeeded(len) catch {};
printer_buf.inflate(len) catch {};
var slice = printer_buf.toOwnedSliceLeaky();
defer js.JSStringRelease(string_ref);
const used = js.JSStringGetUTF8CString(string_ref, slice.ptr, slice.len);
try writer.writeAll(slice[0..used]);
},
.kJSTypeObject => {
// TODO:
try writer.writeAll("[Object object]");
},
.kJSTypeSymbol => {
var description = js.JSObjectGetPropertyForKey(ctx, arg, Properties.Refs.description, null);
return switch (js.JSValueGetType(ctx, description)) {
.kJSTypeString => try valuePrinter(.kJSTypeString, ctx, js.JSStringRetain(description), writer),
else => try valuePrinter(.kJSTypeUndefined, ctx, js.JSStringRetain(description), writer),
};
},
else => {},
}
}
fn output(
writer: anytype,
ctx: js.JSContextRef,
arguments: []const js.JSValueRef,
) !void {
defer Output.flush();
// console.log();
if (arguments.len == 0) {
return;
}
const last = arguments.len - 1;
defer writer.writeAll("\n") catch {};
for (arguments) |arg, i| {
switch (js.JSValueGetType(ctx, arg)) {
.kJSTypeUndefined => {
try valuePrinter(.kJSTypeUndefined, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
.kJSTypeNull => {
try valuePrinter(.kJSTypeNull, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
.kJSTypeBoolean => {
try valuePrinter(.kJSTypeBoolean, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
.kJSTypeNumber => {
try valuePrinter(.kJSTypeNumber, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
.kJSTypeString => {
try valuePrinter(.kJSTypeString, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
.kJSTypeObject => {
try valuePrinter(.kJSTypeObject, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
.kJSTypeSymbol => {
try valuePrinter(.kJSTypeSymbol, ctx, arg, writer);
if (i != last) {
try writer.writeAll(" ");
}
},
else => {},
}
}
}
pub fn stdout(
obj: *GlobalObject,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
output(Output.writer(), ctx, arguments) catch {};
return js.JSValueMakeUndefined(ctx);
}
pub fn stderr(
obj: *GlobalObject,
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
) js.JSValueRef {
output(Output.errorWriter(), ctx, arguments) catch {};
return js.JSValueMakeUndefined(ctx);
// js.JSObjectMakeFunctionWithCallback(ctx: JSContextRef, name: JSStringRef, callAsFunction: JSObjectCallAsFunctionCallback)
}
};