mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
@@ -120,7 +120,7 @@ pub fn build(b: *std.build.Builder) void {
|
||||
addPicoHTTP(exe, cwd);
|
||||
javascript = b.addExecutable("spjs", "src/main_javascript.zig");
|
||||
addPicoHTTP(javascript, cwd);
|
||||
javascript.packages = std.ArrayList(std.build.Pkg).fromOwnedSlice(std.heap.c_allocator, std.heap.c_allocator.dupe(std.build.Pkg, exe.packages.items) catch unreachable);
|
||||
javascript.packages = std.ArrayList(std.build.Pkg).fromOwnedSlice(std.heap.page_allocator, std.heap.page_allocator.dupe(std.build.Pkg, exe.packages.items) catch unreachable);
|
||||
javascript.setOutputDir(output_dir);
|
||||
javascript.setBuildMode(mode);
|
||||
javascript.linkLibC();
|
||||
|
||||
15
demos/css-stress-test/nextjs-framework.tsx
Normal file
15
demos/css-stress-test/nextjs-framework.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { renderNextJSPage } from "speedy-nextjs/server";
|
||||
|
||||
addEventListener("fetch", (event: FetchEvent) => {
|
||||
const AppComponent = module.requireFirst(
|
||||
"pages/_app",
|
||||
"speedy-nextjs/pages/_app"
|
||||
);
|
||||
const Document = module.requireFirst(
|
||||
"pages/_document",
|
||||
"speedy-nextjs/pages/_document"
|
||||
);
|
||||
});
|
||||
|
||||
// typescript isolated modules
|
||||
export {};
|
||||
@@ -20,6 +20,9 @@ if (typeof window !== "undefined") {
|
||||
});
|
||||
|
||||
startReact();
|
||||
} else {
|
||||
const ReactDOMServer = require("react-dom/server.browser");
|
||||
console.log(ReactDOMServer.renderToString(<Base />));
|
||||
}
|
||||
|
||||
export { Base };
|
||||
|
||||
31
src/http.zig
31
src/http.zig
@@ -637,6 +637,8 @@ pub const RequestContext = struct {
|
||||
ctx: RequestContext,
|
||||
conn: tcp.Connection,
|
||||
|
||||
pub var javascript_vm: *JavaScript.VirtualMachine = undefined;
|
||||
|
||||
pub const HandlerThread = struct {
|
||||
args: Api.TransformOptions,
|
||||
existing_bundle: ?*NodeModuleBundle,
|
||||
@@ -685,6 +687,7 @@ pub const RequestContext = struct {
|
||||
.entry_point,
|
||||
);
|
||||
JavaScript.VirtualMachine.instance = vm;
|
||||
javascript_vm = JavaScript.VirtualMachine.instance;
|
||||
var exception: js.JSValueRef = null;
|
||||
var load_result = try JavaScript.Module.loadFromResolveResult(vm, vm.ctx, resolved_entry_point, &exception);
|
||||
|
||||
@@ -1488,6 +1491,8 @@ pub const Server = struct {
|
||||
timer: std.time.Timer = undefined,
|
||||
transform_options: Api.TransformOptions,
|
||||
|
||||
javascript_enabled: bool = false,
|
||||
|
||||
pub fn adjustUlimit() !void {
|
||||
var limit = try std.os.getrlimit(.NOFILE);
|
||||
if (limit.cur < limit.max) {
|
||||
@@ -1509,6 +1514,19 @@ pub const Server = struct {
|
||||
threadlocal var filechange_buf: [32]u8 = undefined;
|
||||
|
||||
pub fn onFileUpdate(ctx: *Server, events: []watcher.WatchEvent, watchlist: watcher.Watchlist) void {
|
||||
if (ctx.javascript_enabled) {
|
||||
_onFileUpdate(ctx, events, watchlist, true);
|
||||
} else {
|
||||
_onFileUpdate(ctx, events, watchlist, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn _onFileUpdate(
|
||||
ctx: *Server,
|
||||
events: []watcher.WatchEvent,
|
||||
watchlist: watcher.Watchlist,
|
||||
comptime is_javascript_enabled: bool,
|
||||
) void {
|
||||
var fbs = std.io.fixedBufferStream(&filechange_buf);
|
||||
var writer = ByteApiWriter.init(&fbs);
|
||||
const message_type = Api.WebsocketMessage{
|
||||
@@ -1517,18 +1535,28 @@ pub const Server = struct {
|
||||
};
|
||||
message_type.encode(&writer) catch unreachable;
|
||||
var header = fbs.getWritten();
|
||||
|
||||
for (events) |event| {
|
||||
const file_path = watchlist.items(.file_path)[event.index];
|
||||
|
||||
// so it's consistent with the rest
|
||||
// if we use .extname we might run into an issue with whether or not the "." is included.
|
||||
const path = Fs.PathName.init(file_path);
|
||||
const id = watchlist.items(.hash)[event.index];
|
||||
var content_fbs = std.io.fixedBufferStream(filechange_buf[header.len..]);
|
||||
|
||||
defer {
|
||||
if (comptime is_javascript_enabled) {
|
||||
// TODO: does this need a lock?
|
||||
if (RequestContext.JavaScriptHandler.javascript_vm.require_cache.get(id)) |module| {
|
||||
module.reload_pending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const change_message = Api.WebsocketMessageFileChangeNotification{
|
||||
.id = id,
|
||||
.loader = (ctx.bundler.options.loaders.get(path.ext) orelse .file).toAPI(),
|
||||
};
|
||||
|
||||
var content_writer = ByteApiWriter.init(&content_fbs);
|
||||
change_message.encode(&content_writer) catch unreachable;
|
||||
const change_buf = content_fbs.getWritten();
|
||||
@@ -1642,6 +1670,7 @@ pub const Server = struct {
|
||||
if (req_ctx.url.extname.len == 0 and !RequestContext.JavaScriptHandler.javascript_disabled) {
|
||||
if (server.transform_options.javascript_framework_file != null) {
|
||||
RequestContext.JavaScriptHandler.enqueue(&req_ctx, server) catch unreachable;
|
||||
server.javascript_enabled = !RequestContext.JavaScriptHandler.javascript_disabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ pub const To = struct {
|
||||
return function;
|
||||
}
|
||||
|
||||
pub fn Finalize(
|
||||
pub fn Finalize(n
|
||||
comptime ZigContextType: type,
|
||||
comptime ctxfn: fn (
|
||||
this: *ZigContextType,
|
||||
|
||||
@@ -255,6 +255,12 @@ pub const Module = struct {
|
||||
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;
|
||||
@@ -408,6 +414,7 @@ pub const Module = struct {
|
||||
call_ctx,
|
||||
call_ctx,
|
||||
&exception,
|
||||
false,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -728,15 +735,131 @@ pub const Module = struct {
|
||||
.{ import_path, module.path.name.dirWithTrailingSlash(), @errorName(err) },
|
||||
);
|
||||
Output.flush();
|
||||
exception.* = js.JSObjectMakeError(ctx, 0, null, null);
|
||||
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 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];
|
||||
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| {
|
||||
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 },
|
||||
.{
|
||||
.@"require" = require,
|
||||
.@"requireFirst" = requireFirst,
|
||||
},
|
||||
.{
|
||||
.@"id" = .{
|
||||
.get = getId,
|
||||
@@ -789,18 +912,7 @@ pub const Module = struct {
|
||||
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,
|
||||
) !void {
|
||||
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);
|
||||
@@ -813,13 +925,18 @@ pub const Module = struct {
|
||||
Output.flush();
|
||||
}
|
||||
|
||||
module.* = Module{
|
||||
.path = path,
|
||||
.ref = undefined,
|
||||
.vm = vm,
|
||||
};
|
||||
module.ref = js.JSObjectMake(global_ctx, Module.module_class, module);
|
||||
js.JSValueProtect(global_ctx, module.ref);
|
||||
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]));
|
||||
@@ -886,8 +1003,15 @@ pub const Module = struct {
|
||||
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| {
|
||||
return LoadResult{ .Module = 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;
|
||||
@@ -1000,22 +1124,50 @@ pub const Module = struct {
|
||||
if (written == 0) {
|
||||
return error.PrintingErrorWriteFailed;
|
||||
}
|
||||
var module = try vm.allocator.create(Module);
|
||||
errdefer vm.allocator.destroy(module);
|
||||
try vm.require_cache.put(hash, module);
|
||||
var module: *Module = undefined;
|
||||
|
||||
try Module.load(
|
||||
module,
|
||||
vm,
|
||||
vm.allocator,
|
||||
vm.log,
|
||||
source_code_printer.ctx.sentinel,
|
||||
path,
|
||||
js.JSContextGetGlobalContext(ctx),
|
||||
ctx,
|
||||
ctx,
|
||||
exception,
|
||||
);
|
||||
if (needs_reload) {
|
||||
module = vm.require_cache.get(hash).?;
|
||||
} else {
|
||||
module = try vm.allocator.create(Module);
|
||||
try vm.require_cache.put(hash, module);
|
||||
}
|
||||
|
||||
errdefer {
|
||||
if (!needs_reload) {
|
||||
vm.allocator.destroy(module);
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_reload) {
|
||||
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 };
|
||||
},
|
||||
@@ -1191,13 +1343,16 @@ pub const EventListenerMixin = struct {
|
||||
|
||||
pub const EventType = enum {
|
||||
fetch,
|
||||
err,
|
||||
|
||||
const SizeMatcher = strings.ExactSizeMatcher("fetch".len);
|
||||
|
||||
pub fn match(str: string) ?EventType {
|
||||
if (strings.eqlComptime(str, "fetch")) {
|
||||
return EventType.fetch;
|
||||
}
|
||||
|
||||
return null;
|
||||
return switch (SizeMatcher.match(str)) {
|
||||
SizeMatcher.case("fetch") => EventType.fetch,
|
||||
SizeMatcher.case("error") => EventType.err,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1226,6 +1381,7 @@ pub const EventListenerMixin = struct {
|
||||
);
|
||||
|
||||
var exception: js.JSValueRef = null;
|
||||
// Rely on JS finalizer
|
||||
var fetch_event = try vm.allocator.create(FetchEvent);
|
||||
fetch_event.* = FetchEvent{
|
||||
.request_context = request_context,
|
||||
|
||||
@@ -1129,4 +1129,5 @@ pub const FetchEvent = struct {
|
||||
) js.JSValueRef {
|
||||
return js.JSValueMakeUndefined(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
5
types.d.ts
vendored
Normal file
5
types.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
interface SpeedyNodeModule extends NodeJS.Module {
|
||||
requireFirst(...id: string[]): any;
|
||||
}
|
||||
|
||||
declare var module: SpeedyNodeModule;
|
||||
Reference in New Issue
Block a user