From 636d2e486f7770b9cf59aed1d66dfe75f45fa2a5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 21 Apr 2022 23:19:06 -0700 Subject: implement step 6 --- src/baby_list.zig | 9 + src/bundler/bundle_v2.zig | 460 +++++++++++++++++++++++++++++++++++++++++++- src/fs.zig | 27 ++- src/import_record.zig | 8 +- src/js_parser/js_parser.zig | 20 +- src/logger.zig | 19 ++ src/runtime.js | 38 +++- src/string_immutable.zig | 73 +++++++ src/string_mutable.zig | 7 +- 9 files changed, 629 insertions(+), 32 deletions(-) diff --git a/src/baby_list.zig b/src/baby_list.zig index 4c991fa13..4273e098d 100644 --- a/src/baby_list.zig +++ b/src/baby_list.zig @@ -34,6 +34,15 @@ pub fn BabyList(comptime Type: type) type { this.len += 1; } + pub inline fn appendSliceAssumeCapacity(this: *@This(), values: []const Type) void { + var tail = this.ptr[this.len]; + for (values) |value| { + tail.* = value; + tail += 1; + } + this.len += values.len; + } + pub inline fn init(items: []const Type) ListType { @setRuntimeSafety(false); return ListType{ diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 9c2d2d4f6..f06193f1e 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1282,6 +1282,25 @@ const LinkerGraph = struct { // it is a 2 dimensional bitset file_entry_bits: Bitmap, + pub fn generateRuntimeSymbolImportAndUse( + graph: *LinkerGraph, + source_index: Index.Int, + index: Index.Int, + entry_point_part_index: Index, + name: []const u8, + count: u32, + ) !void { + const ref = graph.ast.items(.ast)[Index.runtime.get()].module_scope.members.get(name).?.ref; + try graph.generateSymbolImportAndUse( + index, + source_index, + entry_point_part_index.get(), + ref, + count, + Index.runtime, + ); + } + pub fn addPartToFile( graph: *LinkerGraph, id: u32, @@ -1761,12 +1780,441 @@ const LinkerContext = struct { // Step 5: Create namespace exports for every file. This is always necessary // for CommonJS files, and is also necessary for other files if they are // imported using an import star statement. + // Note: `do` will wait for all to finish before moving forward + try this.parse_graph.pool.pool.do(this.allocator(), &this.wait_group, this, doStep5, this.graph.reachable_files); + + // Step 6: Bind imports to exports. This adds non-local dependencies on the + // parts that declare the export to all parts that use the import. Also + // generate wrapper parts for wrapped files. { - try this.parse_graph.pool.pool.do(this.allocator(), &this.wait_group, this, doStep5, this.graph.reachable_files); + const bufPrint = std.fmt.bufPrint; + var parts_list: []js_ast.Part.List = this.graph.ast.items(.parts); + var wrapper_refs = this.graph.meta.items(.wrapper_ref); + const needs_export_symbol_from_runtime: []const bool = this.graph.meta.items(.needs_export_symbol_from_runtime); + var imports_to_bind_list: []*RefImportData = this.graph.meta.items(.imports_to_bind); + var runtime_export_symbol_ref: Ref = Ref.None; + for (reachable) |source_index| { + const id = asts[source_index]; + if (id > named_imports.len) { + continue; + } + const is_entry_point = entry_point_kinds[source_index].isEntryPoint(); + const aliases = this.graph.meta.items(.sorted_and_filtered_export_aliases)[id]; + const wrap = wraps[id]; + const export_kind = export_kinds[id]; + const source: *const Logger.Source = &this.parse_graph.input_files.items(.source)[source_index]; + const exports_ref = exports_refs[id]; + var exports_symbol: ?*js_ast.Symbol = if (exports_ref.isValid()) + this.graph.symbols.get(exports_ref) + else + null; + const module_ref = module_refs[id]; + var module_symbol: ?*js_ast.Symbol = if (module_ref.isValid()) + this.graph.symbols.get(module_ref) + else + null; + + // TODO: see if counting and batching into a single large allocation instead of per-file improves perf + const string_buffer_len: usize = brk: { + var count: usize = 0; + if (is_entry_point and this.output_format == .esm) { + for (aliases) |alias| { + count += std.fmt.count("{}", .{strings.fmtIdentifier(alias)}); + } + count *= "export_".len; + } + + var ident_fmt_len: usize = 0; + if (wrap == .esm or (wrap != .cjs and export_kind != .common_js)) { + ident_fmt_len += if (source.identifier_name.len > 0) + source.identifier_name.len + else + std.fmt.count("{}", .{source.fmtIdentifier()}); + } + + if (wrap == .esm) { + count += "init_".len + ident_fmt_len; + } + + if (wrap != .cjs and export_kind != .common_js) { + count += "exports_".len + ident_fmt_len; + count += "module_".len + ident_fmt_len; + } + + break :brk count; + }; + + var string_buffer = this.allocator.alloc(u8, string_buffer_len) catch unreachable; + var buf = string_buffer; + + defer std.debug.assert(buf.len == 0); // ensure we used all of it + + // Pre-generate symbols for re-exports CommonJS symbols in case they + // are necessary later. This is done now because the symbols map cannot be + // mutated later due to parallelism. + if (is_entry_point and this.output_format == .esm) { + var copies = this.allocator().alloc(Ref, aliases.len) catch unreachable; + + for (aliases) |alias, i| { + const original_name = bufPrint(buf, "export_{}", .{strings.fmtIdentifier(alias)}) catch unreachable; + buf = buf[original_name.len..]; + copies[i] = this.graph.generateNewSymbol(source_index, .other, original_name); + } + this.graph.meta.items(.cjs_export_copies)[id] = copies; + } + + // Use "init_*" for ESM wrappers instead of "require_*" + if (wrap == .esm) { + const original_name = bufPrint( + buf, + "init_{}", + .{ + strings.fmtIdentifier(source.fmtIdentifier()), + }, + ) catch unreachable; + + buf = buf[original_name.len..]; + this.graph.symbols.get(wrapper_refs[id]).original_name = original_name; + } + + // If this isn't CommonJS, then rename the unused "exports" and "module" + // variables to avoid them causing the identically-named variables in + // actual CommonJS files from being renamed. This is purely about + // aesthetics and is not about correctness. This is done here because by + // this point, we know the CommonJS status will not change further. + if (wrap != .cjs and export_kind != .common_js) { + const exports_name = bufPrint(buf, "exports_{s}", .{strings.fmtIdentifier(source.fmtIdentifier())}) catch unreachable; + buf = buf[exports_name.len..]; + const module_name = bufPrint(buf, "module_{s}", .{strings.fmtIdentifier(source.fmtIdentifier())}) catch unreachable; + buf = buf[module_name.len..]; + + exports_symbol.?.original_name = exports_name; + module_symbol.?.original_name = module_name; + } + + // Include the "__export" symbol from the runtime if it was used in the + // previous step. The previous step can't do this because it's running in + // parallel and can't safely mutate the "importsToBind" map of another file. + if (needs_exports_variable[id]) { + if (!runtime_export_symbol_ref.isValid()) { + runtime_export_symbol_ref = this.graph.ast.items(.module_scope)[Index.runtime.get()].members.get("__export").?.ref; + } + + std.debug.assert(runtime_export_symbol_ref.isValid()); + + this.graph.generateSymbolImportAndUse( + id, + source_index, + js_ast.namespace_export_part_index, + runtime_export_symbol_ref, + 1, + Index.runtime.get(), + ) catch unreachable; + } + + var parts: []js_ast.Part = parts_list[id].slice(); + + var imports_to_bind = imports_to_bind_list[id]; + var imports_to_bind_iter = imports_to_bind.iterator(); + while (imports_to_bind_iter.next()) |import| { + const import_source_index = import.value_ptr.source_index; + const import_id = asts[import_source_index]; + const import_ref = import.key_ptr.*; + var named_import = named_imports[import_id].getPtr(import_ref) orelse continue; + const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import_ref); + + for (named_import.local_parts_with_uses.slice()) |part_index| { + var part: *js_ast.Part = &parts[part_index]; + + part.dependencies.ensureUnusedCapacity( + this.allocator(), + parts_declaring_symbol.len + @as(usize, import.value_ptr.re_exports.len), + ) catch unreachable; + + // Depend on the file containing the imported symbol + for (parts_declaring_symbol) |resolved_part_index| { + part.dependencies.appendAssumeCapacity( + this.allocator(), + .{ + .source_index = import_source_index, + .part_index = resolved_part_index, + }, + ); + } + + // Also depend on any files that re-exported this symbol in between the + // file containing the import and the file containing the imported symbol + part.dependencies.appendSliceAssumeCapacity(import.value_ptr.re_exports.slice()); + } + + // Merge these symbols so they will share the same name + this.graph.symbols.merge(import_ref, import.value_ptr.ref); + } + + // If this is an entry point, depend on all exports so they are included + if (is_entry_point) { + const force_include_exports = force_include_exports_for_entry_points[id]; + const add_wrapper = wrap != .none; + var dependencies = std.ArrayList(js_ast.Dependency).initCapacity( + this.allocator(), + @as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper)), + ); + var resolved_exports_list: *RefExportData = this.graph.meta.items(.resolved_exports)[id]; + for (aliases) |alias| { + var export_ = resolved_exports_list.get(alias).?; + var target_source_index = export_.source_index; + var target_id = asts[target_source_index]; + var target_ref = export_.ref; + + // If this is an import, then target what the import points to + + if (imports_to_bind.get(target_ref)) |import_data| { + target_source_index = import_data.value_ptr.source_index; + target_id = asts[target_source_index]; + target_ref = import_data.value_ptr.ref; + dependencies.appendSlice(import_data.re_exports.slice()) catch unreachable; + } + + const top_to_parts = this.topLevelSymbolsToParts(target_id, target_ref); + dependencies.ensureUnusedCapacity(top_to_parts.len) catch unreachable; + // Pull in all declarations of this symbol + for (top_to_parts) |part_index| { + dependencies.appendAssumeCapacity( + .{ + .source_index = target_source_index, + .part_index = part_index, + }, + ); + } + } + + dependencies.ensureUnusedCapacity(@as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper))) catch unreachable; + + // Ensure "exports" is included if the current output format needs it + if (force_include_exports) { + dependencies.appendAssumeCapacity( + .{ .source_index = source_index, .part_index = js_ast.namespace_export_part_index }, + ); + } + + if (add_wrapper) { + dependencies.appendAssumeCapacity( + .{ + .source_index = source_index, + .part_index = this.graph.meta.items(.wrapper_part_index)[id].get(), + }, + ); + } + + // Represent these constraints with a dummy part + const entry_point_part_index = this.graph.addPartToFile( + id, + .{ + .dependencies = js_ast.Dependency.List.fromList(dependencies), + .can_be_removed_if_unused = false, + }, + ) catch unreachable; + this.graph.items(.meta)[id].entry_point_part_index = Index.init(entry_point_part_index); + + // Pull in the "__toCommonJS" symbol if we need it due to being an entry point + if (force_include_exports) { + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + entry_point_part_index, + "__toCommonJS", + 1, + ) catch unreachable; + } + } + + // Encode import-specific constraints in the dependency graph + var import_records = import_records_list[id].slice(); + for (parts) |*part, part_index| { + var to_esm_uses: u32 = 0; + var to_common_js_uses: u32 = 0; + var runtime_require_uses: u32 = 0; + + for (part.import_record_indices.slice()) |import_record_index| { + var record = &import_records[import_record_index]; + const kind = record.kind; + + // Don't follow external imports (this includes import() expressions) + if (!record.source_index.isValid() or this.isExternalDynamicImport(record, source_index)) { + // This is an external import. Check if it will be a "require()" call. + if (kind == .require or !output_format.keepES6ImportExportSyntax() or + (kind == .dynamic)) + { + // We should use "__require" instead of "require" if we're not + // generating a CommonJS output file, since it won't exist otherwise + if (this.shouldCallRuntimeRequire(output_format)) { + record.calls_runtime_require = true; + runtime_require_uses += 1; + } + + // If this wasn't originally a "require()" call, then we may need + // to wrap this in a call to the "__toESM" wrapper to convert from + // CommonJS semantics to ESM semantics. + // + // Unfortunately this adds some additional code since the conversion + // is somewhat complex. As an optimization, we can avoid this if the + // following things are true: + // + // - The import is an ES module statement (e.g. not an "import()" expression) + // - The ES module namespace object must not be captured + // - The "default" and "__esModule" exports must not be accessed + // + if (kind != .require and + (kind != .stmt or + record.contains_import_star or + record.contains_default_alias or + record.contains_es_module_alias)) + { + record.wrap_with_to_esm = true; + to_esm_uses += 1; + } + } + continue; + } + + const other_source_index = record.source_index.get(); + const other_id = asts[other_source_index]; + std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len); + + const other_export_kind = export_kinds[other_id]; + + switch (wrap) { + else => { + + // Depend on the automatically-generated require wrapper symbol + const wrapper_ref = wrapper_refs[other_id]; + this.graph.generateSymbolImportAndUse( + id, + source_index, + @intCast(u32, part_index), + wrapper_ref, + 1, + Index.init(other_source_index), + ) catch unreachable; + + // This is an ES6 import of a CommonJS module, so it needs the + // "__toESM" wrapper as long as it's not a bare "require()" + if (kind != .require and other_export_kind == .common_js) { + record.wrap_with_to_esm = true; + to_esm_uses += 1; + } + }, + .none => { + if (kind == .stmt and other_export_kind == .esm_with_dynamic_fallback) { + // This is an import of a module that has a dynamic export fallback + // object. In that case we need to depend on that object in case + // something ends up needing to use it later. This could potentially + // be omitted in some cases with more advanced analysis if this + // dynamic export fallback object doesn't end up being needed. + this.graph.generateSymbolImportAndUse( + id, + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[other_id], + 1, + Index.init(other_source_index), + ) catch unreachable; + } + }, + } + } + + // If there's an ES6 import of a non-ES6 module, then we're going to need the + // "__toESM" symbol from the runtime to wrap the result of "require()" + this.graph.generateRuntimeSymbolImportAndUse( + id, + source_index, + Index.init(part_index), + "__toESM", + to_esm_uses, + ) catch unreachable; + + // If there's a CommonJS require of an ES6 module, then we're going to need the + // "__toCommonJS" symbol from the runtime to wrap the exports object + this.graph.generateRuntimeSymbolImportAndUse( + id, + source_index, + Index.init(part_index), + "__toCommonJS", + to_common_js_uses, + ) catch unreachable; + + // If there are unbundled calls to "require()" and we're not generating + // code for node, then substitute a "__require" wrapper for "require". + this.graph.generateRuntimeSymbolImportAndUse( + id, + source_index, + Index.init(part_index), + "__require", + runtime_require_uses, + ) catch unreachable; + + // If there's an ES6 export star statement of a non-ES6 module, then we're + // going to need the "__reExport" symbol from the runtime + var re_export_uses: u32 = 0; + + for (export_star_import_records[id]) |import_record_index| { + var record = &import_records[import_record_index]; + + var happens_at_runtime = record.source_index.isInvalid() and (!is_entry_point or !output_format.keepES6ImportExportSyntax()); + if (record.source_index.isValid()) { + var other_source_index = record.source_index.get(); + const other_id = asts[other_source_index]; + std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len); + const other_export_kind = export_kinds[other_id]; + if (other_source_index != source_index and other_export_kind.isDynamic()) { + happens_at_runtime = true; + } + + if (other_export_kind == .esm_with_dynamic_fallback) { + // This looks like "__reExport(exports_a, exports_b)". Make sure to + // pull in the "exports_b" symbol into this export star. This matters + // in code splitting situations where the "export_b" symbol might live + // in a different chunk than this export star. + this.graph.generateSymbolImportAndUse( + id, + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[other_id], + 1, + Index.init(other_source_index), + ) catch unreachable; + } + } + + if (happens_at_runtime) { + // Depend on this file's "exports" object for the first argument to "__reExport" + this.graph.generateSymbolImportAndUse( + id, + source_index, + @intCast(u32, part_index), + this.graph.ast.items(.exports_ref)[id], + 1, + Index.init(source_index), + ) catch unreachable; + this.graph.ast.items(.uses_export_ref)[id] = true; + record.calls_runtime_re_export_fn = true; + re_export_uses += 1; + } + } + + this.graph.generateRuntimeSymbolImportAndUse( + source_index, + id, + Index.init(part_index), + "__reExport", + re_export_uses, + ) catch unreachable; + } + } } } - pub fn createExportsForFile(c: *LinkerContext, allocator_: std.mem.Allocator, source_index: Index.Int, id: u32, ids: []u32, resolved_exports: *RefExportData, imports_to_bind: []*RefImportData, export_aliases: []const string, re_exports_count: usize) void { + pub fn createExportsForFile(c: *LinkerContext, allocator_: std.mem.Allocator, id: u32, ids: []u32, resolved_exports: *RefExportData, imports_to_bind: []*RefImportData, export_aliases: []const string, re_exports_count: usize) void { //////////////////////////////////////////////////////////////////////////////// // WARNING: This method is run in parallel over all files. Do not mutate data // for other files within this method or you will create a data race. @@ -1793,7 +2241,7 @@ const LinkerContext = struct { defer stmts.done(); const loc = Logger.Loc.Empty; // todo: investigate if preallocating this array is faster - var ns_export_dependencies = std.ArrayList(js_ast.Dependency).init(allocator_); + var ns_export_dependencies = std.ArrayList(js_ast.Dependency).initCapacity(allocator_, re_exports_count) catch unreachable; for (export_aliases) |alias| { var export_ = resolved_exports.getPtr(alias).?; @@ -1883,7 +2331,7 @@ const LinkerContext = struct { var declared_symbols = js_ast.DeclaredSymbol.List{}; var exports_ref = c.graph.ast.items(.exports_ref)[id]; var export_stmts: []js_ast.Stmt = stmts.head; - std.debug.assert(stmts.head.len <= 2); + std.debug.assert(stmts.head.len <= 2); // assert we allocated exactly the right amount stmts.head.len = 0; // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point @@ -1976,6 +2424,9 @@ const LinkerContext = struct { } } + /// Step 5: Create namespace exports for every file. This is always necessary + /// for CommonJS files, and is also necessary for other files if they are + /// imported using an import star statement. pub fn doStep5(c: *LinkerContext, source_index: Index.Int, _: usize) void { const ids = c.parse_graph.input_files.items(.ast); const id = ids[source_index]; @@ -2043,7 +2494,6 @@ const LinkerContext = struct { // come second after we fill in that array c.createExportsForFile( allocator_, - source_index, id, ids, resolved_exports, diff --git a/src/fs.zig b/src/fs.zig index e4a2268dc..d5c81a0d4 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -1075,6 +1075,25 @@ pub const PathName = struct { ext: string, filename: string, + pub fn nonUniqueNameStringBase(self: *const PathName) string { + // /bar/foo/index.js -> foo + if (self.dir.len > 0 and strings.eqlComptime(self.base, "index")) { + // "/index" -> "index" + return Fs.PathName.init(self.dir).base; + } + + if (comptime Environment.allow_assert) { + std.debug.assert(!strings.includes(self.base, "/")); + } + + // /bar/foo.js -> foo + return self.base; + } + + pub fn fmtIdentifier(self: *const PathName) strings.FormatValidIdentifier { + return strings.fmtIdentifier(self.nonUniqueNameStringBase()); + } + // For readability, the names of certain automatically-generated symbols are // derived from the file name. For example, instead of the CommonJS wrapper for // a file being called something like "require273" it can be called something @@ -1087,13 +1106,7 @@ pub const PathName = struct { // through the renaming logic that all other symbols go through to avoid name // collisions. pub fn nonUniqueNameString(self: *const PathName, allocator: std.mem.Allocator) !string { - if (strings.eqlComptime(self.base, "index")) { - if (self.dir.len > 0) { - return MutableString.ensureValidIdentifier(PathName.init(self.dir).base, allocator); - } - } - - return MutableString.ensureValidIdentifier(self.base, allocator); + return MutableString.ensureValidIdentifier(self.nonUniqueNameStringBase(), allocator); } pub inline fn dirWithTrailingSlash(this: *const PathName) string { diff --git a/src/import_record.zig b/src/import_record.zig index 66bc7d00e..a975cf669 100644 --- a/src/import_record.zig +++ b/src/import_record.zig @@ -116,11 +116,17 @@ pub const ImportRecord = struct { /// If true, this "export * from 'path'" statement is evaluated at run-time by /// calling the "__reExport()" helper function - calls_run_time_re_export_fn: bool = false, + calls_runtime_re_export_fn: bool = false, + + /// If true, this calls require() at runtime + calls_runtime_require: bool = false, /// Tell the printer to wrap this call to "require()" in "__toModule(...)" wrap_with_to_module: bool = false, + /// Tell the printer to wrap this call to "toESM()" in "__toESM(...)" + wrap_with_to_esm: bool = false, + /// True for require calls like this: "try { require() } catch {}". In this /// case we shouldn't generate an error if the path could not be resolved. is_inside_try_body: bool = false, diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 3d9419e92..539782e3d 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -2227,7 +2227,7 @@ pub const Parser = struct { import_record.is_unused = import_record.is_unused or (import_record.kind == .stmt and !import_record.was_originally_bare_import and - !import_record.calls_run_time_re_export_fn); + !import_record.calls_runtime_re_export_fn); } var iter = scan_pass.used_symbols.iterator(); @@ -4125,20 +4125,13 @@ fn NewParser_( var import_record: *ImportRecord = &p.import_records.items[import_record_i]; import_record.path.namespace = "runtime"; import_record.tag = .runtime; - var import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator); - var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + suffix.len); + const import_path_identifier = comptime suffix ++ "bun_runtime"; var clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len); var stmts = try allocator.alloc(Stmt, 1 + if (additional_stmt != null) @as(usize, 1) else @as(usize, 0)); var declared_symbols = js_ast.DeclaredSymbol.List{}; try declared_symbols.ensureTotalCapacity(p.allocator, imports.len); - std.mem.copy(u8, namespace_identifier[0..suffix.len], suffix); - std.mem.copy( - u8, - namespace_identifier[suffix.len..namespace_identifier.len], - import_path_identifier[0..import_path_identifier.len], - ); - const namespace_ref = try p.newSymbol(.other, namespace_identifier); + const namespace_ref = try p.newSymbol(.other, import_path_identifier); try p.module_scope.generated.append(allocator, namespace_ref); for (imports) |alias, i| { const ref = symbols.get(alias) orelse unreachable; @@ -5588,6 +5581,7 @@ fn NewParser_( var stmt = stmt_; if (is_macro) { const id = p.addImportRecord(.stmt, path.loc, path.text); + p.import_records.items[id].tag = .macro; p.import_records.items[id].path.namespace = js_ast.Macro.namespace; p.import_records.items[id].is_unused = true; @@ -6276,7 +6270,7 @@ fn NewParser_( if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused - p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; + p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } try p.lexer.expectOrInsertSemicolon(); @@ -6315,7 +6309,7 @@ fn NewParser_( if (comptime track_symbol_usage_during_parse_pass) { // In the scan pass, we need _some_ way of knowing *not* to mark as unused - p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; + p.import_records.items[import_record_index].calls_runtime_re_export_fn = true; } return p.s(S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index }, loc); @@ -9016,7 +9010,7 @@ fn NewParser_( } pub fn addImportRecordByRange(p: *P, kind: ImportKind, range: logger.Range, name: string) u32 { - var index = p.import_records.items.len; + const index = p.import_records.items.len; const record = ImportRecord{ .kind = kind, .range = range, diff --git a/src/logger.zig b/src/logger.zig index aae5e5aa3..18b59d7dc 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -1021,8 +1021,27 @@ pub const Source = struct { contents: string, contents_is_recycled: bool = false, + /// Lazily-generated human-readable identifier name that is non-unique + /// Avoid accessing this directly most of the time + identifier_name: string = "", + index: Index = Index.invalid, + pub fn fmtIdentifier(this: *const Source) strings.FormatValidIdentifier { + return this.path.name.fmtIdentifier(); + } + + pub fn identifierName(this: *Source, allocator: std.mem.Allocator) !string { + if (this.identifier_name.len > 0) { + return this.identifier_name; + } + + std.debug.assert(this.path.text.len > 0); + const name = try this.path.name.nonUniqueNameString(allocator); + this.identifier_name = name; + return name; + } + pub fn rangeOfIdentifier(this: *const Source, loc: Loc) Range { const js_lexer = @import("./js_lexer.zig"); return js_lexer.rangeOfIdentifier(this.contents, loc); diff --git a/src/runtime.js b/src/runtime.js index d485b0a42..29efc3ba9 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -156,10 +156,10 @@ export var __exportDefault = (target, value) => { }); }; -export var __reExport = (target, module, desc) => { +export var __reExport = (target, module, copyDefault, desc) => { if ((module && typeof module === "object") || typeof module === "function") for (let key of __getOwnPropNames(module)) - if (!__hasOwnProp.call(target, key) && key !== "default") + if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default")) __defProp(target, key, { get: () => module[key], configurable: true, @@ -168,3 +168,37 @@ export var __reExport = (target, module, desc) => { }); return target; }; + +// Converts the module from CommonJS to ESM +export var __toESM = (module, isNodeMode) => { + return __reExport( + __markAsModule( + __defProp( + module != null ? __create(__getProtoOf(module)) : {}, + "default", + + // If the importer is not in node compatibility mode and this is an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has been set), then forward + // "default" to the export named "default". Otherwise set "default" to + // "module.exports" for node compatibility. + !isNodeMode && module && module.__esModule + ? { get: () => module.default, enumerable: true } + : { value: module, enumerable: true } + ) + ), + module + ); +}; + +// Converts the module from ESM to CommonJS +export var __toCommonJS = /* @__PURE__ */ ((cache) => { + return (module, temp) => { + return ( + (cache && cache.get(module)) || + ((temp = __reExport(__markAsModule({}), module, /* copyDefault */ 1)), + cache && cache.set(module, temp), + temp) + ); + }; +})(typeof WeakMap !== "undefined" ? new WeakMap() : 0); diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 2cd70a6bb..1a21ae7c3 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -64,6 +64,79 @@ pub fn indexOfCharNeg(self: string, char: u8) i32 { return -1; } +/// Format a string to an ECMAScript identifier. +/// Unlike the string_mutable.zig version, this always allocate/copy +pub fn fmtIdentifier(name: string) FormatValidIdentifier { + return FormatValidIdentifier{ .name = name }; +} + +/// Format a string to an ECMAScript identifier. +/// Different implementation than string_mutable because string_mutable may avoid allocating +/// This will always allocate +pub const FormatValidIdentifier = struct { + name: string, + const js_lexer = @import("./js_lexer.zig"); + pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + var iterator = strings.CodepointIterator.init(self.name); + var cursor = strings.CodepointIterator.Cursor{}; + + var has_needed_gap = false; + var needs_gap = false; + var start_i: usize = 0; + + if (!iterator.next(&cursor)) { + try writer.writeAll("_"); + return; + } + + // Common case: no gap necessary. No allocation necessary. + needs_gap = !js_lexer.isIdentifierStart(cursor.c); + if (!needs_gap) { + // Are there any non-alphanumeric chars at all? + while (iterator.next(&cursor)) { + if (!js_lexer.isIdentifierContinue(cursor.c) or cursor.width > 1) { + needs_gap = true; + start_i = cursor.i; + break; + } + } + } + + if (needs_gap) { + needs_gap = false; + + var slice = self.name[start_i..]; + iterator = strings.CodepointIterator.init(slice); + cursor = strings.CodepointIterator.Cursor{}; + + while (iterator.next(&cursor)) { + if (js_lexer.isIdentifierContinue(cursor.c) and cursor.width == 1) { + if (needs_gap) { + try writer.writeAll("_"); + needs_gap = false; + has_needed_gap = true; + } + try writer.writeAll(slice[cursor.i .. cursor.i + @as(u32, cursor.width)]); + } else if (!needs_gap) { + needs_gap = true; + // skip the code point, replace it with a single _ + } + } + + // If it ends with an emoji + if (needs_gap) { + try writer.writeAll("_"); + needs_gap = false; + has_needed_gap = true; + } + + return; + } + + try writer.writeAll(self.name); + } +}; + pub fn indexOfSigned(self: string, str: string) i32 { const i = std.mem.indexOf(u8, self, str) orelse return -1; return @intCast(i32, i); diff --git a/src/string_mutable.zig b/src/string_mutable.zig index 7ef05fbe7..3481f1d2c 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -62,10 +62,9 @@ pub const MutableString = struct { return mutable; } - // Convert it to an ASCII identifier. Note: If you change this to a non-ASCII - // identifier, you're going to potentially cause trouble with non-BMP code - // points in target environments that don't support bracketed Unicode escapes. - + /// Convert it to an ASCII identifier. Note: If you change this to a non-ASCII + /// identifier, you're going to potentially cause trouble with non-BMP code + /// points in target environments that don't support bracketed Unicode escapes. pub fn ensureValidIdentifier(str: string, allocator: std.mem.Allocator) !string { if (str.len == 0) { return "_"; -- cgit v1.2.3