diff options
-rw-r--r-- | .vscode/launch.json | 2 | ||||
-rw-r--r-- | src/api/schema.d.ts | 1 | ||||
-rw-r--r-- | src/api/schema.js | 8 | ||||
-rw-r--r-- | src/api/schema.peechy | 4 | ||||
-rw-r--r-- | src/api/schema.zig | 5 | ||||
-rw-r--r-- | src/bundler.zig | 153 | ||||
-rw-r--r-- | src/import_record.zig | 2 | ||||
-rw-r--r-- | src/js_ast.zig | 45 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 71 | ||||
-rw-r--r-- | src/js_printer.zig | 194 | ||||
-rw-r--r-- | src/linker.zig | 2 | ||||
-rw-r--r-- | src/runtime.js | 101 | ||||
-rw-r--r-- | src/runtime.version | 2 | ||||
-rw-r--r-- | src/runtime.zig | 27 | ||||
-rw-r--r-- | src/test/fixtures/optional-chain-polyfill.js | 2 |
15 files changed, 540 insertions, 79 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index 453fdbecb..c6dd132c6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "request": "launch", "name": "Dev Launch", "program": "${workspaceFolder}/build/debug/macos-x86_64/esdev", - "args": ["nql-define.js", "--resolve=dev"], + "args": ["optional-chain-polyfill.js", "--resolve=disable"], "cwd": "${workspaceFolder}/src/test/fixtures", "console": "internalConsole" }, diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index 8d8ae010e..b3eefd8dd 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -137,6 +137,7 @@ type uint32 = number; path: StringPointer; code: StringPointer; package_id: uint32; + id: uint32; path_extname_length: byte; } diff --git a/src/api/schema.js b/src/api/schema.js index 3f0bb8179..e72112e32 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -167,6 +167,7 @@ function decodeJavascriptBundledModule(bb) { result["path"] = decodeStringPointer(bb); result["code"] = decodeStringPointer(bb); result["package_id"] = bb.readUint32(); + result["id"] = bb.readUint32(); result["path_extname_length"] = bb.readByte(); return result; } @@ -194,6 +195,13 @@ function encodeJavascriptBundledModule(message, bb) { throw new Error("Missing required field \"package_id\""); } + var value = message["id"]; + if (value != null) { + bb.writeUint32(value); + } else { + throw new Error("Missing required field \"id\""); + } + var value = message["path_extname_length"]; if (value != null) { bb.writeByte(value); diff --git a/src/api/schema.peechy b/src/api/schema.peechy index f893c525b..69f904e41 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -49,6 +49,10 @@ struct JavascriptBundledModule { StringPointer path; StringPointer code; uint32 package_id; + + // This is the export id, which is hash(path).hash(package.hash) + uint32 id; + // This lets us efficiently compare strings ignoring the extension // If we instead omit the extension byte path_extname_length; diff --git a/src/api/schema.zig b/src/api/schema.zig index 4a2e44f9b..319bc9b6f 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -454,6 +454,9 @@ code: StringPointer, /// package_id package_id: u32 = 0, +/// id +id: u32 = 0, + /// path_extname_length path_extname_length: u8 = 0, @@ -464,6 +467,7 @@ pub fn decode(reader: anytype) anyerror!JavascriptBundledModule { this.path = try reader.readValue(StringPointer); this.code = try reader.readValue(StringPointer); this.package_id = try reader.readValue(u32); + this.id = try reader.readValue(u32); this.path_extname_length = try reader.readValue(u8); return this; } @@ -472,6 +476,7 @@ pub fn encode(this: *const @This(), writer: anytype) anyerror!void { try writer.writeValue(this.path); try writer.writeValue(this.code); try writer.writeInt(this.package_id); + try writer.writeInt(this.id); try writer.writeInt(this.path_extname_length); } diff --git a/src/bundler.zig b/src/bundler.zig index 630e96772..d347aaedb 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -472,6 +472,7 @@ pub fn NewBundler(cache_files: bool) type { // 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, @@ -489,8 +490,10 @@ pub fn NewBundler(cache_files: bool) type { var jsx = bundler.options.jsx; jsx.parse = loader.isJSX(); + var opts = js_parser.Parser.Options.init(jsx, loader); - opts.output_commonjs = true; + opts.transform_require_to_import = false; + opts.enable_bundling = true; var ast: js_ast.Ast = (try bundler.resolver.caches.js.parse( bundler.allocator, @@ -522,7 +525,6 @@ pub fn NewBundler(cache_files: bool) type { "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, - source.path.text, }, ) catch {}; continue; @@ -533,18 +535,19 @@ pub fn NewBundler(cache_files: bool) type { // 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 unreachable; // // omit package name - // node_module_root += package_json.name.len; + node_module_root += package_json.name.len; // omit node_modules node_module_root += node_module_root_string.len; - // omit trailing separator - 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( absolute_path[node_module_root..], ); - - import_record.package_json_hash = package_json.hash; + hasher = std.hash.Wyhash.init(0); + hasher.update(import_record.path.text); + hasher.update(std.mem.asBytes(&package_json.hash)); + import_record.module_id = @truncate(u32, hasher.final()); + import_record.is_bundled = true; const get_or_put_result = try this.resolved_paths.getOrPut(absolute_path); @@ -556,28 +559,101 @@ pub fn NewBundler(cache_files: bool) type { } else |err| {} } - const PackageNameVersionPair = struct { name: string, version: string, hash: u32 }; - var package: PackageNameVersionPair = undefined; - - if (resolve.package_json) |package_json| { - package = .{ - .name = package_json.name, - .version = package_json.version, - .hash = package_json.hash, - }; - } else { - if (this.bundler.resolver.packageJSONForResolvedNodeModule(&resolve)) |package_json| { - package = .{ - .name = package_json.name, - .version = package_json.version, - .hash = package_json.hash, - }; - } - } + const package = resolve.package_json orelse this.bundler.resolver.packageJSONForResolvedNodeModule(&resolve) orelse unreachable; + const package_relative_path = brk: { + // 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(resolve.path_pair.primary.text, node_module_root_string) orelse unreachable; + // omit node_modules + node_module_root += node_module_root_string.len; + + file_path.pretty = resolve.path_pair.primary.text[node_module_root..]; + + // omit trailing separator + node_module_root += 1; + + // omit package name + node_module_root += package.name.len; + + break :brk resolve.path_pair.primary.text[node_module_root..]; + }; + + // const load_from_symbol_ref = ast.runtime_imports.$$r.?; + // const reexport_ref = ast.runtime_imports.__reExport.?; + const register_ref = ast.runtime_imports.register.?; + const E = js_ast.E; + const Expr = js_ast.Expr; + const Stmt = js_ast.Stmt; + var part = &ast.parts[ast.parts.len - 1]; + var new_stmts: [1]Stmt = undefined; + var register_args: [4]Expr = undefined; + + var package_json_string = E.String{ .utf8 = package.name }; + var module_path_string = E.String{ .utf8 = package_relative_path }; + var target_identifier = E.Identifier{ .ref = register_ref }; + var cjs_args: [2]js_ast.G.Arg = undefined; + var module_binding = js_ast.B.Identifier{ .ref = ast.module_ref.? }; + var exports_binding = js_ast.B.Identifier{ .ref = ast.exports_ref.? }; + cjs_args[0] = js_ast.G.Arg{ + .binding = js_ast.Binding{ + .loc = logger.Loc.Empty, + .data = .{ .b_identifier = &module_binding }, + }, + }; + cjs_args[1] = js_ast.G.Arg{ + .binding = js_ast.Binding{ + .loc = logger.Loc.Empty, + .data = .{ .b_identifier = &exports_binding }, + }, + }; + var closure = E.Arrow{ + .args = &cjs_args, + .body = .{ + .loc = logger.Loc.Empty, + .stmts = part.stmts, + }, + }; + + // $$m(12345, "react", "index.js", function(module, exports) { + + // }) + register_args[0] = Expr{ .loc = .{ .start = 0 }, .data = .{ .e_string = &package_json_string } }; + register_args[1] = Expr{ .loc = .{ .start = 0 }, .data = .{ .e_string = &module_path_string } }; + register_args[2] = Expr{ .loc = .{ .start = 0 }, .data = .{ .e_arrow = &closure } }; + + var call_register = E.Call{ + .target = Expr{ + .data = .{ .e_identifier = &target_identifier }, + .loc = logger.Loc{ .start = 0 }, + }, + .args = ®ister_args, + }; + var register_expr = Expr{ .loc = call_register.target.loc, .data = .{ .e_call = &call_register } }; + var decls: [1]js_ast.G.Decl = undefined; + var bundle_export_binding = js_ast.B.Identifier{ .ref = ast.bundle_export_ref.? }; + var binding = js_ast.Binding{ + .loc = register_expr.loc, + .data = .{ .b_identifier = &bundle_export_binding }, + }; + decls[0] = js_ast.G.Decl{ + .value = register_expr, + .binding = binding, + }; + var export_var = js_ast.S.Local{ + .decls = &decls, + .is_export = true, + }; + new_stmts[0] = Stmt{ .loc = register_expr.loc, .data = .{ .s_local = &export_var } }; + part.stmts = &new_stmts; const code_offset = this.tmpfile_byte_offset - code_start_byte_offset; var writer = js_printer.NewFileWriter(this.tmpfile); var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols}); + hasher = std.hash.Wyhash.init(0); + hasher.update(package_relative_path); + hasher.update(std.mem.asBytes(&package.hash)); + const module_id = @truncate(u32, hasher.final()); const code_length = @truncate( u32, @@ -590,10 +666,11 @@ pub fn NewBundler(cache_files: bool) type { false, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, + .bundle_export_ref = ast.bundle_export_ref.?, + .source_path = file_path, .externals = ast.externals, - // Indent by one - .indent = 1, - .package_json_hash = package.hash, + .indent = 0, + .module_hash = module_id, .runtime_imports = ast.runtime_imports, }, Linker, @@ -613,26 +690,16 @@ pub fn NewBundler(cache_files: bool) type { }, ); } - // 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(resolve.path_pair.primary.text, node_module_root_string) orelse unreachable; - // omit package name - node_module_root += package.name.len; - // omit node_modules - node_module_root += node_module_root_string.len; - // omit trailing separator - node_module_root += 1; - - var path_str = resolve.path_pair.primary.text[node_module_root..]; - var path_extname_length = @truncate(u8, std.fs.path.extension(path_str).len); + + var path_extname_length = @truncate(u8, std.fs.path.extension(package_relative_path).len); try this.module_list.append( Api.JavascriptBundledModule{ .path = try this.appendHeaderString( - path_str, + package_relative_path, ), .path_extname_length = path_extname_length, .package_id = package_get_or_put_entry.value_ptr.*, + .id = module_id, .code = Api.StringPointer{ .length = @truncate(u32, code_length), .offset = @truncate(u32, code_offset), @@ -934,6 +1001,8 @@ pub fn NewBundler(cache_files: bool) type { var jsx = bundler.options.jsx; jsx.parse = loader.isJSX(); var opts = js_parser.Parser.Options.init(jsx, loader); + opts.enable_bundling = bundler.options.node_modules_bundle != null; + opts.transform_require_to_import = true; 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/import_record.zig b/src/import_record.zig index 89801b9be..0d3cff529 100644 --- a/src/import_record.zig +++ b/src/import_record.zig @@ -45,7 +45,7 @@ pub const ImportRecord = struct { path: fs.Path, // 0 is invalid - package_json_hash: u32 = 0, + module_id: u32 = 0, source_index: Ref.Int = std.math.maxInt(Ref.Int), diff --git a/src/js_ast.zig b/src/js_ast.zig index 9be9d461a..ca33e661c 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -1955,6 +1955,49 @@ pub const Expr = struct { return std.meta.activeTag(a.data) == Expr.Tag.e_missing; } + // The goal of this function is to "rotate" the AST if it's possible to use the + // left-associative property of the operator to avoid unnecessary parentheses. + // + // When using this, make absolutely sure that the operator is actually + // associative. For example, the "-" operator is not associative for + // floating-point numbers. + pub fn joinWithLeftAssociativeOp( + op: Op.Code, + a: Expr, + b: Expr, + allocator: *std.mem.Allocator, + ) Expr { + // "(a, b) op c" => "a, b op c" + switch (a.data) { + .e_binary => |comma| { + if (comma.op == .bin_comma) { + comma.right = joinWithLeftAssociativeOp(op, comma.right, b, allocator); + } + }, + else => {}, + } + + // "a op (b op c)" => "(a op b) op c" + // "a op (b op (c op d))" => "((a op b) op c) op d" + switch (b.data) { + .e_binary => |binary| { + if (binary.op == op) { + return joinWithLeftAssociativeOp( + op, + joinWithLeftAssociativeOp(op, a, binary.left, allocator), + binary.right, + allocator, + ); + } + }, + else => {}, + } + + // "a op b" => "a op b" + // "(a op b) op c" => "(a op b) op c" + return Expr.alloc(allocator, E.Binary{ .op = op, .left = a, .right = b }, a.loc); + } + pub fn joinWithComma(a: Expr, b: Expr, allocator: *std.mem.Allocator) Expr { if (a.isMissing()) { return b; @@ -3453,6 +3496,8 @@ pub const Ast = struct { uses_module_ref: bool = false, exports_kind: ExportsKind = ExportsKind.none, + bundle_export_ref: ?Ref = null, + // This is a list of ES6 features. They are ranges instead of booleans so // that they can be used in log messages. Check to see if "Len > 0". import_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax or "import()" diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index cb0b25f74..d0998876a 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -1543,6 +1543,7 @@ pub const Parser = struct { pub const Options = struct { jsx: options.JSX.Pragma, + can_import_from_bundle: bool = false, ts: bool = false, keep_names: bool = true, omit_runtime_for_tests: bool = false, @@ -1552,7 +1553,8 @@ pub const Parser = struct { suppress_warnings_about_weird_code: bool = true, // Used when bundling node_modules - output_commonjs: bool = false, + enable_bundling: bool = false, + transform_require_to_import: bool = true, moduleType: ModuleType = ModuleType.esm, trim_unused_imports: bool = true, @@ -1907,27 +1909,31 @@ pub const Parser = struct { exports_kind = .esm; } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { exports_kind = .cjs; - var args = p.allocator.alloc(Expr, 2) catch unreachable; - to_module_expr = p.callRuntime(logger.Loc.Empty, "__commonJS", args); + if (p.options.transform_require_to_import) { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + to_module_expr = p.callRuntime(logger.Loc.Empty, "__commonJS", args); + } } else { exports_kind = .esm; } var runtime_imports_iter = p.runtime_imports.iter(); - while (runtime_imports_iter.next()) |entry| { - const imports = [_]u16{entry.key}; - p.generateImportStmt( - RuntimeImports.Name, - &imports, - &before, - p.runtime_imports, - null, - "import_", - true, - ) catch unreachable; - } - - if (p.cjs_import_stmts.items.len > 0 and !p.options.output_commonjs) { + // don't import runtime if we're bundling, it's already included + if (!p.options.transform_require_to_import) { + while (runtime_imports_iter.next()) |entry| { + const imports = [_]u16{entry.key}; + p.generateImportStmt( + RuntimeImports.Name, + &imports, + &before, + p.runtime_imports, + null, + "import_", + true, + ) catch unreachable; + } + } + if (p.cjs_import_stmts.items.len > 0 and p.options.transform_require_to_import) { 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); @@ -2146,6 +2152,8 @@ pub fn NewParser( cjs_import_stmts: std.ArrayList(Stmt), + bundle_export_ref: ?Ref = null, + injected_define_symbols: List(Ref), symbol_uses: SymbolUseMap, declared_symbols: List(js_ast.DeclaredSymbol), @@ -2404,6 +2412,11 @@ pub fn NewParser( const import_record_index = p.addImportRecord(.require, arg.loc, original_name); p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0; p.import_records_for_current_part.append(import_record_index) catch unreachable; + + if (!p.options.transform_require_to_import) { + return p.e(E.Require{ .import_record_index = import_record_index }, arg.loc); + } + const suffix = "_module"; var base_identifier_name = fs.PathName.init(original_name).nonUniqueNameString(p.allocator) catch unreachable; var cjs_import_name = p.allocator.alloc(u8, base_identifier_name.len + suffix.len) catch unreachable; @@ -2941,8 +2954,18 @@ pub fn NewParser( p.require_ref = try p.declareCommonJSSymbol(.unbound, "require"); - p.exports_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "exports"); - p.module_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "module"); + if (p.options.enable_bundling) { + p.bundle_export_ref = try p.declareSymbol(.unbound, logger.Loc.Empty, "IF_YOU_SEE_THIS_ITS_A_BUNDLER_BUG_PLEASE_FILE_AN_ISSUE_THX"); + p.runtime_imports.__reExport = try p.declareSymbol(.unbound, logger.Loc.Empty, "__reExport"); + p.runtime_imports.register = try p.declareSymbol(.unbound, logger.Loc.Empty, "$$m"); + p.runtime_imports.__export = try p.declareSymbol(.unbound, logger.Loc.Empty, "__export"); + + p.exports_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "exports"); + p.module_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "module"); + } else { + p.exports_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "exports"); + p.module_ref = try p.declareSymbol(.hoisted, logger.Loc.Empty, "module"); + } p.runtime_imports.__require = p.require_ref; @@ -6485,6 +6508,7 @@ pub fn NewParser( .text = comment.text, }, p.lexer.loc())); } + p.lexer.comments_to_preserve_before.shrinkRetainingCapacity(0); if (p.lexer.token == eend) { break; @@ -11152,7 +11176,13 @@ pub fn NewParser( }, .e_binary => |ex| { switch (ex.op) { - .bin_strict_eq, .bin_strict_ne, .bin_comma, .bin_logical_or, .bin_logical_and, .bin_nullish_coalescing => { + .bin_strict_eq, + .bin_strict_ne, + .bin_comma, + .bin_logical_or, + .bin_logical_and, + .bin_nullish_coalescing, + => { return p.exprCanBeRemovedIfUnused(&ex.left) and p.exprCanBeRemovedIfUnused(&ex.right); }, else => {}, @@ -13269,6 +13299,7 @@ pub fn NewParser( .named_exports = p.named_exports, .import_keyword = p.es6_import_keyword, .export_keyword = p.es6_export_keyword, + .bundle_export_ref = p.bundle_export_ref, // .top_Level_await_keyword = p.top_level_await_keyword, }; } diff --git a/src/js_printer.zig b/src/js_printer.zig index 79124c245..54cf7611c 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -76,7 +76,9 @@ pub const Options = struct { indent: usize = 0, externals: []u32 = &[_]u32{}, runtime_imports: runtime.Runtime.Imports, - package_json_hash: u32 = 0, + module_hash: u32 = 0, + source_path: ?fs.Path = null, + bundle_export_ref: ?js_ast.Ref = null, rewrite_require_resolve: bool = true, // If we're writing out a source map, this table of line start indices lets // us do binary search on to figure out what line a given AST node came from @@ -475,8 +477,12 @@ pub fn NewPrinter( } pub fn printNonNegativeFloat(p: *Printer, float: f64) void { - if (float < 1000 and @intToFloat(f64, @floatToInt(i64, float)) == float) { - std.fmt.formatFloatDecimal(float, .{}, p) catch unreachable; + // Is this actually an integer? + if (float < std.math.maxInt(u32) and std.math.ceil(float) == float) { + // In JavaScript, numbers are represented as 64 bit floats + // However, they could also be signed or unsigned int 32 (when doing bit shifts) + // In this case, it's always going to unsigned since that conversion has already happened. + std.fmt.formatInt(@floatToInt(u32, float), 10, true, .{}, p) catch unreachable; return; } @@ -930,7 +936,15 @@ pub fn NewPrinter( } }, .e_require => |e| { - p.printRequireOrImportExpr(e.import_record_index, &([_]G.Comment{}), level, flags); + if (rewrite_esm_to_cjs) { + p.printIndent(); + p.printBundledRequire(e.*); + p.printSemicolonIfNeeded(); + } + + if (!rewrite_esm_to_cjs) { + p.printRequireOrImportExpr(e.import_record_index, &([_]G.Comment{}), level, flags); + } }, .e_require_or_require_resolve => |e| { const wrap = level.gte(.new) or flags.forbid_call; @@ -1078,7 +1092,6 @@ pub fn NewPrinter( p.print("("); flags.forbid_in = !flags.forbid_in; } - flags.forbid_in = true; p.printExpr(e.test_, .conditional, flags); p.printSpace(); p.print("? "); @@ -1517,7 +1530,7 @@ pub fn NewPrinter( } var left_level = entry.level.sub(1); - var right_level = left_level; + var right_level = entry.level.sub(1); if (e.op.isRightAssociative()) { left_level = entry.level; @@ -2172,7 +2185,13 @@ pub fn NewPrinter( p.printIndent(); p.printSpaceBeforeIdentifier(); - p.print("export default"); + + if (rewrite_esm_to_cjs) { + p.printSymbol(p.options.runtime_imports.__export.?); + p.print(".default ="); + } else { + p.print("export default"); + } p.printSpace(); @@ -2583,13 +2602,14 @@ pub fn NewPrinter( const record = p.import_records[s.import_record_index]; var item_count: usize = 0; - if (rewrite_esm_to_cjs) { - return p.printImportAsCommonJS(record, s, stmt); - } p.printIndent(); p.printSpaceBeforeIdentifier(); + if (rewrite_esm_to_cjs) { + return p.printBundledImport(record, s, stmt); + } + if (record.wrap_with_to_module) { if (p.options.runtime_imports.__require) |require_ref| { var module_name_buf: [256]u8 = undefined; @@ -2772,7 +2792,142 @@ pub fn NewPrinter( } } - pub fn printImportAsCommonJS(p: *Printer, record: importRecord.ImportRecord, s: *S.Import, stmt: Stmt) void {} + pub fn printBundledImport(p: *Printer, record: importRecord.ImportRecord, s: *S.Import, stmt: Stmt) void { + if (record.is_internal) { + return; + } + + const ImportVariant = enum { + path_only, + import_star, + import_default, + import_star_and_import_default, + import_items, + import_items_and_default, + import_items_and_star, + import_items_and_default_and_star, + + pub fn hasItems(import_variant: @This()) @This() { + return switch (import_variant) { + .import_default => .import_items_and_default, + .import_star => .import_items_and_star, + .import_star_and_import_default => .import_items_and_default_and_star, + else => .import_items, + }; + } + + // We always check star first so don't need to be exhaustive here + pub fn hasStar(import_variant: @This()) @This() { + return switch (import_variant) { + .path_only => .import_star, + else => import_variant, + }; + } + + // We check default after star + pub fn hasDefault(import_variant: @This()) @This() { + return switch (import_variant) { + .path_only => .import_default, + .import_star => .import_star_and_import_default, + else => import_variant, + }; + } + }; + + var variant = ImportVariant.path_only; + + if (s.star_name_loc != null and s.star_name_loc.?.start > -1) { + variant = variant.hasStar(); + } + + if (s.default_name != null) { + variant = variant.hasDefault(); + } + + if (s.items.len > 0) { + variant = variant.hasItems(); + } + + switch (variant) { + .import_star => { + p.print("var "); + p.printSymbol(s.namespace_ref); + p.print(" = "); + p.printLoadFromBundle(s.import_record_index); + p.printSemicolonAfterStatement(); + }, + .import_default => { + p.print("var "); + p.printSymbol(s.default_name.?.ref.?); + p.print(" = "); + p.printLoadFromBundle(s.import_record_index); + p.print(".default"); + p.printSemicolonAfterStatement(); + }, + .import_star_and_import_default => { + p.print("var "); + p.printSymbol(s.namespace_ref); + p.print(" = "); + p.printLoadFromBundle(s.import_record_index); + p.print(", "); + p.printSymbol(s.default_name.?.ref.?); + p.print(" = "); + p.printSymbol(s.namespace_ref); + p.print(".default"); + p.printSemicolonAfterStatement(); + }, + .import_items => { + p.print("var {"); + + var item_count: usize = 0; + + for (s.items) |*item, i| { + if (i != 0) { + p.print(","); + if (s.is_single_line) { + p.printSpace(); + } + } + + p.printClauseAlias(item.alias); + const name = p.renamer.nameForSymbol(item.name.ref.?); + if (!strings.eql(name, item.alias)) { + p.printSpace(); + p.print(":"); + p.printSpaceBeforeIdentifier(); + p.printIdentifier(name); + } + item_count += 1; + } + + p.print("}"); + p.print(" = "); + p.printLoadFromBundle(s.import_record_index); + + p.printSemicolonAfterStatement(); + }, + .import_items_and_default => {}, + .import_items_and_star => {}, + .import_items_and_default_and_star => {}, + .path_only => { + p.printLoadFromBundle(s.import_record_index); + p.printSemicolonAfterStatement(); + }, + } + } + pub fn printLoadFromBundle(p: *Printer, import_record_index: u32) void { + const record = p.import_records[import_record_index]; + p.print("$"); + std.fmt.formatInt(record.module_id, 16, true, .{}, p) catch unreachable; + p.print("()"); + } + pub fn printBundledRequire(p: *Printer, require: E.Require) void { + if (p.import_records[require.import_record_index].is_internal) { + return; + } + + p.printLoadFromBundle(require.import_record_index); + } pub fn printForLoopInit(p: *Printer, initSt: Stmt) void { switch (initSt.data) { @@ -2910,9 +3065,24 @@ pub fn NewPrinter( } pub fn printDeclStmt(p: *Printer, is_export: bool, comptime keyword: string, decls: []G.Decl) void { + if (rewrite_esm_to_cjs and keyword[0] == 'v' and is_export) { + // this is a top-level export + if (decls.len == 1 and std.meta.activeTag(decls[0].binding.data) == .b_identifier and decls[0].binding.data.b_identifier.ref.eql(p.options.bundle_export_ref.?)) { + p.print("// "); + p.print(p.options.source_path.?.pretty); + p.print("\nexport var $"); + std.fmt.formatInt(p.options.module_hash, 16, true, .{}, p) catch unreachable; + p.print(" = "); + p.printExpr(decls[0].value.?, .comma, ExprFlag.None()); + p.printSemicolonAfterStatement(); + return; + } + } + p.printIndent(); p.printSpaceBeforeIdentifier(); - if (is_export) { + + if (!rewrite_esm_to_cjs and is_export) { p.print("export "); } p.printDecls(keyword, decls, ExprFlag.None()); diff --git a/src/linker.zig b/src/linker.zig index fd00911f5..c8f792c4c 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -167,7 +167,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.package_json_hash = package.hash; + import_record.module_id = found_module.id; needs_bundle = true; continue; } diff --git a/src/runtime.js b/src/runtime.js index 5ac6d3a93..5d204a922 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -1,3 +1,4 @@ +var $$mod$ = Symbol.for; var __create = Object.create; var __defProp = Object.defineProperty; var __getProtoOf = Object.getPrototypeOf; @@ -54,9 +55,18 @@ export var __commonJS = var require_cache = new WeakMap(); export var __SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { - RequireFailedError: class RequireFailedError {}, + 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" +); + export var __require = (namespace) => { var entry = require_cache.get(namespace); if (typeof entry !== "undefined") { @@ -84,6 +94,95 @@ export var __require = (namespace) => { return exports; }; +if ( + !( + "__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE" in + globalThis + ) +) { + globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_LOAD_CACHE = + new Map(); +} + +if ( + !( + "__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_REGISTRY" in + globalThis + ) +) { + globalThis.__SPEEDY_INTERNAL_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__MODULE_REGISTRY = + 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 __name = (target, name) => { Object.defineProperty(target, "name", { value: name, diff --git a/src/runtime.version b/src/runtime.version index e8750c9a1..6bf95182a 100644 --- a/src/runtime.version +++ b/src/runtime.version @@ -1 +1 @@ -e9b61815176778be
\ No newline at end of file +6e0bb8ddff21cfa4
\ No newline at end of file diff --git a/src/runtime.zig b/src/runtime.zig index 6f53af6ad..5526d589f 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -21,6 +21,9 @@ pub const Runtime = struct { __require: ?Ref = null, __export: ?Ref = null, __reExport: ?Ref = null, + __load: ?Ref = null, + load_from_bundle: ?Ref = null, + register: ?Ref = null, pub const all = [_][]const u8{ "__name", @@ -29,6 +32,11 @@ pub const Runtime = struct { "__commonJS", "__export", "__reExport", + "__load", + // require + "load_from_bundle", + // + "register", }; pub const Name = "<RUNTIME"; @@ -77,6 +85,22 @@ pub const Runtime = struct { return Entry{ .key = 5, .value = val }; } }, + 6 => { + if (@field(this.runtime_imports, all[6])) |val| { + return Entry{ .key = 6, .value = val }; + } + }, + 7 => { + if (@field(this.runtime_imports, all[7])) |val| { + return Entry{ .key = 7, .value = val }; + } + }, + 8 => { + if (@field(this.runtime_imports, all[8])) |val| { + return Entry{ .key = 8, .value = val }; + } + }, + else => { return null; }, @@ -127,6 +151,9 @@ pub const Runtime = struct { 3 => @field(imports, all[3]), 4 => @field(imports, all[4]), 5 => @field(imports, all[5]), + 6 => @field(imports, all[6]), + 7 => @field(imports, all[7]), + 8 => @field(imports, all[8]), else => null, }; } diff --git a/src/test/fixtures/optional-chain-polyfill.js b/src/test/fixtures/optional-chain-polyfill.js new file mode 100644 index 000000000..f163ab25c --- /dev/null +++ b/src/test/fixtures/optional-chain-polyfill.js @@ -0,0 +1,2 @@ +var onError = _this2.props.onError; +onError === null || onError === void 0 ? void 0 : onError(err, ret, parsedFile); |