aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-08-15 21:48:56 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-08-15 21:48:56 -0700
commit41260ecd183694bed3c2c0789cd7133218efee8b (patch)
tree6ea2ba0e23e8cffa38ff27e70261642a7aeb3763
parentdb4caf0d4216644f8c554938f4fa802a441920ac (diff)
downloadbun-41260ecd183694bed3c2c0789cd7133218efee8b.tar.gz
bun-41260ecd183694bed3c2c0789cd7133218efee8b.tar.zst
bun-41260ecd183694bed3c2c0789cd7133218efee8b.zip
Support multiple route dirs, fix bundling JSX, fix cjs bug, remove warning about unbundled modules in speedy env,
Former-commit-id: ae718dbd05397bed9bc49a77fae20de70b635e82
-rw-r--r--examples/nexty/client.development.tsx5
-rw-r--r--examples/nexty/index.js0
-rw-r--r--examples/nexty/package.json30
-rw-r--r--examples/nexty/server.development.tsx46
-rw-r--r--examples/nexty/server.production.tsx0
-rw-r--r--src/api/schema.d.ts2
-rw-r--r--src/api/schema.js11
-rw-r--r--src/api/schema.peechy2
-rw-r--r--src/api/schema.zig6
-rw-r--r--src/bundler.zig157
-rw-r--r--src/cli.zig7
-rw-r--r--src/http.zig38
-rw-r--r--src/javascript/jsc/javascript.zig89
-rw-r--r--src/js_ast.zig8
-rw-r--r--src/js_lexer.zig1
-rw-r--r--src/js_parser/js_parser.zig180
-rw-r--r--src/logger.zig2
-rw-r--r--src/options.zig23
-rw-r--r--src/resolver/package_json.zig50
-rw-r--r--src/resolver/resolver.zig25
-rw-r--r--src/runtime.js6
-rw-r--r--src/runtime.version2
-rw-r--r--src/runtime.zig8
23 files changed, 515 insertions, 183 deletions
diff --git a/examples/nexty/client.development.tsx b/examples/nexty/client.development.tsx
deleted file mode 100644
index 7f517ab78..000000000
--- a/examples/nexty/client.development.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import ReactDOM from "react-dom";
-
-export function start(EntryPointNamespace) {
- ReactDOM.hydrate(<EntryPointNamespace.default />);
-}
diff --git a/examples/nexty/index.js b/examples/nexty/index.js
deleted file mode 100644
index e69de29bb..000000000
--- a/examples/nexty/index.js
+++ /dev/null
diff --git a/examples/nexty/package.json b/examples/nexty/package.json
deleted file mode 100644
index d1e984fdc..000000000
--- a/examples/nexty/package.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "name": "nexty",
- "version": "1.0.0",
- "description": "",
- "framework": {
- "router": {
- "dir": "pages",
- "extensions": [
- ".js",
- ".ts",
- ".tsx"
- ]
- },
- "static": "public",
- "development": {
- "client": "client.development.tsx",
- "server": "server.development.tsx"
- },
- "production": {
- "client": "client.production.tsx",
- "server": "server.production.tsx"
- }
- },
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "",
- "license": "ISC",
- "dependencies": {}
-}
diff --git a/examples/nexty/server.development.tsx b/examples/nexty/server.development.tsx
deleted file mode 100644
index 0e28dac65..000000000
--- a/examples/nexty/server.development.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import ReactDOMServer from "react-dom/server.browser";
-
-addEventListener("fetch", async (event: FetchEvent) => {
- var route = Wundle.match(event);
-
- console.log("Main:", Wundle.main);
- console.log("cwd:", Wundle.cwd);
- console.log("Origin:", Wundle.origin);
-
- const { default: PageComponent } = await import(route.filePath);
- // This returns all .css files that were imported in the line above.
- // It's recursive, so any file that imports a CSS file will be included.
- const stylesheets = Wundle.getImportedStyles() as string[];
-
- const response = new Response(`
- <!DOCTYPE html>
-<html>
- <head>
- ${stylesheets
- .map((style) => `<link rel="stylesheet" href="${style}">`)
- .join("\n")}
-
- <link
- rel="stylesheet"
- crossorigin="anonymous"
- href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&family=Space+Mono:wght@400;700"
- />
- </head>
- <body>
-
- <div id="reactroot">${ReactDOMServer.renderToString(
- <PageComponent />
- )}</div>
-
- <script src="${route.scriptSrc}" async type="module"></script>
- </body>
-</html>
- `);
-
- event.respondWith(response);
-});
-
-// typescript isolated modules
-export {};
-
-declare var Wundle: any;
diff --git a/examples/nexty/server.production.tsx b/examples/nexty/server.production.tsx
deleted file mode 100644
index e69de29bb..000000000
--- a/examples/nexty/server.production.tsx
+++ /dev/null
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts
index 0b80700c5..4e652ae81 100644
--- a/src/api/schema.d.ts
+++ b/src/api/schema.d.ts
@@ -285,7 +285,7 @@ type uint32 = number;
}
export interface RouteConfig {
- dir?: string;
+ dir?: string[];
extensions?: string[];
static_dir?: string;
asset_prefix?: string;
diff --git a/src/api/schema.js b/src/api/schema.js
index 3a76caedf..5149c1ffd 100644
--- a/src/api/schema.js
+++ b/src/api/schema.js
@@ -867,7 +867,9 @@ function decodeRouteConfig(bb) {
return result;
case 1:
- result["dir"] = bb.readString();
+ var length = bb.readVarUint();
+ var values = result["dir"] = Array(length);
+ for (var i = 0; i < length; i++) values[i] = bb.readString();
break;
case 2:
@@ -895,7 +897,12 @@ function encodeRouteConfig(message, bb) {
var value = message["dir"];
if (value != null) {
bb.writeByte(1);
- bb.writeString(value);
+ var values = value, n = values.length;
+ bb.writeVarUint(n);
+ for (var i = 0; i < n; i++) {
+ value = values[i];
+ bb.writeString(value);
+ }
}
var value = message["extensions"];
diff --git a/src/api/schema.peechy b/src/api/schema.peechy
index 431fab3c1..863c69d6a 100644
--- a/src/api/schema.peechy
+++ b/src/api/schema.peechy
@@ -178,7 +178,7 @@ struct LoadedRouteConfig {
}
message RouteConfig {
- string dir = 1;
+ string[] dir = 1;
string[] extensions = 2;
string static_dir = 3;
string asset_prefix = 4;
diff --git a/src/api/schema.zig b/src/api/schema.zig
index ffb949a1f..60eceb1ad 100644
--- a/src/api/schema.zig
+++ b/src/api/schema.zig
@@ -1027,7 +1027,7 @@ pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
pub const RouteConfig = struct {
/// dir
-dir: ?[]const u8 = null,
+dir: []const []const u8,
/// extensions
extensions: []const []const u8,
@@ -1047,7 +1047,7 @@ pub fn decode(reader: anytype) anyerror!RouteConfig {
0 => { return this; },
1 => {
- this.dir = try reader.readValue([]const u8);
+ this.dir = try reader.readArray([]const u8);
},
2 => {
this.extensions = try reader.readArray([]const u8);
@@ -1069,7 +1069,7 @@ unreachable;
pub fn encode(this: *const @This(), writer: anytype) anyerror!void {
if (this.dir) |dir| {
try writer.writeFieldID(1);
- try writer.writeValue(dir);
+ try writer.writeArray([]const u8, dir);
}
if (this.extensions) |extensions| {
try writer.writeFieldID(2);
diff --git a/src/bundler.zig b/src/bundler.zig
index 97e9f692c..0e731eeab 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -130,6 +130,58 @@ pub const ClientEntryPoint = struct {
}
};
+pub const ServerEntryPoint = struct {
+ code_buffer: [std.fs.MAX_PATH_BYTES * 2 + 500]u8 = undefined,
+ output_code_buffer: [std.fs.MAX_PATH_BYTES * 8 + 500]u8 = undefined,
+ source: logger.Source = undefined,
+
+ pub fn generate(
+ entry: *ServerEntryPoint,
+ comptime BundlerType: type,
+ bundler: *BundlerType,
+ original_path: Fs.PathName,
+ name: string,
+
+ ) !void {
+
+ // This is *extremely* naive.
+ // The basic idea here is this:
+ // --
+ // import * as EntryPoint from 'entry-point';
+ // import boot from 'framework';
+ // boot(EntryPoint);
+ // --
+ // We go through the steps of printing the code -- only to then parse/transpile it because
+ // we want it to go through the linker and the rest of the transpilation process
+
+ const dir_to_use: string = original_path.dirWithTrailingSlash();
+
+ const code = try std.fmt.bufPrint(
+ &entry.code_buffer,
+ \\//Auto-generated file
+ \\import * as start from '{s}{s}';
+ \\if ('default' in start && typeof start.default === 'function') {{
+ \\ const result = start.default();
+ \\ if (result && typeof result === 'object' && result instanceof Promise) {{
+ \\ result.then(undefined, undefined);
+ \\ }}
+ \\}}
+ \\export * from '{s}{s}';
+ ,
+ .{
+ dir_to_use,
+ original_path.filename,
+ dir_to_use,
+ original_path.filename,
+ },
+ );
+
+ entry.source = logger.Source.initPathString(name, code);
+ entry.source.path.text = name;
+ entry.source.path.namespace = "server-entry";
+ }
+};
+
pub const ResolveResults = std.AutoHashMap(u64, void);
pub const ResolveQueue = std.fifo.LinearFifo(_resolver.Result, std.fifo.LinearFifoBufferType.Dynamic);
@@ -528,6 +580,20 @@ pub fn NewBundler(cache_files: bool) type {
pub const current_version: u32 = 1;
+ pub fn enqueueItem(this: *GenerateNodeModuleBundle, resolve: _resolver.Result) !void {
+ var this_module_resolved_path = try this.resolved_paths.getOrPut(resolve.path_pair.primary.text);
+
+ if (!this_module_resolved_path.found_existing) {
+ if (resolve.isLikelyNodeModule()) {
+ if (BundledModuleData.get(this, &resolve)) |module_data_| {
+ this_module_resolved_path.value_ptr.* = module_data_.module_id;
+ }
+ }
+
+ this.resolve_queue.writeItemAssumeCapacity(resolve);
+ }
+ }
+
// The Speedy Bundle Format
// Your entire node_modules folder in a single compact file designed for web browsers.
// A binary JavaScript bundle format prioritizing bundle time and serialization/deserialization time
@@ -621,75 +687,87 @@ pub fn NewBundler(cache_files: bool) type {
if (bundler.log.level == .verbose) {
bundler.resolver.debug_logs = try DebugLogs.init(allocator);
}
+ temp_stmts_list = @TypeOf(temp_stmts_list).init(allocator);
+ const include_refresh_runtime = !this.bundler.options.production and this.bundler.options.jsx.supports_fast_refresh and bundler.options.platform.isWebLike();
+
+ const resolve_queue_estimate = bundler.options.entry_points.len +
+ @intCast(usize, @boolToInt(framework_config != null)) +
+ @intCast(usize, @boolToInt(include_refresh_runtime)) +
+ @intCast(usize, @boolToInt(bundler.options.jsx.parse));
if (bundler.router) |router| {
+ defer this.bundler.resetStore();
+
const entry_points = try router.getEntryPoints(allocator);
- try this.resolve_queue.ensureUnusedCapacity(entry_points.len + bundler.options.entry_points.len + @intCast(usize, @boolToInt(framework_config != null)));
+ try this.resolve_queue.ensureUnusedCapacity(entry_points.len + resolve_queue_estimate);
for (entry_points) |entry_point| {
const source_dir = bundler.fs.top_level_dir;
const resolved = try bundler.linker.resolver.resolve(source_dir, entry_point, .entry_point);
- this.resolve_queue.writeItemAssumeCapacity(resolved);
+ try this.enqueueItem(resolved);
}
this.bundler.resetStore();
} else {
- try this.resolve_queue.ensureUnusedCapacity(bundler.options.entry_points.len + @intCast(usize, @boolToInt(framework_config != null)));
+ try this.resolve_queue.ensureUnusedCapacity(resolve_queue_estimate);
}
for (bundler.options.entry_points) |entry_point| {
+ defer this.bundler.resetStore();
+
const entry_point_path = bundler.normalizeEntryPointPath(entry_point);
const source_dir = bundler.fs.top_level_dir;
const resolved = try bundler.linker.resolver.resolve(source_dir, entry_point, .entry_point);
- this.resolve_queue.writeItemAssumeCapacity(resolved);
+ try this.enqueueItem(resolved);
}
if (framework_config) |conf| {
+ defer this.bundler.resetStore();
+
if (conf.client) {
if (bundler.configureFrameworkWithResolveResult(true)) |result_| {
if (result_) |result| {
- this.resolve_queue.writeItemAssumeCapacity(result);
+ try this.enqueueItem(result);
}
} else |err| {}
} else {
if (bundler.configureFrameworkWithResolveResult(false)) |result_| {
if (result_) |result| {
- this.resolve_queue.writeItemAssumeCapacity(result);
+ try this.enqueueItem(result);
}
} else |err| {}
}
}
- this.bundler.resetStore();
-
- while (this.resolve_queue.readItem()) |resolved| {
- try this.processFile(resolved);
- }
-
// Normally, this is automatic
// However, since we only do the parsing pass, it may not get imported automatically.
- if (this.has_jsx) {
+ if (bundler.options.jsx.parse) {
+ defer this.bundler.resetStore();
if (this.bundler.resolver.resolve(
this.bundler.fs.top_level_dir,
this.bundler.options.jsx.import_source,
- .stmt,
+ .require,
)) |new_jsx_runtime| {
- if (!this.resolved_paths.contains(new_jsx_runtime.path_pair.primary.text)) {
- try this.processFile(new_jsx_runtime);
- }
+ try this.enqueueItem(new_jsx_runtime);
} else |err| {}
}
- if (this.has_jsx and this.bundler.options.jsx.supports_fast_refresh) {
+ if (include_refresh_runtime) {
+ defer this.bundler.resetStore();
+
if (this.bundler.resolver.resolve(
this.bundler.fs.top_level_dir,
- "react-refresh/runtime",
+ this.bundler.options.jsx.refresh_runtime,
.require,
)) |refresh_runtime| {
- if (!this.resolved_paths.contains(refresh_runtime.path_pair.primary.text)) {
- try this.processFile(refresh_runtime);
- }
+ try this.enqueueItem(refresh_runtime);
} else |err| {}
}
+ this.bundler.resetStore();
+
+ while (this.resolve_queue.readItem()) |resolved| {
+ try this.processFile(resolved);
+ }
+
if (this.log.errors > 0) {
// We stop here because if there are errors we don't know if the bundle is valid
// This manifests as a crash when sorting through the module list because we may have added files to the bundle which were never actually finished being added.
@@ -891,6 +969,8 @@ pub fn NewBundler(cache_files: bool) type {
fn processImportRecord(this: *GenerateNodeModuleBundle, import_record: ImportRecord) !void {}
threadlocal var package_key_buf: [512]u8 = undefined;
threadlocal var file_path_buf: [4096]u8 = undefined;
+
+ threadlocal var temp_stmts_list: std.ArrayList(js_ast.Stmt) = undefined;
fn processFile(this: *GenerateNodeModuleBundle, _resolve: _resolver.Result) !void {
var resolve = _resolve;
if (resolve.is_external) return;
@@ -929,7 +1009,7 @@ pub fn NewBundler(cache_files: bool) type {
var opts = js_parser.Parser.Options.init(jsx, loader);
opts.transform_require_to_import = false;
opts.enable_bundling = true;
-
+ opts.warn_about_unbundled_modules = false;
var ast: js_ast.Ast = (try bundler.resolver.caches.js.parse(
bundler.allocator,
opts,
@@ -1024,12 +1104,26 @@ pub fn NewBundler(cache_files: bool) type {
const E = js_ast.E;
const Expr = js_ast.Expr;
const Stmt = js_ast.Stmt;
+
+ var prepend_part: js_ast.Part = undefined;
+ var needs_prepend_part = false;
+ if (ast.parts.len > 1) {
+ for (ast.parts) |part| {
+ if (part.tag != .none and part.stmts.len > 0) {
+ prepend_part = part;
+ needs_prepend_part = true;
+ break;
+ }
+ }
+ }
+
if (ast.parts.len == 0) {
if (comptime isDebug) {
Output.prettyErrorln("Missing AST for file: {s}", .{file_path.text});
Output.flush();
}
}
+
var part = &ast.parts[ast.parts.len - 1];
var new_stmts: [1]Stmt = undefined;
var register_args: [3]Expr = undefined;
@@ -1058,6 +1152,7 @@ pub fn NewBundler(cache_files: bool) type {
.data = .{ .b_identifier = &exports_binding },
},
};
+
var closure = E.Arrow{
.args = &cjs_args,
.body = .{
@@ -1101,6 +1196,9 @@ pub fn NewBundler(cache_files: bool) type {
var writer = js_printer.NewFileWriter(this.tmpfile);
var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols});
+ // It should only have one part.
+ ast.parts = ast.parts[ast.parts.len - 1 ..];
+
const code_offset = @truncate(u32, try this.tmpfile.getPos());
const written = @truncate(
u32,
@@ -1119,6 +1217,8 @@ pub fn NewBundler(cache_files: bool) type {
.indent = 0,
.module_hash = module_id,
.runtime_imports = ast.runtime_imports,
+ .prepend_part_value = &prepend_part,
+ .prepend_part_key = if (needs_prepend_part) closure.body.stmts.ptr else null,
},
Linker,
&bundler.linker,
@@ -1219,7 +1319,6 @@ pub fn NewBundler(cache_files: bool) type {
try this.resolve_queue.writeItem(_resolved_import.*);
if (BundledModuleData.get(this, resolved_import)) |module| {
- import_record.path = Fs.Path.init(module.import_path);
get_or_put_result.value_ptr.* = module.module_id;
}
} else |err| {
@@ -1301,7 +1400,12 @@ pub fn NewBundler(cache_files: bool) type {
errdefer bundler.resetStore();
var file_path = resolve_result.path_pair.primary;
- file_path.pretty = allocator.dupe(u8, bundler.fs.relativeTo(file_path.text)) catch unreachable;
+
+ if (strings.indexOf(file_path.text, bundler.fs.top_level_dir)) |i| {
+ file_path.pretty = file_path.text[i + bundler.fs.top_level_dir.len ..];
+ } else {
+ file_path.pretty = allocator.dupe(u8, bundler.fs.relativeTo(file_path.text)) catch unreachable;
+ }
var old_bundler_allocator = bundler.allocator;
bundler.allocator = allocator;
@@ -1711,9 +1815,10 @@ pub fn NewBundler(cache_files: bool) type {
opts.enable_bundling = false;
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 and bundler.options.platform != .speedy and client_entry_point_ == null;
+ opts.features.hot_module_reloading = bundler.options.hot_module_reloading and bundler.options.platform != .speedy; // and client_entry_point_ == null;
opts.features.react_fast_refresh = opts.features.hot_module_reloading and jsx.parse and bundler.options.jsx.supports_fast_refresh;
opts.filepath_hash_for_hmr = file_hash orelse 0;
+ opts.warn_about_unbundled_modules = bundler.options.platform != .speedy;
const value = (bundler.resolver.caches.js.parse(allocator, opts, bundler.options.define, bundler.log, &source) catch null) orelse return null;
return ParseResult{
.ast = value,
diff --git a/src/cli.zig b/src/cli.zig
index da4186a67..3158f421c 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -229,7 +229,7 @@ pub const Cli = struct {
var route_config: ?Api.RouteConfig = null;
if (args.option("--static-dir")) |public_dir| {
- route_config = route_config orelse Api.RouteConfig{ .extensions = &.{} };
+ route_config = route_config orelse Api.RouteConfig{ .extensions = &.{}, .dir = &.{} };
route_config.?.static_dir = public_dir;
}
@@ -429,6 +429,7 @@ pub const Cli = struct {
var env_loader = this_bundler.env;
wait_group = sync.WaitGroup.init();
var server_bundler_generator_thread: ?std.Thread = null;
+ var generated_server = false;
if (this_bundler.options.framework) |*framework| {
if (framework.toAPI(allocator, this_bundler.fs.top_level_dir, false)) |_server_conf| {
const ServerBundleGeneratorThread = struct {
@@ -506,6 +507,7 @@ pub const Cli = struct {
this_bundler.router,
},
);
+ generated_server = true;
} else {
ServerBundleGeneratorThread.generate(
log_,
@@ -516,6 +518,7 @@ pub const Cli = struct {
loaded_route_config,
this_bundler.router,
);
+ generated_server = true;
}
}
}
@@ -551,7 +554,7 @@ pub const Cli = struct {
const indent = comptime " ";
Output.prettyln(indent ++ "<d>{d:6}ms elapsed", .{@intCast(u32, elapsed)});
- if (server_bundler_generator_thread != null) {
+ if (generated_server) {
Output.prettyln(indent ++ "<r>Saved to ./{s}, ./{s}", .{ filepath, server_bundle_filepath });
} else {
Output.prettyln(indent ++ "<r>Saved to ./{s}", .{filepath});
diff --git a/src/http.zig b/src/http.zig
index c47c1dbec..4a528626c 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -496,7 +496,7 @@ pub const RequestContext = struct {
allocator: *std.mem.Allocator,
printer: js_printer.BufferPrinter,
timer: std.time.Timer,
-
+ count: usize = 0,
pub const WatchBuildResult = struct {
value: Value,
id: u32,
@@ -513,6 +513,13 @@ pub const RequestContext = struct {
};
};
pub fn build(this: *WatchBuilder, id: u32, from_timestamp: u32) !WatchBuildResult {
+ if (this.count == 0) {
+ var writer = try js_printer.BufferWriter.init(this.allocator);
+ this.printer = js_printer.BufferPrinter.init(writer);
+ this.printer.ctx.append_null_byte = false;
+ }
+
+ defer this.count += 1;
var log = logger.Log.init(this.allocator);
errdefer log.deinit();
@@ -789,7 +796,7 @@ pub const RequestContext = struct {
entry_point,
) catch |err| {
Output.prettyErrorln(
- "<r>JavaScript VM failed to start.\n<red>{s}:<r> while loading <r><b>\"\"",
+ "<r>JavaScript VM failed to start.\n<red>{s}:<r> while loading <r><b>\"{s}\"",
.{ @errorName(err), entry_point },
);
@@ -920,18 +927,20 @@ pub const RequestContext = struct {
message_buffer: MutableString,
pub var open_websockets: std.ArrayList(*WebsocketHandler) = undefined;
var open_websockets_lock = sync.RwLock.init();
- pub fn addWebsocket(ctx: *RequestContext) !*WebsocketHandler {
+ pub fn addWebsocket(ctx: *RequestContext, server: *Server) !*WebsocketHandler {
open_websockets_lock.lock();
defer open_websockets_lock.unlock();
- var clone = try ctx.allocator.create(WebsocketHandler);
+
+ var clone = try server.allocator.create(WebsocketHandler);
clone.ctx = ctx.*;
clone.conn = ctx.conn.*;
- clone.message_buffer = try MutableString.init(ctx.allocator, 0);
+ clone.message_buffer = try MutableString.init(server.allocator, 0);
clone.ctx.conn = &clone.conn;
- var printer_writer = try js_printer.BufferWriter.init(ctx.allocator);
+
+ var printer_writer = try js_printer.BufferWriter.init(server.allocator);
clone.builder = WatchBuilder{
- .allocator = ctx.allocator,
+ .allocator = server.allocator,
.bundler = ctx.bundler,
.printer = js_printer.BufferPrinter.init(printer_writer),
.timer = ctx.timer,
@@ -940,6 +949,7 @@ pub const RequestContext = struct {
clone.websocket = Websocket.Websocket.create(&clone.conn, SOCKET_FLAGS);
clone.tombstone = false;
+
try open_websockets.append(clone);
return clone;
}
@@ -1017,6 +1027,9 @@ pub const RequestContext = struct {
Output.enable_ansi_colors = stderr.isTty();
js_ast.Stmt.Data.Store.create(self.ctx.allocator);
js_ast.Expr.Data.Store.create(self.ctx.allocator);
+ self.builder.printer = js_printer.BufferPrinter.init(
+ js_printer.BufferWriter.init(self.ctx.allocator) catch unreachable,
+ );
_handle(self) catch {};
}
@@ -1032,7 +1045,6 @@ pub const RequestContext = struct {
switch (err) {
error.BadRequest => {
try ctx.sendBadRequest();
- ctx.done();
},
else => {
return err;
@@ -1277,9 +1289,9 @@ pub const RequestContext = struct {
return false;
}
- pub fn handleWebsocket(ctx: *RequestContext) anyerror!void {
+ pub fn handleWebsocket(ctx: *RequestContext, server: *Server) anyerror!void {
ctx.controlled = true;
- var handler = try WebsocketHandler.addWebsocket(ctx);
+ var handler = try WebsocketHandler.addWebsocket(ctx, server);
_ = try std.Thread.spawn(.{}, WebsocketHandler.handle, .{handler});
}
@@ -1590,14 +1602,14 @@ pub const RequestContext = struct {
}
}
- pub fn handleReservedRoutes(ctx: *RequestContext) !bool {
+ pub fn handleReservedRoutes(ctx: *RequestContext, server: *Server) !bool {
if (strings.eqlComptime(ctx.url.extname, "jsb") and ctx.bundler.options.node_modules_bundle != null) {
try ctx.sendJSB();
return true;
}
if (strings.eqlComptime(ctx.url.path, "_api.hmr")) {
- try ctx.handleWebsocket();
+ try ctx.handleWebsocket(server);
return true;
}
@@ -1879,7 +1891,7 @@ pub const Server = struct {
req_ctx.keep_alive = false;
}
- var finished = req_ctx.handleReservedRoutes() catch |err| {
+ var finished = req_ctx.handleReservedRoutes(server) catch |err| {
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
return;
};
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 21756bf9f..29ede6112 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -8,7 +8,9 @@ const logger = @import("../../logger.zig");
const Api = @import("../../api/schema.zig").Api;
const options = @import("../../options.zig");
const Bundler = @import("../../bundler.zig").ServeBundler;
+const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
const js_printer = @import("../../js_printer.zig");
+const js_parser = @import("../../js_parser.zig");
const hash_map = @import("../../hash_map.zig");
const http = @import("../../http.zig");
const ImportKind = ast.ImportKind;
@@ -22,7 +24,7 @@ const Runtime = @import("../../runtime.zig");
const Router = @import("./api/router.zig");
const ImportRecord = ast.ImportRecord;
const DotEnv = @import("../../env_loader.zig");
-
+const ParseResult = @import("../../bundler.zig").ParseResult;
pub const GlobalClasses = [_]type{
Request.Class,
Response.Class,
@@ -275,6 +277,7 @@ pub const VirtualMachine = struct {
process: js.JSObjectRef = null,
flush_list: std.ArrayList(string),
+ entry_point: ServerEntryPoint = undefined,
pub var vm_loaded = false;
pub var vm: *VirtualMachine = undefined;
@@ -390,9 +393,77 @@ pub const VirtualMachine = struct {
Runtime.Runtime.byteCodeCacheFile(&vm.bundler.fs.fs) orelse 0,
),
};
+ // This is all complicated because the imports have to be linked and we want to run the printer on it
+ // so it consistently handles bundled imports
+ // we can't take the shortcut of just directly importing the file, sadly.
+ } else if (strings.eqlComptime(_specifier, main_file_name)) {
+ var bundler = &vm.bundler;
+ var old = vm.bundler.log;
+ vm.bundler.log = log;
+ vm.bundler.linker.log = log;
+ vm.bundler.resolver.log = log;
+ defer {
+ vm.bundler.log = old;
+ vm.bundler.linker.log = old;
+ vm.bundler.resolver.log = old;
+ }
+
+ var jsx = bundler.options.jsx;
+ jsx.parse = false;
+ var opts = js_parser.Parser.Options.init(jsx, .js);
+ opts.enable_bundling = false;
+ opts.transform_require_to_import = true;
+ opts.can_import_from_bundle = bundler.options.node_modules_bundle != null;
+ opts.features.hot_module_reloading = false;
+ opts.features.react_fast_refresh = false;
+ opts.filepath_hash_for_hmr = 0;
+ opts.warn_about_unbundled_modules = false;
+ const main_ast = (bundler.resolver.caches.js.parse(vm.allocator, opts, bundler.options.define, bundler.log, &vm.entry_point.source) catch null) orelse {
+ return error.ParseError;
+ };
+ var parse_result = ParseResult{ .source = vm.entry_point.source, .ast = main_ast, .loader = .js, .input_fd = null };
+ var file_path = Fs.Path.init(bundler.fs.top_level_dir);
+ file_path.name.dir = bundler.fs.top_level_dir;
+ file_path.name.base = "bun:main";
+ try bundler.linker.link(
+ file_path,
+ &parse_result,
+ .absolute_path,
+ true,
+ );
+
+ 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 = false;
+
+ source_code_printer_loaded = true;
+ }
+
+ source_code_printer.ctx.reset();
+
+ var written = try vm.bundler.print(
+ parse_result,
+ @TypeOf(&source_code_printer),
+ &source_code_printer,
+ .esm,
+ );
+
+ if (written == 0) {
+ return error.PrintingErrorWriteFailed;
+ }
+
+ return ResolvedSource{
+ .source_code = ZigString.init(vm.allocator.dupe(u8, source_code_printer.ctx.written) catch unreachable),
+ .specifier = ZigString.init(std.mem.span(main_file_name)),
+ .source_url = ZigString.init(std.mem.span(main_file_name)),
+ .hash = 0,
+ .bytecodecache_fd = 0,
+ };
}
const specifier = normalizeSpecifier(_specifier);
+
std.debug.assert(std.fs.path.isAbsolute(specifier)); // if this crashes, it means the resolver was skipped.
const path = Fs.Path.init(specifier);
@@ -496,10 +567,14 @@ pub const VirtualMachine = struct {
} else if (vm.node_modules != null and strings.eql(specifier, vm.bundler.linker.nodeModuleBundleImportPath())) {
ret.path = vm.bundler.linker.nodeModuleBundleImportPath();
return;
+ } else if (strings.eqlComptime(specifier, main_file_name)) {
+ ret.result = null;
+ ret.path = vm.entry_point.source.path.text;
+ return;
}
const result = try vm.bundler.resolver.resolve(
- Fs.PathName.init(source).dirWithTrailingSlash(),
+ if (!strings.eqlComptime(source, main_file_name)) Fs.PathName.init(source).dirWithTrailingSlash() else VirtualMachine.vm.bundler.fs.top_level_dir,
specifier,
.stmt,
);
@@ -605,6 +680,7 @@ pub const VirtualMachine = struct {
return slice;
}
+ const main_file_name: string = "bun:main";
threadlocal var errors_stack: [256]*c_void = undefined;
pub fn fetch(ret: *ErrorableResolvedSource, global: *JSGlobalObject, specifier: ZigString, source: ZigString) callconv(.C) void {
var log = logger.Log.init(vm.bundler.allocator);
@@ -705,11 +781,10 @@ pub const VirtualMachine = struct {
}
}
- pub fn loadEntryPoint(this: *VirtualMachine, entry_point: string) !*JSInternalPromise {
- var path = this.bundler.normalizeEntryPointPath(entry_point);
-
- this.main = entry_point;
- var promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(path));
+ pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) !*JSInternalPromise {
+ try this.entry_point.generate(@TypeOf(this.bundler), &this.bundler, Fs.PathName.init(entry_path), main_file_name);
+ this.main = entry_path;
+ var promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(main_file_name)));
this.global.vm().drainMicrotasks();
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 8e388a103..f6bd9e24e 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -3757,6 +3757,14 @@ pub const Part = struct {
// This is true if this file has been marked as live by the tree shaking
// algorithm.
is_live: bool = false,
+
+ tag: Tag = Tag.none,
+
+ pub const Tag = enum {
+ none,
+ jsx_import,
+ };
+
pub const SymbolUseMap = AutoHashMap(Ref, Symbol.Use);
pub fn jsonStringify(self: *const Part, options: std.json.StringifyOptions, writer: anytype) !void {
return std.json.stringify(self.stmts, options, writer);
diff --git a/src/js_lexer.zig b/src/js_lexer.zig
index cf1470cab..2ddd25f61 100644
--- a/src/js_lexer.zig
+++ b/src/js_lexer.zig
@@ -689,6 +689,7 @@ pub const Lexer = struct {
// what caused us to get on this slow path in the first place.
if (lexer.code_point == '\\') {
try lexer.step();
+
if (lexer.code_point != 'u') {
try lexer.syntaxError();
}
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 4bc70eb01..cf36c89e6 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -1680,6 +1680,8 @@ pub const Parser = struct {
filepath_hash_for_hmr: u32 = 0,
features: RuntimeFeatures = RuntimeFeatures{},
+ warn_about_unbundled_modules: bool = true,
+
// Used when bundling node_modules
enable_bundling: bool = false,
transform_require_to_import: bool = true,
@@ -1822,6 +1824,8 @@ pub const Parser = struct {
var parts = List(js_ast.Part).init(p.allocator);
try p.appendPart(&parts, stmts);
+ var did_import_fast_refresh = false;
+
// Auto-import JSX
if (p.options.jsx.parse) {
const jsx_symbol: Symbol = p.symbols.items[p.jsx_runtime_ref.inner_index];
@@ -1869,7 +1873,7 @@ pub const Parser = struct {
@intCast(u32, @boolToInt(jsx_filename_symbol.use_count_estimate > 0));
const imports_count =
- @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) + @intCast(u32, std.math.max(jsx_factory_symbol.use_count_estimate, jsx_fragment_symbol.use_count_estimate));
+ @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) + @intCast(u32, std.math.max(jsx_factory_symbol.use_count_estimate, jsx_fragment_symbol.use_count_estimate)) + @intCast(u32, @boolToInt(p.options.features.react_fast_refresh));
const stmts_count = imports_count + 1;
const symbols_count: u32 = imports_count + decls_count;
const loc = logger.Loc{ .start = 0 };
@@ -1896,7 +1900,7 @@ pub const Parser = struct {
const automatic_identifier = p.e(E.Identifier{ .ref = automatic_namespace_ref }, loc);
const dot_call_target = brk: {
- if (p.options.can_import_from_bundle) {
+ if (p.options.can_import_from_bundle or p.options.enable_bundling) {
break :brk automatic_identifier;
} else {
require_call_args_base[require_call_args_i] = automatic_identifier;
@@ -1973,7 +1977,7 @@ pub const Parser = struct {
const dot_call_target = brk: {
// var react = $aopaSD123();
- if (p.options.can_import_from_bundle) {
+ if (p.options.can_import_from_bundle or p.options.enable_bundling) {
break :brk classic_identifier;
} else {
require_call_args_base[require_call_args_i] = classic_identifier;
@@ -2051,6 +2055,36 @@ pub const Parser = struct {
declared_symbols_i += 1;
}
+ if (p.options.features.react_fast_refresh) {
+ defer did_import_fast_refresh = true;
+ const refresh_runtime_symbol: Symbol = p.symbols.items[p.jsx_refresh_runtime_ref.inner_index];
+
+ declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_refresh_runtime_ref, .is_top_level = true };
+ declared_symbols_i += 1;
+
+ const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime);
+ jsx_part_stmts[stmt_i] = p.s(S.Import{
+ .namespace_ref = p.jsx_refresh_runtime_ref,
+ .star_name_loc = loc,
+ .is_single_line = true,
+ .import_record_index = import_record_id,
+ }, loc);
+
+ stmt_i += 1;
+ p.named_imports.put(
+ p.jsx_refresh_runtime_ref,
+ js_ast.NamedImport{
+ .alias = refresh_runtime_symbol.original_name,
+ .alias_is_star = true,
+ .alias_loc = loc,
+ .namespace_ref = p.jsx_refresh_runtime_ref,
+ .import_record_index = import_record_id,
+ },
+ ) catch unreachable;
+ p.is_import_item.put(p.jsx_refresh_runtime_ref, true) catch unreachable;
+ import_records[import_record_i] = import_record_id;
+ }
+
jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls[0..decl_i] }, loc);
stmt_i += 1;
@@ -2059,10 +2093,50 @@ pub const Parser = struct {
.declared_symbols = declared_symbols,
.import_record_indices = import_records,
.symbol_uses = SymbolUseMap.init(p.allocator),
+ .tag = .jsx_import,
}) catch unreachable;
}
}
+ if (!did_import_fast_refresh and p.options.features.react_fast_refresh) {
+ std.debug.assert(!p.options.enable_bundling);
+ var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1);
+ const loc = logger.Loc.Empty;
+ const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime);
+ var import_stmt = p.s(S.Import{
+ .namespace_ref = p.jsx_refresh_runtime_ref,
+ .star_name_loc = loc,
+ .is_single_line = true,
+ .import_record_index = import_record_id,
+ }, loc);
+
+ p.recordUsage(p.jsx_refresh_runtime_ref);
+ const refresh_runtime_symbol: Symbol = p.symbols.items[p.jsx_refresh_runtime_ref.inner_index];
+ p.named_imports.put(
+ p.jsx_refresh_runtime_ref,
+ js_ast.NamedImport{
+ .alias = refresh_runtime_symbol.original_name,
+ .alias_is_star = true,
+ .alias_loc = loc,
+ .namespace_ref = p.jsx_refresh_runtime_ref,
+ .import_record_index = import_record_id,
+ },
+ ) catch unreachable;
+ p.is_import_item.put(p.jsx_refresh_runtime_ref, true) catch unreachable;
+ var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1);
+ import_records[0] = import_record_id;
+ declared_symbols[0] = .{ .ref = p.jsx_refresh_runtime_ref, .is_top_level = true };
+ var part_stmts = try p.allocator.alloc(Stmt, 1);
+ part_stmts[0] = import_stmt;
+
+ before.append(js_ast.Part{
+ .stmts = part_stmts,
+ .declared_symbols = declared_symbols,
+ .import_record_indices = import_records,
+ .symbol_uses = SymbolUseMap.init(p.allocator),
+ }) catch unreachable;
+ }
+
// Analyze cross-part dependencies for tree shaking and code splitting
var exports_kind = js_ast.ExportsKind.none;
const uses_exports_ref = p.symbols.items[p.exports_ref.inner_index].use_count_estimate > 0;
@@ -2102,6 +2176,7 @@ pub const Parser = struct {
) catch unreachable;
}
}
+
if (has_cjs_imports) {
var import_records = try p.allocator.alloc(u32, p.cjs_import_stmts.items.len);
var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, p.cjs_import_stmts.items.len);
@@ -2467,6 +2542,9 @@ pub fn NewParser(
jsx_automatic_ref: js_ast.Ref = Ref.None,
jsx_classic_ref: js_ast.Ref = Ref.None,
+ // only applicable when is_react_fast_refresh_enabled
+ jsx_refresh_runtime_ref: js_ast.Ref = Ref.None,
+
jsx_source_list_ref: js_ast.Ref = Ref.None,
// Imports (both ES6 and CommonJS) are tracked at the top level
@@ -2651,9 +2729,11 @@ pub fn NewParser(
}, state.loc);
}
- // Use a debug log so people can see this if they want to
- const r = js_lexer.rangeOfIdentifier(p.source, state.loc);
- p.log.addRangeDebug(p.source, r, "This \"import\" expression will not be bundled because the argument is not a string literal") catch unreachable;
+ if (p.options.warn_about_unbundled_modules) {
+ // Use a debug log so people can see this if they want to
+ const r = js_lexer.rangeOfIdentifier(p.source, state.loc);
+ p.log.addRangeDebug(p.source, r, "This \"import\" expression cannot be bundled because the argument is not a string literal") catch unreachable;
+ }
return p.e(E.Import{
.expr = arg,
@@ -3242,10 +3322,18 @@ pub fn NewParser(
if (p.options.features.hot_module_reloading) {
p.hmr_module_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "__hmrModule");
- p.runtime_imports.__HMRModule = try p.declareSymbol(.hoisted, logger.Loc.Empty, "__HMRModule");
+ if (is_react_fast_refresh_enabled) {
+ p.jsx_refresh_runtime_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "__RefreshRuntime");
+
+ p.runtime_imports.__FastRefreshModule = try p.declareSymbol(.hoisted, logger.Loc.Empty, "__FastRefreshModule");
+ p.recordUsage(p.runtime_imports.__FastRefreshModule.?);
+ } else {
+ p.runtime_imports.__HMRModule = try p.declareSymbol(.hoisted, logger.Loc.Empty, "__HMRModule");
+ p.recordUsage(p.runtime_imports.__HMRModule.?);
+ }
+
p.runtime_imports.__HMRClient = try p.declareSymbol(.hoisted, logger.Loc.Empty, "__HMRClient");
p.recordUsage(p.hmr_module_ref);
- p.recordUsage(p.runtime_imports.__HMRModule.?);
p.recordUsage(p.runtime_imports.__HMRClient.?);
} else {
p.runtime_imports.__export = p.exports_ref;
@@ -11254,7 +11342,7 @@ pub fn NewParser(
// error from the unbundled require() call failing.
if (e_.args.len == 1) {
return p.require_transposer.maybeTransposeIf(e_.args[0], null);
- } else {
+ } else if (p.options.warn_about_unbundled_modules) {
const r = js_lexer.rangeOfIdentifier(p.source, e_.target.loc);
p.log.addRangeDebug(p.source, r, "This call to \"require\" will not be bundled because it has multiple arguments") catch unreachable;
}
@@ -13697,9 +13785,17 @@ pub fn NewParser(
// 6. __hmrModule.onSetExports = (newExports) => {
// $named_exports_count __hmrExport_exportName = newExports.exportName; <-- ${named_exports_count}
// }
+
+ // if there are no exports:
+ // - there shouldn't be an export statement
+ // - we don't need the S.Local for wrapping the exports
+ // We still call exportAll just with an empty object.
+ const has_any_exports = named_exports_count > 0;
+
+ const toplevel_stmts_count = 4 + (@intCast(usize, @boolToInt(has_any_exports)) * 2);
var _stmts = p.allocator.alloc(
Stmt,
- end_iife_stmts_count + 6 + (named_exports_count * 2) + imports_count + exports_from_count,
+ end_iife_stmts_count + toplevel_stmts_count + (named_exports_count * 2) + imports_count + exports_from_count,
) catch unreachable;
// Normally, we'd have to grow that inner function's stmts list by one
// But we can avoid that by just making them all use this same array.
@@ -13715,7 +13811,7 @@ pub fn NewParser(
// Second pass: move any imports from the part's stmts array to the new stmts
var imports_list = curr_stmts[0..imports_count];
curr_stmts = curr_stmts[imports_list.len..];
- var toplevel_stmts = curr_stmts[0..6];
+ var toplevel_stmts = curr_stmts[0..toplevel_stmts_count];
curr_stmts = curr_stmts[toplevel_stmts.len..];
var exports_from = curr_stmts[0..exports_from_count];
curr_stmts = curr_stmts[exports_from.len..];
@@ -13752,21 +13848,27 @@ pub fn NewParser(
}
var args_list: []Expr = if (isDebug) &Prefill.HotModuleReloading.DebugEnabledArgs else &Prefill.HotModuleReloading.DebugDisabled;
- var call_args = try p.allocator.alloc(Expr, 3);
- var new_call_args = call_args[0..2];
+
+ const new_call_args_count: usize = if (is_react_fast_refresh_enabled) 3 else 2;
+ var call_args = try p.allocator.alloc(Expr, new_call_args_count + 1);
+ var new_call_args = call_args[0..new_call_args_count];
var hmr_module_ident = p.e(E.Identifier{ .ref = p.hmr_module_ref }, logger.Loc.Empty);
new_call_args[0] = p.e(E.Number{ .value = @intToFloat(f64, p.options.filepath_hash_for_hmr) }, logger.Loc.Empty);
// This helps us provide better error messages
new_call_args[1] = p.e(E.String{ .utf8 = p.source.path.pretty }, logger.Loc.Empty);
+ if (is_react_fast_refresh_enabled) {
+ new_call_args[2] = p.e(E.Identifier{ .ref = p.jsx_refresh_runtime_ref }, logger.Loc.Empty);
+ }
var exports_dot = p.e(E.Dot{
.target = hmr_module_ident,
.name = ExportsStringName,
.name_loc = logger.Loc.Empty,
}, logger.Loc.Empty);
var hmr_module_class_ident = p.e(E.Identifier{ .ref = p.runtime_imports.__HMRClient.? }, logger.Loc.Empty);
+ var toplevel_stmts_i: u8 = 0;
// HMRClient.activate(true)
- toplevel_stmts[0] = p.s(
+ toplevel_stmts[toplevel_stmts_i] = p.s(
S.SExpr{
.value = p.e(E.Call{
.target = p.e(E.Dot{
@@ -13780,16 +13882,23 @@ pub fn NewParser(
},
logger.Loc.Empty,
);
+ toplevel_stmts_i += 1;
var decls = try p.allocator.alloc(G.Decl, 2 + named_exports_count);
var first_decl = decls[0..2];
// We cannot rely on import.meta.url because if we import it within a blob: url, it will be nonsensical
// var __hmrModule = new HMRModule(123123124, "/index.js"), __exports = __hmrModule.exports;
+ const hmr_import_ref = if (is_react_fast_refresh_enabled) p.runtime_imports.__FastRefreshModule else p.runtime_imports.__HMRModule;
first_decl[0] = G.Decl{
.binding = p.b(B.Identifier{ .ref = p.hmr_module_ref }, logger.Loc.Empty),
.value = p.e(E.New{
.args = new_call_args,
- .target = p.e(E.Identifier{ .ref = p.runtime_imports.__HMRModule.? }, logger.Loc.Empty),
+ .target = p.e(
+ E.Identifier{
+ .ref = hmr_import_ref.?,
+ },
+ logger.Loc.Empty,
+ ),
}, logger.Loc.Empty),
};
first_decl[1] = G.Decl{
@@ -13914,13 +14023,15 @@ pub fn NewParser(
logger.Loc.Empty,
);
- toplevel_stmts[1] = p.s(
+ toplevel_stmts[toplevel_stmts_i] = p.s(
S.Local{
.decls = first_decl,
},
logger.Loc.Empty,
);
+ toplevel_stmts_i += 1;
+
var func = p.e(
E.Function{
.func = .{
@@ -13936,7 +14047,7 @@ pub fn NewParser(
);
// (__hmrModule._load = function())()
- toplevel_stmts[2] = p.s(
+ toplevel_stmts[toplevel_stmts_i] = p.s(
S.SExpr{
.value = p.e(
E.Call{
@@ -13959,13 +14070,19 @@ pub fn NewParser(
logger.Loc.Empty,
);
- toplevel_stmts[3] = p.s(
- S.Local{
- .decls = exports_decls,
- },
- logger.Loc.Empty,
- );
- toplevel_stmts[4] = p.s(
+ toplevel_stmts_i += 1;
+
+ if (has_any_exports) {
+ toplevel_stmts[toplevel_stmts_i] = p.s(
+ S.Local{
+ .decls = exports_decls,
+ },
+ logger.Loc.Empty,
+ );
+ toplevel_stmts_i += 1;
+ }
+
+ toplevel_stmts[toplevel_stmts_i] = p.s(
S.SExpr{
.value = Expr.assign(
p.e(
@@ -13992,12 +14109,15 @@ pub fn NewParser(
},
logger.Loc.Empty,
);
- toplevel_stmts[5] = p.s(
- S.ExportClause{
- .items = export_clauses,
- },
- logger.Loc.Empty,
- );
+ toplevel_stmts_i += 1;
+ if (export_clauses.len > 0) {
+ toplevel_stmts[toplevel_stmts_i] = p.s(
+ S.ExportClause{
+ .items = export_clauses,
+ },
+ logger.Loc.Empty,
+ );
+ }
part.stmts = stmts_for_top_part;
}
diff --git a/src/logger.zig b/src/logger.zig
index 9d1db56e6..a5f4eeae2 100644
--- a/src/logger.zig
+++ b/src/logger.zig
@@ -596,6 +596,8 @@ pub const Source = struct {
}
pub fn rangeOfString(self: *const Source, loc: Loc) Range {
+ if (loc.start < 0) return Range.None;
+
const text = self.contents[loc.i()..];
if (text.len == 0) {
diff --git a/src/options.zig b/src/options.zig
index 44a4f06b3..583ecb5dd 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -429,6 +429,7 @@ pub const JSX = struct {
import_source: string = "react/jsx-dev-runtime",
classic_import_source: string = "react",
package_name: []const u8 = "react",
+ refresh_runtime: string = "react-refresh/runtime",
supports_fast_refresh: bool = false,
jsx: string = "jsxDEV",
@@ -1465,6 +1466,7 @@ pub const Framework = struct {
pub const RouteConfig = struct {
dir: string = "",
+ possible_dirs: []const string = &[_]string{},
// Frameworks like Next.js (and others) use a special prefix for bundled/transpiled assets
// This is combined with "origin" when printing import paths
@@ -1517,13 +1519,26 @@ pub const RouteConfig = struct {
pub fn fromApi(router_: Api.RouteConfig, allocator: *std.mem.Allocator) !RouteConfig {
var router = zero();
- var router_dir: string = std.mem.trimRight(u8, router_.dir orelse "", "/\\");
var static_dir: string = std.mem.trimRight(u8, router_.static_dir orelse "", "/\\");
var asset_prefix: string = std.mem.trimRight(u8, router_.asset_prefix orelse "", "/\\");
- if (router_dir.len != 0) {
- router.dir = router_dir;
- router.routes_enabled = true;
+ switch (router_.dir.len) {
+ 0 => {},
+ 1 => {
+ router.dir = std.mem.trimRight(u8, router_.dir[0], "/\\");
+ router.routes_enabled = router.dir.len > 0;
+ },
+ else => {
+ router.possible_dirs = router_.dir;
+ for (router_.dir) |dir| {
+ const trimmed = std.mem.trimRight(u8, dir, "/\\");
+ if (trimmed.len > 0) {
+ router.dir = trimmed;
+ }
+ }
+
+ router.routes_enabled = router.dir.len > 0;
+ },
}
if (static_dir.len > 0) {
diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig
index 3191f0a59..5a6c2f5b0 100644
--- a/src/resolver/package_json.zig
+++ b/src/resolver/package_json.zig
@@ -201,11 +201,51 @@ pub const PackageJSON = struct {
if (!pair.router.routes_enabled) {
if (framework_object.expr.asProperty("router")) |router| {
if (router.expr.asProperty("dir")) |route_dir| {
- if (route_dir.expr.asString(allocator)) |str| {
- if (str.len > 0) {
- pair.router.dir = str;
- pair.loaded_routes = true;
- }
+ switch (route_dir.expr.data) {
+ .e_string => |estr| {
+ const str = estr.string(allocator) catch unreachable;
+ if (str.len > 0) {
+ pair.router.dir = str;
+ pair.router.possible_dirs = &[_]string{};
+
+ pair.loaded_routes = true;
+ }
+ },
+ .e_array => |array| {
+ var count: usize = 0;
+ for (array.items) |item| {
+ count += @boolToInt(item.data == .e_string and item.data.e_string.utf8.len > 0);
+ }
+ switch (count) {
+ 0 => {},
+ 1 => {
+ const str = array.items[0].data.e_string.string(allocator) catch unreachable;
+ if (str.len > 0) {
+ pair.router.dir = str;
+ pair.router.possible_dirs = &[_]string{};
+
+ pair.loaded_routes = true;
+ }
+ },
+ else => {
+ const list = allocator.alloc(string, count) catch unreachable;
+
+ var list_i: usize = 0;
+ for (array.items) |item| {
+ if (item.data == .e_string and item.data.e_string.utf8.len > 0) {
+ list[list_i] = item.data.e_string.string(allocator) catch unreachable;
+ list_i += 1;
+ }
+ }
+
+ pair.router.dir = list[0];
+ pair.router.possible_dirs = list;
+
+ pair.loaded_routes = true;
+ },
+ }
+ },
+ else => {},
}
}
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 9ff5a4eab..db8ee6fd3 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -403,12 +403,25 @@ pub fn NewResolver(cache_files: bool) type {
}
if (pair.loaded_routes) {
- var parts = [_]string{ r.fs.top_level_dir, std.fs.path.sep_str, pair.router.dir };
- const abs = r.fs.join(&parts);
- // must end in trailing slash
- var realpath = std.os.realpath(abs, &buf) catch return error.RoutesDirNotFound;
- var out = try r.allocator.alloc(u8, realpath.len + 1);
- std.mem.copy(u8, out, realpath);
+ const chosen_dir: string = brk: {
+ if (pair.router.possible_dirs.len > 0) {
+ for (pair.router.possible_dirs) |route_dir| {
+ var parts = [_]string{ r.fs.top_level_dir, std.fs.path.sep_str, route_dir };
+ const abs = r.fs.join(&parts);
+ // must end in trailing slash
+ break :brk (std.os.realpath(abs, &buf) catch continue);
+ }
+ return error.MissingRouteDir;
+ } else {
+ var parts = [_]string{ r.fs.top_level_dir, std.fs.path.sep_str, pair.router.dir };
+ const abs = r.fs.join(&parts);
+ // must end in trailing slash
+ break :brk std.os.realpath(abs, &buf) catch return error.MissingRouteDir;
+ }
+ };
+
+ var out = try r.allocator.alloc(u8, chosen_dir.len + 1);
+ std.mem.copy(u8, out, chosen_dir);
out[out.len - 1] = '/';
pair.router.dir = out;
pair.router.routes_enabled = true;
diff --git a/src/runtime.js b/src/runtime.js
index affd3bc4d..3afbf80f7 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -70,9 +70,13 @@ export var __commonJS = (cb, name) => {
typeof mod.exports === "object" &&
!("default" in mod.exports)
) {
+ var defaultValue = mod.exports;
Object.defineProperty(mod.exports, "default", {
get() {
- return mod.exports;
+ return defaultValue;
+ },
+ set(value) {
+ defaultValue = value;
},
enumerable: true,
});
diff --git a/src/runtime.version b/src/runtime.version
index 4cda1e7bd..2de34e9ad 100644
--- a/src/runtime.version
+++ b/src/runtime.version
@@ -1 +1 @@
-7d529e0a92952d7a \ No newline at end of file
+bf074417343b306f \ No newline at end of file
diff --git a/src/runtime.zig b/src/runtime.zig
index 33a8fbd0c..04161a524 100644
--- a/src/runtime.zig
+++ b/src/runtime.zig
@@ -62,6 +62,7 @@ pub const Runtime = struct {
lazy_export: ?Ref = null,
__HMRModule: ?Ref = null,
__HMRClient: ?Ref = null,
+ __FastRefreshModule: ?Ref = null,
pub const all = [_][]const u8{
"__name",
@@ -78,6 +79,7 @@ pub const Runtime = struct {
"lazy_export",
"__HMRModule",
"__HMRClient",
+ "__FastRefreshModule",
};
pub const Name = "<RUNTIME";
@@ -156,6 +158,11 @@ pub const Runtime = struct {
return Entry{ .key = 11, .value = val };
}
},
+ 12 => {
+ if (@field(this.runtime_imports, all[12])) |val| {
+ return Entry{ .key = 12, .value = val };
+ }
+ },
else => {
return null;
@@ -213,6 +220,7 @@ pub const Runtime = struct {
9 => @field(imports, all[9]),
10 => @field(imports, all[10]),
11 => @field(imports, all[11]),
+ 12 => @field(imports, all[12]),
else => null,
};
}