diff options
author | 2021-06-11 10:53:55 -0700 | |
---|---|---|
committer | 2021-06-11 10:53:55 -0700 | |
commit | dc3309d130c171f75d4e416f3569faa9c985d123 (patch) | |
tree | 322a6ad3b280f47804f8e429b8089bee71777c80 | |
parent | d2e1c7955be8989eac8169ab46af93492e706d0b (diff) | |
download | bun-dc3309d130c171f75d4e416f3569faa9c985d123.tar.gz bun-dc3309d130c171f75d4e416f3569faa9c985d123.tar.zst bun-dc3309d130c171f75d4e416f3569faa9c985d123.zip |
alright basic stuff works now. still bugs with JS parser
Former-commit-id: a1dd2a2a32819c23541eed2acfd585e5fd6e5688
-rw-r--r-- | .vscode/launch.json | 14 | ||||
-rw-r--r-- | src/api/schema.peechy | 10 | ||||
-rw-r--r-- | src/bundler.zig | 58 | ||||
-rw-r--r-- | src/http.zig | 80 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 33 | ||||
-rw-r--r-- | src/js_printer.zig | 172 | ||||
-rw-r--r-- | src/linker.zig | 52 | ||||
-rw-r--r-- | src/node_module_bundle.zig | 4 | ||||
-rw-r--r-- | src/runtime.js | 163 | ||||
-rw-r--r-- | src/runtime.version | 2 | ||||
-rw-r--r-- | src/runtime.zig | 13 |
11 files changed, 431 insertions, 170 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index c6dd132c6..414818c10 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -40,6 +40,20 @@ { "type": "lldb", "request": "launch", + "name": "Demo Lazy Build", + "program": "${workspaceFolder}/build/debug/macos-x86_64/esdev", + "args": [ + "./src/index.tsx", + "--resolve=lazy", + "--public-url=http://localhost:9000/" + ], + "cwd": "${workspaceFolder}/demos/simple-react", + "console": "internalConsole" + }, + + { + "type": "lldb", + "request": "launch", "name": "Demo Build", "program": "${workspaceFolder}/build/debug/macos-x86_64/esdev", "args": [ diff --git a/src/api/schema.peechy b/src/api/schema.peechy index 69f904e41..87f44dbaf 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -47,14 +47,17 @@ struct StringPointer { struct JavascriptBundledModule { // package-relative path including file extension StringPointer path; + + // Source code StringPointer code; + + // index into JavascriptBundle.packages uint32 package_id; - // This is the export id, which is hash(path).hash(package.hash) + // The ESM export is this id ("$" + number.toString(16)) uint32 id; // This lets us efficiently compare strings ignoring the extension - // If we instead omit the extension byte path_extname_length; } @@ -68,7 +71,7 @@ struct JavascriptBundledPackage { } struct JavascriptBundle { - // This has to be sorted by ${package_prefix}path + // These are sorted alphabetically so you can do binary search JavascriptBundledModule[] modules; JavascriptBundledPackage[] packages; @@ -78,6 +81,7 @@ struct JavascriptBundle { // generated by hashing all ${name}@${version} in sorted order byte[] app_package_json_dependencies_hash; byte[] import_from_name; + // This is what StringPointer refers to byte[] manifest_string; } diff --git a/src/bundler.zig b/src/bundler.zig index bb10af3f4..e2905b443 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -288,7 +288,12 @@ pub fn NewBundler(cache_files: bool) type { try generator.appendBytes(&initial_header); // If we try to be smart and rely on .written, it turns out incorrect const code_start_pos = try this.tmpfile.getPos(); - try generator.appendBytes(runtime.SourceContent ++ "\n\n"); + if (isDebug) { + try generator.appendBytes(runtime.Runtime.sourceContent()); + try generator.appendBytes("\n\n"); + } else { + try generator.appendBytes(comptime runtime.Runtime.sourceContent() ++ "\n\n"); + } if (bundler.log.level == .verbose) { bundler.resolver.debug_logs = try DebugLogs.init(allocator); @@ -374,10 +379,10 @@ pub fn NewBundler(cache_files: bool) type { javascript_bundle.modules = this.module_list.items; javascript_bundle.packages = sorted_package_list; javascript_bundle.manifest_string = this.header_string_buffer.list.items; - var etag_bytes: [8]u8 = undefined; const etag_u64 = hasher.final(); - std.mem.writeIntNative(u64, &etag_bytes, etag_u64); - javascript_bundle.etag = &etag_bytes; + // We store the etag as a ascii hex encoded u64 + // This is so we can send the bytes directly in the HTTP server instead of formatting it as hex each time. + javascript_bundle.etag = try std.fmt.allocPrint(allocator, "{x}", .{etag_u64}); javascript_bundle.generated_at = @truncate(u32, @intCast(u64, std.time.milliTimestamp())); const basename = std.fs.path.basename(destination); @@ -469,10 +474,10 @@ pub fn NewBundler(cache_files: bool) type { defer this.scan_pass_result.reset(); defer this.bundler.resetStore(); var file_path = resolve.path_pair.primary; + var hasher = std.hash.Wyhash.init(0); // If we're in a node_module, build that almost normally if (resolve.is_from_node_modules) { - var hasher = std.hash.Wyhash.init(0); switch (loader) { .jsx, .tsx, @@ -773,7 +778,42 @@ pub fn NewBundler(cache_files: bool) type { continue; } + // Always enqueue unwalked import paths, but if it's not a node_module, we don't care about the hash try this.resolve_queue.writeItem(_resolved_import.*); + + // trim node_modules/${package.name}/ from the string to save space + // This reduces metadata size by about 30% for a large-ish file + // A future optimization here could be to reuse the string from the original path + var node_module_root = strings.indexOf(resolved_import.path_pair.primary.text, node_module_root_string) orelse continue; + + const package_json: *const PackageJSON = (resolved_import.package_json orelse (this.bundler.resolver.packageJSONForResolvedNodeModule(resolved_import) orelse { + this.log.addWarningFmt( + &source, + import_record.range.loc, + this.allocator, + "Failed to find package.json for \"{s}\". This will be unresolved and might break at runtime. If it's external, you could add it to the external list.", + .{ + resolved_import.path_pair.primary.text, + }, + ) catch {}; + continue; + })); + + // omit node_modules + node_module_root += node_module_root_string.len; + // // omit package name + node_module_root += package_json.name.len; + node_module_root += 1; + + // It should be the first index, not the last to support bundling multiple of the same package + import_record.path = Fs.Path.init( + resolved_import.path_pair.primary.text[node_module_root..], + ); + + hasher = std.hash.Wyhash.init(0); + hasher.update(import_record.path.text); + hasher.update(std.mem.asBytes(&package_json.hash)); + get_or_put_result.entry.value = @truncate(u32, hasher.final()); } else |err| {} } }, @@ -1019,6 +1059,7 @@ pub fn NewBundler(cache_files: bool) type { var opts = js_parser.Parser.Options.init(jsx, loader); opts.enable_bundling = bundler.options.node_modules_bundle != null; opts.transform_require_to_import = true; + opts.can_import_from_bundle = bundler.options.node_modules_bundle != null; const value = (bundler.resolver.caches.js.parse(allocator, opts, bundler.options.define, bundler.log, &source) catch null) orelse return null; return ParseResult{ .ast = value, @@ -1179,7 +1220,7 @@ pub fn NewBundler(cache_files: bool) type { if (strings.eqlComptime(relative_path, "__runtime.js")) { return ServeResult{ - .file = options.OutputFile.initBuf(runtime.SourceContent, "__runtime.js", .js), + .file = options.OutputFile.initBuf(runtime.Runtime.sourceContent(), "__runtime.js", .js), .mime_type = MimeType.javascript, }; } @@ -1430,7 +1471,7 @@ pub fn NewBundler(cache_files: bool) type { if (bundler.linker.any_needs_runtime) { try bundler.output_files.append( - options.OutputFile.initBuf(runtime.SourceContent, bundler.linker.runtime_source_path, .js), + options.OutputFile.initBuf(runtime.Runtime.sourceContent(), bundler.linker.runtime_source_path, .js), ); } @@ -1640,7 +1681,7 @@ pub const Transformer = struct { jsx.parse = loader.isJSX(); var file_op = options.OutputFile.FileOperation.fromFile(file_to_write.handle, output_path.pretty); - const parser_opts = js_parser.Parser.Options.init(jsx.*, loader); + var parser_opts = js_parser.Parser.Options.init(jsx.*, loader); file_op.is_tmpdir = false; output_file.value = .{ .move = file_op }; @@ -1650,6 +1691,7 @@ pub const Transformer = struct { file_op.fd = file.handle; var parser = try js_parser.Parser.init(parser_opts, log, _source, transformer.define, allocator); + parser_opts.can_import_from_bundle = false; const result = try parser.parse(); const ast = result.ast; diff --git a/src/http.zig b/src/http.zig index 8e3d4b2c1..2d54a45c0 100644 --- a/src/http.zig +++ b/src/http.zig @@ -186,7 +186,7 @@ pub const RequestContext = struct { const status_text = switch (code) { 200...299 => "OK", 300...399 => "=>", - 400...499 => "UH", + 400...499 => "DID YOU KNOW YOU CAN MAKE THIS SAY WHATEVER YOU WANT", 500...599 => "ERR", else => @compileError("Invalid code passed to printStatusLine"), }; @@ -333,7 +333,39 @@ pub const RequestContext = struct { ctx.done(); } + pub fn sendJSB(ctx: *RequestContext) !void { + const node_modules_bundle = ctx.bundler.options.node_modules_bundle orelse unreachable; + defer ctx.done(); + ctx.appendHeader("ETag", node_modules_bundle.bundle.etag); + ctx.appendHeader("Content-Type", "text/javascript"); + ctx.appendHeader("Cache-Control", "immutable, max-age=99999"); + + if (ctx.header("If-None-Match")) |etag_header| { + if (std.mem.eql(u8, node_modules_bundle.bundle.etag, etag_header.value)) { + try ctx.sendNotModified(); + return; + } + } + + const content_length = node_modules_bundle.container.code_length.? - node_modules_bundle.codeStartOffset(); + try ctx.writeStatus(200); + try ctx.prepareToSendBody(content_length, false); + + _ = try std.os.sendfile( + ctx.conn.client.socket.fd, + node_modules_bundle.fd, + node_modules_bundle.codeStartOffset(), + content_length, + &[_]std.os.iovec_const{}, + &[_]std.os.iovec_const{}, + 0, + ); + } + pub fn handleGet(ctx: *RequestContext) !void { + if (strings.eqlComptime(ctx.url.extname, "jsb") and ctx.bundler.options.node_modules_bundle != null) { + return try ctx.sendJSB(); + } const result = try ctx.bundler.buildFile( &ctx.log, ctx.allocator, @@ -596,34 +628,34 @@ pub const RequestContext = struct { .copy, .move => |file| { defer std.os.close(file.fd); - if (result.mime_type.category != .html) { - // hash(absolute_file_path, size, mtime) - var weak_etag = std.hash.Wyhash.init(1); - weak_etag_buffer[0] = 'W'; - weak_etag_buffer[1] = '/'; - weak_etag.update(result.file.input.text); - std.mem.writeIntNative(u64, weak_etag_tmp_buffer[0..8], result.file.size); - weak_etag.update(weak_etag_tmp_buffer[0..8]); - - if (result.file.mtime) |mtime| { - std.mem.writeIntNative(i128, weak_etag_tmp_buffer[0..16], mtime); - weak_etag.update(weak_etag_tmp_buffer[0..16]); - } + // if (result.mime_type.category != .html) { + // hash(absolute_file_path, size, mtime) + var weak_etag = std.hash.Wyhash.init(1); + weak_etag_buffer[0] = 'W'; + weak_etag_buffer[1] = '/'; + weak_etag.update(result.file.input.text); + std.mem.writeIntNative(u64, weak_etag_tmp_buffer[0..8], result.file.size); + weak_etag.update(weak_etag_tmp_buffer[0..8]); + + if (result.file.mtime) |mtime| { + std.mem.writeIntNative(i128, weak_etag_tmp_buffer[0..16], mtime); + weak_etag.update(weak_etag_tmp_buffer[0..16]); + } - const etag_content_slice = std.fmt.bufPrintIntToSlice(weak_etag_buffer[2..], weak_etag.final(), 16, true, .{}); - const complete_weak_etag = weak_etag_buffer[0 .. etag_content_slice.len + 2]; + const etag_content_slice = std.fmt.bufPrintIntToSlice(weak_etag_buffer[2..], weak_etag.final(), 16, true, .{}); + const complete_weak_etag = weak_etag_buffer[0 .. etag_content_slice.len + 2]; - ctx.appendHeader("ETag", complete_weak_etag); + ctx.appendHeader("ETag", complete_weak_etag); - if (ctx.header("If-None-Match")) |etag_header| { - if (strings.eql(complete_weak_etag, etag_header.value)) { - try ctx.sendNotModified(); - return; - } + if (ctx.header("If-None-Match")) |etag_header| { + if (strings.eql(complete_weak_etag, etag_header.value)) { + try ctx.sendNotModified(); + return; } - } else { - ctx.appendHeader("Cache-Control", "no-cache"); } + // } else { + // ctx.appendHeader("Cache-Control", "no-cache"); + // } switch (result.file.size) { 0 => { diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 0918298f8..23e9cfad0 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -1748,8 +1748,9 @@ pub const Parser = struct { if (jsx_symbol.use_count_estimate > 0) { require_call_args_base[require_call_args_i] = p.e(E.Identifier{ .ref = automatic_namespace_ref }, loc); require_call_args_i += 1; + var require_call_args = require_call_args_base[0..require_call_args_i]; - var require_call = p.callRuntime(loc, "__require", require_call_args); + var require_call = p.callRequireOrBundledRequire(require_call_args); declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_runtime_ref, .is_top_level = true }; declared_symbols_i += 1; @@ -1816,7 +1817,7 @@ pub const Parser = struct { if (jsx_classic_symbol.use_count_estimate > 0) { require_call_args_base[require_call_args_i] = p.e(E.Identifier{ .ref = classic_namespace_ref }, loc); var require_call_args = require_call_args_base[require_call_args_i..]; - var require_call = p.callRuntime(loc, "__require", require_call_args); + var require_call = p.callRequireOrBundledRequire(require_call_args); if (jsx_factory_symbol.use_count_estimate > 0) { declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_factory_ref, .is_top_level = true }; declared_symbols_i += 1; @@ -1888,7 +1889,7 @@ pub const Parser = struct { jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls }, loc); - before.append(js_ast.Part{ + after.append(js_ast.Part{ .stmts = jsx_part_stmts, .declared_symbols = declared_symbols, .import_record_indices = import_records, @@ -1946,7 +1947,7 @@ pub const Parser = struct { }; } - before.append(js_ast.Part{ + after.append(js_ast.Part{ .stmts = p.cjs_import_stmts.items, .declared_symbols = declared_symbols, .import_record_indices = import_records, @@ -1966,16 +1967,24 @@ pub const Parser = struct { after_len + parts_len, ); + + var remaining_parts = _parts; if (before_len > 0) { - std.mem.copy(js_ast.Part, _parts, before.toOwnedSlice()); + var parts_to_copy = before.toOwnedSlice(); + std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); + remaining_parts = remaining_parts[parts_to_copy.len..]; } if (parts_len > 0) { - std.mem.copy(js_ast.Part, _parts[before_len .. before_len + parts_len], parts.toOwnedSlice()); + var parts_to_copy = parts.toOwnedSlice(); + std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); + remaining_parts = remaining_parts[parts_to_copy.len..]; } if (after_len > 0) { - std.mem.copy(js_ast.Part, _parts[before_len + parts_len .. _parts.len], after.toOwnedSlice()); + var parts_to_copy = after.toOwnedSlice(); + std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); } + parts_slice = _parts; } else { after.deinit(); @@ -2641,6 +2650,16 @@ pub fn NewParser( } } + // If we're auto-importing JSX and it's bundled, we use the bundled version + // This means we need to transform from require(react) to react() + pub fn callRequireOrBundledRequire(p: *P, require_args: []Expr) Expr { + if (p.options.can_import_from_bundle) { + return p.e(E.Call{ .target = require_args[0] }, require_args[0].loc); + } else { + return p.callRuntime(require_args[0].loc, "__require", require_args); + } + } + pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) !void { if (p.named_exports.get(alias)) |name| { // Duplicate exports are an error diff --git a/src/js_printer.zig b/src/js_printer.zig index 0e7ca5201..f45feea91 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2145,8 +2145,7 @@ pub fn NewPrinter( p.print("export "); } else { if (rewrite_esm_to_cjs) { - p.printSymbol(p.options.runtime_imports.__export.?); - p.print("."); + p.print("var "); p.printSymbol(nameRef); p.print(" = "); } @@ -2164,7 +2163,22 @@ pub fn NewPrinter( p.printSpace(); p.printSymbol(nameRef); p.printFunc(s.func); - p.printNewline(); + + if (rewrite_esm_to_cjs and s.func.flags.is_export) { + p.printSemicolonAfterStatement(); + } else { + p.printNewline(); + } + + if (rewrite_esm_to_cjs and s.func.flags.is_export) { + p.printIndent(); + p.printSymbol(p.options.runtime_imports.__export.?); + p.print("."); + p.printSymbol(nameRef); + p.print(" = "); + p.printSymbol(nameRef); + p.printSemicolonAfterStatement(); + } }, .s_class => |s| { // Give an extra newline for readaiblity @@ -2174,23 +2188,40 @@ pub fn NewPrinter( p.printIndent(); p.printSpaceBeforeIdentifier(); + const nameRef = s.class.class_name.?.ref.?; if (s.is_export) { if (!rewrite_esm_to_cjs) { p.print("export "); } if (rewrite_esm_to_cjs) { - p.printSymbol(p.options.runtime_imports.__export.?); - p.print("."); - p.printSymbol(s.class.class_name.?.ref.?); + p.print("var "); + p.printSymbol(nameRef); p.print(" = "); } } p.print("class "); - p.printSymbol(s.class.class_name.?.ref.?); + p.printSymbol(nameRef); p.printClass(s.class); - p.printNewline(); + + if (rewrite_esm_to_cjs and s.is_export) { + p.printSemicolonAfterStatement(); + } else { + p.printNewline(); + } + + if (rewrite_esm_to_cjs) { + if (s.is_export) { + p.printIndent(); + p.printSymbol(p.options.runtime_imports.__export.?); + p.print("."); + p.printSymbol(nameRef); + p.print(" = "); + p.printSymbol(nameRef); + p.printSemicolonAfterStatement(); + } + } }, .s_empty => |s| { p.printIndent(); @@ -2206,10 +2237,7 @@ pub fn NewPrinter( p.printIndent(); p.printSpaceBeforeIdentifier(); - if (rewrite_esm_to_cjs) { - p.printSymbol(p.options.runtime_imports.__export.?); - p.print(".default ="); - } else { + if (!rewrite_esm_to_cjs) { p.print("export default"); } @@ -2217,21 +2245,38 @@ pub fn NewPrinter( switch (s.value) { .expr => |expr| { + if (rewrite_esm_to_cjs) { + p.printSymbol(p.options.runtime_imports.__export.?); + p.print(".default = "); + } + // Functions and classes must be wrapped to avoid confusion with their statement forms p.export_default_start = p.writer.written; p.printExpr(expr, .comma, ExprFlag.None()); p.printSemicolonAfterStatement(); return; }, + .stmt => |s2| { switch (s2.data) { - .s_function => { - const func = s2.getFunction(); + .s_function => |func| { p.printSpaceBeforeIdentifier(); + if (rewrite_esm_to_cjs) { + if (func.func.name) |name| { + // p.print("var "); + // p.printSymbol(name.ref.?); + // p.print(" = "); + } else { + p.printSymbol(p.options.runtime_imports.__export.?); + p.print(".default = "); + } + } + if (func.func.flags.is_async) { p.print("async "); } p.print("function"); + if (func.func.flags.is_generator) { p.print("*"); p.printSpace(); @@ -2240,22 +2285,65 @@ pub fn NewPrinter( } if (func.func.name) |name| { - p.printSymbol(name.ref orelse Global.panic("Internal error: Expected func to have a name ref\n{s}", .{func})); + p.printSymbol(name.ref.?); } + p.printFunc(func.func); - p.printNewline(); + + if (rewrite_esm_to_cjs) { + p.printSemicolonAfterStatement(); + + if (rewrite_esm_to_cjs) { + if (func.func.name) |name| { + p.printIndent(); + p.printSpaceBeforeIdentifier(); + p.printSymbol(p.options.runtime_imports.__export.?); + p.print(".default = "); + p.printSymbol(name.ref.?); + p.printSemicolonAfterStatement(); + } + } + } else { + p.printNewline(); + } }, - .s_class => { - const class = s2.getClass(); + .s_class => |class| { p.printSpaceBeforeIdentifier(); + + if (rewrite_esm_to_cjs) { + if (class.class.class_name) |name| { + // p.print("var "); + // p.printSymbol(name.ref.?); + // p.print(" = "); + } else { + p.printSymbol(p.options.runtime_imports.__export.?); + p.print(".default = "); + } + } + if (class.class.class_name) |name| { p.print("class "); p.printSymbol(name.ref orelse Global.panic("Internal error: Expected class to have a name ref\n{s}", .{class})); } else { p.print("class"); } + p.printClass(class.class); - p.printNewline(); + + if (rewrite_esm_to_cjs) { + p.printSemicolonAfterStatement(); + + if (class.class.class_name) |name| { + p.printIndent(); + p.printSpaceBeforeIdentifier(); + p.printSymbol(p.options.runtime_imports.__export.?); + p.print(".default = "); + p.printSymbol(name.ref.?); + p.printSemicolonAfterStatement(); + } + } else { + p.printNewline(); + } }, else => { Global.panic("Internal error: unexpected export default stmt data {s}", .{s}); @@ -2346,11 +2434,12 @@ pub fn NewPrinter( const last = s.items.len - 1; for (s.items) |item, i| { const name = p.renamer.nameForSymbol(item.name.ref.?); - p.printIdentifier(name); + p.printClauseAlias(item.alias); + if (!strings.eql(name, item.alias)) { p.print(":"); p.printSpace(); - p.printClauseAlias(item.alias); + p.printIdentifier(name); } if (i < last) { @@ -2434,6 +2523,7 @@ pub fn NewPrinter( p.printSymbol(p.options.runtime_imports.lazy_export.?); p.print("("); p.printSymbol(p.options.runtime_imports.__export.?); + p.print(","); // Avoid initializing an entire component library because you imported one icon p.printLoadFromBundleWithoutCall(s.import_record_index); @@ -2778,6 +2868,46 @@ pub fn NewPrinter( return; } + } else if (record.is_bundled) { + p.print("import {"); + p.printLoadFromBundleWithoutCall(s.import_record_index); + p.print(" as "); + p.printSymbol(s.namespace_ref); + p.print("} from "); + p.printQuotedUTF8(record.path.text, false); + p.printSemicolonAfterStatement(); + + if (s.items.len > 0) { + p.printIndent(); + p.printSpaceBeforeIdentifier(); + p.print("var {"); + for (s.items) |item, i| { + p.print(item.alias); + const name = p.renamer.nameForSymbol(item.name.ref.?); + if (!strings.eql(name, item.alias)) { + p.print(":"); + p.printSymbol(item.name.ref.?); + } + + if (i < s.items.len - 1) { + p.print(", "); + } + } + p.print("} = "); + p.printSymbol(s.namespace_ref); + p.print("()"); + p.printSemicolonAfterStatement(); + } else if (s.default_name) |default_name| { + p.printIndent(); + p.printSpaceBeforeIdentifier(); + p.print("var {default: "); + p.printSymbol(default_name.ref.?); + p.print("} = "); + p.printSymbol(s.namespace_ref); + p.print("()"); + p.printSemicolonAfterStatement(); + } + return; } p.print("import"); diff --git a/src/linker.zig b/src/linker.zig index c8f792c4c..15b2e82e6 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -166,7 +166,7 @@ pub fn NewLinker(comptime BundlerType: type) type { }; import_record.is_bundled = true; - import_record.path.text = node_modules_bundle.str(found_module.path); + import_record.path.text = node_modules_bundle.bundle.import_from_name; import_record.module_id = found_module.id; needs_bundle = true; continue; @@ -257,6 +257,56 @@ pub fn NewLinker(comptime BundlerType: type) type { .range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 }, }; } + + const ImportStatementSorter = struct { + import_records: []ImportRecord, + pub fn lessThan(ctx: @This(), lhs: js_ast.Stmt, rhs: js_ast.Stmt) bool { + switch (lhs.data) { + .s_import => |li| { + switch (rhs.data) { + .s_import => |ri| { + const a = ctx.import_records[li.import_record_index]; + const b = ctx.import_records[ri.import_record_index]; + if (a.is_bundled and !b.is_bundled) { + return false; + } else { + return true; + } + }, + else => { + return true; + }, + } + }, + else => { + switch (rhs.data) { + .s_import => |ri| { + const a = ctx.import_records[ri.import_record_index]; + if (!a.is_bundled) { + return false; + } else { + return true; + } + }, + else => { + return true; + }, + } + }, + } + } + }; + + // std.sort.sort(comptime T: type, items: []T, context: anytype, comptime lessThan: fn(context:@TypeOf(context), lhs:T, rhs:T)bool) + + // Change the import order so that any bundled imports appear last + // This is to make it so the bundle (which should be quite large) is least likely to block rendering + if (needs_bundle) { + const sorter = ImportStatementSorter{ .import_records = result.ast.import_records }; + for (result.ast.parts) |*part, i| { + std.sort.sort(js_ast.Stmt, part.stmts, sorter, ImportStatementSorter.lessThan); + } + } } const ImportPathsList = allocators.BSSStringList(512, 128); diff --git a/src/node_module_bundle.zig b/src/node_module_bundle.zig index ec85e4fb1..8642336e3 100644 --- a/src/node_module_bundle.zig +++ b/src/node_module_bundle.zig @@ -317,6 +317,10 @@ pub const NodeModuleBundle = struct { Output.prettyln(indent ++ "<b>{d:6} packages", .{this.bundle.packages.len}); } + pub fn codeStartOffset(this: *const NodeModuleBundle) u32 { + return @intCast(u32, jsbundle_prefix.len); + } + pub fn printSummaryFromDisk( comptime StreamType: type, input: StreamType, diff --git a/src/runtime.js b/src/runtime.js index ddcdaddd7..f065a1c4e 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -9,21 +9,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor; export var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); -export var __reExport = (target, module, desc) => { - if ((module && typeof module === "object") || typeof module === "function") { - for (let key of __getOwnPropNames(module)) - if (!__hasOwnProp.call(target, key) && key !== "default") - __defProp(target, key, { - get: () => module[key], - enumerable: - !(desc = __getOwnPropDesc(module, key)) || desc.enumerable, - }); - } - return target; -}; - -export var $$lz = (target, module, props) => { - for (key in props) { +// lazy require to prevent loading one icon from a design system +export var $$lzy = (target, module, props) => { + for (let key in props) { if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, { get: () => module()[props[key]], @@ -48,20 +36,52 @@ export var __toModule = (module) => { ); }; -export var __commonJS = - (cb, name, mod = {}) => - () => { - return ( - mod, - // friendly name for any errors while requiring - (__name(cb, name), - cb((mod = { exports: {} }), mod.exports), - __name(mod, name), - mod), - // Don't add a name to exports incase it exports "name" - mod.exports - ); - }; +export var __commonJS = (cb, name) => { + var mod = {}; + var has_run = false; + + return { + [`#init_${name}`]() { + if (has_run) { + return mod.exports; + } + has_run = true; + __name(cb); + + mod = { exports: {} }; + + cb(mod, mod.exports); + + // If it's a default-only export, don't crash if they call .default on the module + if ( + typeof mod.exports === "object" && + "default" in mod.exports && + Object.keys(mod.exports).len === 1 + ) { + mod.exports = mod.exports.default; + Object.defineProperty(mod.exports, "default", { + get() { + return mod.exports; + }, + enumerable: false, + }); + // If it's a namespace export without .default, pretend .default is the same as mod.exports + } else if ( + typeof mod.exports === "object" && + !("default" in mod.exports) + ) { + Object.defineProperty(mod.exports, "default", { + get() { + return mod.exports; + }, + enumerable: false, + }); + } + + return mod.exports; + }, + }[`#init_${name}`]; +}; var require_cache = new WeakMap(); @@ -69,14 +89,14 @@ export var __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { RequireFailedError: class {}, }; -__name( - __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.RequireFailedError, - "RequireFailedError" -); -__name( - __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.Module, - "Module" -); +// __name( +// __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.RequireFailedError, +// "RequireFailedError" +// ); +// __name( +// __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.Module, +// "Module" +// ); export var __require = (namespace) => { var entry = require_cache.get(namespace); @@ -125,73 +145,8 @@ if ( new Map(); } -// Like require() but accepts: -// - package_json_hash -// - package_json_name -// - module_path -// This locks the require to a specific package version -// This is also slightly faster to generate since we don't need to allocate additional strings -// for import paths -export var $$r = (package_json_hash, package_json_name, module_path) => { - // Symbol is useful here because it gives us: - // - A built-in "description" property for providing friendlier errors. Potentially shared across multiple tabs depending on engine implementaion, saving a little memory. - // - Guranteed uniqueness, letting the JS engine deal with mapping import paths to unique identifiers instead of us - // - Relatively cheap in-memory size, costs one machine word - // - Shouldn't cause de-opts from mixing short strings and long strings - // - auto-incrementing integer ID is an alternative, but a stable key means we don't worry about generating a manifest ahead of time and we don't worry about the order of the module declarations - return __load( - // The displayed description is everything after the first slash - Symbol.for(`${package_json_hash}/${package_json_name}/${module_path}`) - ); -}; - -export var __load = (id) => { - if ( - globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.has( - id - ) - ) { - return globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.get( - id - ); - } - - const namespace = - globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_REGISTRY.get( - id - ); - - const target = - Object.prototype.hasOwnProperty.call(namespace, "default") && - Object.keys(namespace).length === 1 - ? namespace["default"] - : namespace; - - if (typeof target !== "function") { - throw new __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.RequireFailedError( - `Couldn't find module "${namespace.description.substring( - namespace.description.indexOf("/") + 1 - )}"` - ); - } - - globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.set( - id, - target() - ); - - // It might be slightly slower to do this extra get, but only returning from the map - // might be a better hint to a JS engine that "target" doesn't escape this function - return globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE.get( - id - ); -}; - -export var $$m = (package_json_hash, package_json_name, module_path, cb) => { - globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_REGISTRY.set( - Symbol.for(`${package_json_hash}/${package_json_name}/${module_path}`), - __commonJS(cb, `${package_json_name}/${module_path}`) - ); +export var $$m = (package_json_name, module_path, cb) => { + return __commonJS(cb, `${package_json_name}/${module_path}`); }; export var __name = (target, name) => { diff --git a/src/runtime.version b/src/runtime.version index b48df3c1c..d7de1f38d 100644 --- a/src/runtime.version +++ b/src/runtime.version @@ -1 +1 @@ -9e9dfde98026fc70
\ No newline at end of file +727d9b7284639b19
\ No newline at end of file diff --git a/src/runtime.zig b/src/runtime.zig index 5eceba772..f74ed8067 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -2,8 +2,19 @@ const options = @import("./options.zig"); usingnamespace @import("ast/base.zig"); usingnamespace @import("global.zig"); const std = @import("std"); -pub const SourceContent = @embedFile("./runtime.js"); +pub const ProdSourceContent = @embedFile("./runtime.js"); + pub const Runtime = struct { + pub fn sourceContent() string { + if (isDebug) { + var runtime_path = std.fs.path.join(std.heap.c_allocator, &[_]string{ std.fs.path.dirname(@src().file).?, "runtime.js" }) catch unreachable; + const file = std.fs.openFileAbsolute(runtime_path, .{}) catch unreachable; + defer file.close(); + return file.readToEndAlloc(std.heap.c_allocator, (file.stat() catch unreachable).size) catch unreachable; + } else { + return ProdSourceContent; + } + } pub var version_hash = @embedFile("./runtime.version"); pub fn version() string { return version_hash; |