aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/launch.json2
-rw-r--r--src/api/schema.d.ts1
-rw-r--r--src/api/schema.js8
-rw-r--r--src/api/schema.peechy4
-rw-r--r--src/api/schema.zig5
-rw-r--r--src/bundler.zig153
-rw-r--r--src/import_record.zig2
-rw-r--r--src/js_ast.zig45
-rw-r--r--src/js_parser/js_parser.zig71
-rw-r--r--src/js_printer.zig194
-rw-r--r--src/linker.zig2
-rw-r--r--src/runtime.js101
-rw-r--r--src/runtime.version2
-rw-r--r--src/runtime.zig27
-rw-r--r--src/test/fixtures/optional-chain-polyfill.js2
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 = &register_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);