From 406ffb9e19846f791f1e6962cd8f6594e3a7e193 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sat, 29 Apr 2023 16:28:27 -0700 Subject: wip treeshake nested namespace imports It doesn't handle several important edgecases. I don't think this code is currently in the right place. --- src/bundler/bundle_v2.zig | 41 +++++++++++++++++++++++++-- src/js_ast.zig | 19 +++++++++---- src/js_parser.zig | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 168ef3137..9159dbb49 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -9045,8 +9045,11 @@ const LinkerContext = struct { .imports = &named_imports, }; named_imports.sort(sorter); - - for (named_imports.keys(), named_imports.values()) |ref, named_import| { + const nested_named_imports = c.graph.ast.items(.nested_named_imports); + const named_import_len = named_imports.count(); + for (0..named_import_len) |named_import_i| { + const ref = named_imports.keys()[named_import_i]; + const named_import = named_imports.values()[named_import_i]; // Re-use memory for the cycle detector c.cycle_detector.clearRetainingCapacity(); @@ -9078,6 +9081,36 @@ const LinkerContext = struct { }, }, ) catch unreachable; + + if (nested_named_imports[source_index].get(import_ref)) |nested| { + for (nested.slice()) |nested_import| { + if (c.graph.meta.items(.resolved_exports)[result.source_index].get(nested_import.alias)) |matching_export| { + var named_import_entry = named_imports.getOrPut(nested_import.ref) catch unreachable; + if (!named_import_entry.found_existing) { + named_import_entry.value_ptr.* = js_ast.NamedImport{ + .alias = nested_import.alias, + .import_record_index = named_import.import_record_index, + .namespace_ref = import_ref, + }; + + imports_to_bind.put( + c.allocator, + nested_import.ref, + .{ + .data = matching_export.data, + }, + ) catch unreachable; + } + } else if (c.graph.symbols.get(nested_import.ref)) |nested_symbol| { + nested_symbol.namespace_alias = js_ast.G.NamespaceAlias{ + .namespace_ref = import_ref, + .alias = nested_import.alias, + }; + } + + // } + } + } }, .namespace => { c.graph.symbols.get(import_ref).?.namespace_alias = js_ast.G.NamespaceAlias{ @@ -9217,6 +9250,7 @@ const LinkerContext = struct { continue :next_export; } } + const ref = entry.value_ptr.ref; var resolved = resolved_exports.getOrPut(this.allocator, entry.key_ptr.*) catch unreachable; if (!resolved.found_existing) { @@ -9384,6 +9418,9 @@ const LinkerContext = struct { for (this.export_star_records[source_index]) |id| { const records: []const ImportRecord = this.import_records[id].slice(); for (records) |record| { + // ignore external export * from + if (record.source_index.isInvalid()) continue; + // This file has dynamic exports if the exported imports are from a file // that either has dynamic exports directly or transitively by itself // having an export star from a file with dynamic exports. diff --git a/src/js_ast.zig b/src/js_ast.zig index 410539761..5f9ed06f9 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -5210,12 +5210,12 @@ pub const S = struct { pub fn canBeMovedAround(self: ExportDefault) bool { return switch (self.value) { .expr => |e| switch (e.data) { - .e_class => |class| class.extends == null, + .e_class => |class| class.canClassBeMoved(), .e_arrow, .e_function => true, else => e.canBeConstValue(), }, .stmt => |s| switch (s.data) { - .s_class => |class| class.class.extends == null, + .s_class => |class| class.class.canClassBeMoved(), .s_function => true, else => false, }, @@ -5719,6 +5719,7 @@ pub const Ast = struct { // since we already have to traverse the AST then anyway and the parser pass // is conveniently fully parallelized. named_imports: NamedImports = NamedImports.init(bun.failing_allocator), + nested_named_imports: NestedNamedImports = NestedNamedImports.init(bun.failing_allocator), named_exports: NamedExports = NamedExports.init(bun.failing_allocator), export_star_import_records: []u32 = &([_]u32{}), @@ -5740,7 +5741,13 @@ pub const Ast = struct { }; pub const CommonJSNamedExports = bun.StringArrayHashMapUnmanaged(CommonJSNamedExport); - pub const NamedImports = std.ArrayHashMap(Ref, NamedImport, RefHashCtx, true); + pub const NestedNamedImport = struct { + alias: string, + ref: Ref, + }; + + pub const NamedImports = std.ArrayHashMap(Ref, NamedImport, RefHashCtx, false); + pub const NestedNamedImports = std.ArrayHashMap(Ref, BabyList(NestedNamedImport), RefHashCtx, false); pub const NamedExports = bun.StringArrayHashMap(NamedExport); pub const ConstValuesMap = std.ArrayHashMapUnmanaged(Ref, Expr, RefHashCtx, false); @@ -9288,7 +9295,7 @@ pub const Macro = struct { this.caller.loc, this.allocator, "cannot coerce {s} to Bun's AST. Please return a valid macro using the JSX syntax", - .{@tagName(value.jsType())}, + .{@tagName(value.jsTypeLoose())}, ) catch unreachable; break :brk error.MacroFailed; }, @@ -9320,7 +9327,7 @@ pub const Macro = struct { var blob_: ?JSC.WebCore.Blob = null; var mime_type: ?HTTP.MimeType = null; - if (value.jsType() == .DOMWrapper) { + if (value.jsTypeLoose() == .DOMWrapper) { if (value.as(JSC.WebCore.Response)) |resp| { mime_type = HTTP.MimeType.init(resp.mimeType(null)); blob_ = resp.body.use(); @@ -9535,7 +9542,7 @@ pub const Macro = struct { this.caller.loc, this.allocator, "cannot coerce {s} to Bun's AST. Please return a valid macro using the JSX syntax", - .{@tagName(value.jsType())}, + .{@tagName(value.jsTypeLoose())}, ) catch unreachable; return error.MacroFailed; } diff --git a/src/js_parser.zig b/src/js_parser.zig index 5933b3e9d..6afdf7253 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2904,6 +2904,7 @@ pub const Parser = struct { var before = ListManaged(js_ast.Part).init(p.allocator); var after = ListManaged(js_ast.Part).init(p.allocator); var parts = ListManaged(js_ast.Part).init(p.allocator); + defer { after.deinit(); before.deinit(); @@ -2919,6 +2920,7 @@ pub const Parser = struct { if (!p.options.tree_shaking) { try p.appendPart(&parts, stmts); } else { + // When tree shaking is enabled, each top-level statement is potentially a separate part. for (stmts) |stmt| { switch (stmt.data) { @@ -4811,6 +4813,7 @@ fn NewParser_( import_items_for_namespace: std.AutoHashMapUnmanaged(Ref, ImportItemForNamespaceMap) = .{}, is_import_item: RefMap = .{}, named_imports: NamedImportsType, + nested_named_imports: js_ast.Ast.NestedNamedImports, named_exports: js_ast.Ast.NamedExports, import_namespace_cc_map: Map(ImportNamespaceCallOrConstruct, bool) = .{}, @@ -17396,6 +17399,71 @@ fn NewParser_( } } }, + .e_import_identifier => |id| { + + // Rewrite property accesses on explicit namespace imports as an identifier. + // This lets us replace them easily in the printer to rebind them to + // something else without paying the cost of a whole-tree traversal during + // module linking just to rewrite these EDot expressions. + if (p.options.bundle and js_lexer.isLatin1Identifier([]const u8, name)) { + var import_items_for_namespace_entry = p.import_items_for_namespace.getOrPut(p.allocator, id.ref) catch unreachable; + if (!import_items_for_namespace_entry.found_existing) { + import_items_for_namespace_entry.value_ptr.* = ImportItemForNamespaceMap.init(p.allocator); + } + var import_items = import_items_for_namespace_entry.value_ptr; + const ref = (import_items.get(name) orelse brk: { + const generated_name = std.fmt.allocPrint(p.allocator, "{s}_{s}", .{ p.loadNameFromRef(id.ref), name }) catch unreachable; + // Generate a new import item symbol in the module scope + const new_item = LocRef{ + .loc = name_loc, + .ref = p.newSymbol(.import, generated_name) catch unreachable, + }; + p.module_scope.generated.push(p.allocator, new_item.ref.?) catch unreachable; + + import_items.put(name, new_item) catch unreachable; + p.is_import_item.put(p.allocator, new_item.ref.?, {}) catch unreachable; + + var symbol = &p.symbols.items[new_item.ref.?.innerIndex()]; + + // Mark this as generated in case it's missing. We don't want to + // generate errors for missing import items that are automatically + // generated. + symbol.import_item_status = .generated; + + var nested_entry = p.nested_named_imports.getOrPutValue( + id.ref, + .{}, + ) catch unreachable; + nested_entry.value_ptr.push(p.allocator, .{ + .ref = new_item.ref.?, + .alias = name, + }) catch unreachable; + + break :brk new_item; + }).ref.?; + + // Undo the usage count for the namespace itself. This is used later + // to detect whether the namespace symbol has ever been "captured" + // or whether it has just been used to read properties off of. + // + // The benefit of doing this is that if both this module and the + // imported module end up in the same module group and the namespace + // symbol has never been captured, then we don't need to generate + // any code for the namespace at all. + p.ignoreUsage(id.ref); + + // Track how many times we've referenced this symbol + p.recordUsage(ref); + + return p.handleIdentifier( + name_loc, + E.Identifier{ .ref = ref }, + name, + identifier_opts, + ); + } + }, + .e_string => |str| { if (p.options.features.minify_syntax) { // minify "long-string".length to 11 @@ -21182,6 +21250,7 @@ fn NewParser_( .approximate_newline_count = p.lexer.approximate_newline_count, .exports_kind = exports_kind, .named_imports = p.named_imports, + .nested_named_imports = p.nested_named_imports, .named_exports = p.named_exports, .import_keyword = p.esm_import_keyword, .export_keyword = p.esm_export_keyword, @@ -21254,6 +21323,7 @@ fn NewParser_( .define = define, .import_records = undefined, .named_imports = undefined, + .nested_named_imports = undefined, .named_exports = js_ast.Ast.NamedExports.init(allocator), .log = log, .allocator = allocator, @@ -21286,6 +21356,7 @@ fn NewParser_( if (comptime !only_scan_imports_and_do_not_visit) { this.import_records = @TypeOf(this.import_records).init(allocator); this.named_imports = NamedImportsType.init(allocator); + this.nested_named_imports = js_ast.Ast.NestedNamedImports.init(allocator); } this.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(this); -- cgit v1.2.3