From df88b998d682ec4ca4ee77e64fbf6efc5d8cf9fa Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Sat, 15 Apr 2023 05:17:12 -0700 Subject: Mostly implement cross-module constant inlining, but disable it There are some test failures --- src/bundler.zig | 4 +- src/bundler/bundle_v2.zig | 84 +++++++++++++++++++++++++--- src/cli.zig | 6 ++ src/cli/build_command.zig | 5 ++ src/js_printer.zig | 140 ++++++++++++++++++++++++++-------------------- src/options.zig | 1 + src/runtime.zig | 2 + 7 files changed, 172 insertions(+), 70 deletions(-) diff --git a/src/bundler.zig b/src/bundler.zig index e471bddc6..aba5f9e5a 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1385,7 +1385,7 @@ pub const Bundler = struct { opts.transform_require_to_import = bundler.options.allow_runtime and !bundler.options.platform.isBun(); opts.features.allow_runtime = bundler.options.allow_runtime; opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); - opts.features.should_fold_typescript_constant_expressions = loader.isTypeScript() or platform.isBun(); + opts.features.should_fold_typescript_constant_expressions = loader.isTypeScript() or platform.isBun() or bundler.options.inlining; opts.features.dynamic_require = platform.isBun(); // @bun annotation @@ -1417,6 +1417,8 @@ pub const Bundler = struct { opts.features.jsx_optimization_hoist = bundler.options.jsx_optimization_hoist orelse opts.features.jsx_optimization_inline; opts.features.hoist_bun_plugin = this_parse.hoist_bun_plugin; opts.features.inject_jest_globals = this_parse.inject_jest_globals; + opts.features.minify_syntax = bundler.options.minify_syntax; + if (bundler.macro_context == null) { bundler.macro_context = js_ast.Macro.MacroContext.init(bundler); } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 6a1cf486c..0a5b238b0 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -946,7 +946,9 @@ const ParseTask = struct { opts.features.top_level_await = true; opts.features.jsx_optimization_inline = platform.isBun() and (bundler.options.jsx_optimization_inline orelse !task.jsx.development); opts.features.auto_import_jsx = task.jsx.parse and bundler.options.auto_import_jsx; - opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); + opts.features.trim_unused_imports = loader.isTypeScript() or (bundler.options.trim_unused_imports orelse false); + opts.features.inlining = true; + opts.tree_shaking = task.tree_shaking; opts.module_type = task.module_type; task.jsx.parse = loader.isJSX(); @@ -1445,6 +1447,8 @@ pub const Graph = struct { use_directive_entry_points: UseDirective.List = .{}, + const_values: std.HashMapUnmanaged(Ref, Expr, Ref.HashCtx, 80) = .{}, + pub const InputFile = struct { source: Logger.Source, loader: options.Loader = options.Loader.file, @@ -1543,6 +1547,8 @@ const LinkerGraph = struct { has_client_components: bool = false, has_server_components: bool = false, + const_values: std.HashMapUnmanaged(Ref, Expr, Ref.HashCtx, 80) = .{}, + pub fn init(allocator: std.mem.Allocator, file_count: usize) !LinkerGraph { return LinkerGraph{ .allocator = allocator, @@ -1910,6 +1916,26 @@ const LinkerGraph = struct { this.symbols = js_ast.Symbol.Map.initList(symbols); } + { + var const_values = this.const_values; + var count: usize = 0; + + for (this.ast.items(.const_values)) |const_value| { + count += const_value.count(); + } + + if (count > 0) { + try const_values.ensureTotalCapacity(this.allocator, @truncate(u32, count)); + for (this.ast.items(.const_values)) |const_value| { + for (const_value.keys(), const_value.values()) |key, value| { + const_values.putAssumeCapacityNoClobber(key, value); + } + } + } + + this.const_values = const_values; + } + var in_resolved_exports: []ResolvedExports = this.meta.items(.resolved_exports); var src_resolved_exports: []js_ast.Ast.NamedExports = this.ast.items(.named_exports); for (src_resolved_exports, in_resolved_exports, 0..) |src, *dest, source_index| { @@ -2363,7 +2389,7 @@ const LinkerContext = struct { const part_index = @truncate(u32, part_index_); const is_part_in_this_chunk = is_file_in_chunk and part.is_live; for (part.import_record_indices.slice()) |record_id| { - const record = &records[record_id]; + const record: *const ImportRecord = &records[record_id]; if (record.source_index.isValid() and (record.kind == .stmt or is_part_in_this_chunk)) { if (v.c.isExternalDynamicImport(record, source_index)) { // Don't follow import() dependencies @@ -3431,7 +3457,6 @@ const LinkerContext = struct { const loc = Logger.Loc.Empty; // todo: investigate if preallocating this array is faster 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).?; @@ -3719,14 +3744,52 @@ const LinkerContext = struct { var parts_slice: []js_ast.Part = parts.slice(); var named_imports: js_ast.Ast.NamedImports = c.graph.ast.items(.named_imports)[id]; defer c.graph.ast.items(.named_imports)[id] = named_imports; - for (parts_slice, 0..) |*part, part_index| { + outer: for (parts_slice, 0..) |*part, part_index| { // TODO: inline const TypeScript enum here // TODO: inline function calls here - // note: if we crash on append, it is due to threadlocal heaps in mimalloc - const symbol_uses = part.symbol_uses.keys(); + // Inline cross-module constants + if (c.graph.const_values.count() > 0) { + // First, find any symbol usage that points to a constant value. + // This will be pretty rare. + const first_constant_i: ?usize = brk: { + for (part.symbol_uses.keys(), 0..) |ref, j| { + if (c.graph.const_values.contains(ref)) { + break :brk j; + } + } + + break :brk null; + }; + if (first_constant_i) |j| { + var end_i: usize = 0; + // symbol_uses is an array + var keys = part.symbol_uses.keys()[j..]; + var values = part.symbol_uses.values()[j..]; + for (keys, values) |ref, val| { + if (c.graph.const_values.contains(ref)) { + continue; + } + + keys[end_i] = ref; + values[end_i] = val; + end_i += 1; + } + part.symbol_uses.entries.len = end_i + j; + + if (part.symbol_uses.entries.len == 0 and part.can_be_removed_if_unused) { + part.tag = .dead_due_to_inlining; + part.dependencies.len = 0; + continue :outer; + } + + part.symbol_uses.reIndex(allocator_) catch unreachable; + } + } + + var symbol_uses = part.symbol_uses.keys(); // Now that we know this, we can determine cross-part dependencies for (symbol_uses, 0..) |ref, j| { @@ -3734,8 +3797,6 @@ const LinkerContext = struct { std.debug.assert(part.symbol_uses.values()[j].count_estimate > 0); } - // TODO: inline const values from an import - const other_parts = c.topLevelSymbolsToParts(id, ref); for (other_parts) |other_part_index| { @@ -4566,6 +4627,7 @@ const LinkerContext = struct { .allocator = allocator, .require_ref = runtimeRequireRef, .minify_whitespace = c.options.minify_whitespace, + .const_values = c.graph.const_values, }; var cross_chunk_import_records = ImportRecord.List.initCapacity(allocator, chunk.cross_chunk_imports.len) catch unreachable; @@ -5190,6 +5252,7 @@ const LinkerContext = struct { .require_or_import_meta_for_source_callback = js_printer.RequireOrImportMeta.Callback.init(LinkerContext, requireOrImportMetaForSource, c), .minify_whitespace = c.options.minify_whitespace, + .const_values = c.graph.const_values, }; return .{ @@ -5787,6 +5850,7 @@ const LinkerContext = struct { stmt.loc, ); stmt.data.s_local.is_export = false; + } }, @@ -6379,6 +6443,7 @@ const LinkerContext = struct { .commonjs_named_exports = ast.commonjs_named_exports, .commonjs_named_exports_ref = ast.exports_ref, + .const_values = c.graph.const_values, .allocator = allocator, .to_esm_ref = toESMRef, @@ -6692,7 +6757,8 @@ const LinkerContext = struct { } } - for (parts[source_index].slice()) |part| { + const parts_in_file = parts[source_index].slice(); + for (parts_in_file) |part| { for (part.dependencies.slice()) |dependency| { if (dependency.source_index.get() != source_index) { if (imports_a_boundary and diff --git a/src/cli.zig b/src/cli.zig index 674aeac02..2a368fc39 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -199,6 +199,8 @@ pub const Arguments = struct { clap.parseParam("--server-components Enable React Server Components (experimental)") catch unreachable, clap.parseParam("--splitting Split up code!") catch unreachable, clap.parseParam("--transform Do not bundle") catch unreachable, + clap.parseParam("--minify-syntax Minify syntax and inline data (experimental)") catch unreachable, + clap.parseParam("--minify-whitespace Minify whitespace (experimental)") catch unreachable, }; // TODO: update test completions @@ -499,6 +501,8 @@ pub const Arguments = struct { if (cmd == .BuildCommand) { ctx.bundler_options.transform_only = args.flag("--transform"); + ctx.bundler_options.minify_syntax = args.flag("--minify-syntax"); + ctx.bundler_options.minify_whitespace = args.flag("--minify-whitespace"); if (args.option("--outdir")) |outdir| { if (outdir.len > 0) { ctx.bundler_options.outdir = outdir; @@ -912,6 +916,8 @@ pub const Command = struct { react_server_components: bool = false, code_splitting: bool = false, transform_only: bool = false, + minify_syntax: bool = false, + minify_whitespace: bool = false, }; const _ctx = Command.Context{ diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig index 51e98f9d0..d527ee427 100644 --- a/src/cli/build_command.zig +++ b/src/cli/build_command.zig @@ -53,6 +53,11 @@ pub const BuildCommand = struct { this_bundler.resolver.opts.react_server_components = ctx.bundler_options.react_server_components; this_bundler.options.code_splitting = ctx.bundler_options.code_splitting; this_bundler.resolver.opts.code_splitting = ctx.bundler_options.code_splitting; + this_bundler.options.minify_syntax = ctx.bundler_options.minify_syntax; + this_bundler.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace; + + this_bundler.options.minify_whitespace = ctx.bundler_options.minify_whitespace; + this_bundler.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace; this_bundler.configureLinker(); diff --git a/src/js_printer.zig b/src/js_printer.zig index f809c6280..7bd7fcc70 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -498,6 +498,9 @@ pub const Options = struct { module_type: options.OutputFormat = .preserve, + /// Used for cross-module inlining of import items when bundling + const_values: std.HashMapUnmanaged(Ref, Expr, Ref.HashCtx, 80) = .{}, + // TODO: remove this // The reason for this is: // 1. You're bundling a React component @@ -2578,78 +2581,88 @@ fn NewPrinter( // Potentially use a property access instead of an identifier var didPrint = false; - if (p.symbols().getWithLink(e.ref)) |symbol| { - if (symbol.import_item_status == .missing) { - p.addSourceMapping(expr.loc); - p.printUndefined(level); - didPrint = true; - } else if (symbol.namespace_alias) |namespace| { - if (namespace.import_record_index < p.import_records.len) { - const import_record = p.importRecord(namespace.import_record_index); - if ((comptime is_inside_bundle) or import_record.is_legacy_bundled or namespace.was_originally_property_access) { - var wrap = false; - didPrint = true; - - if (p.call_target) |target| { - wrap = e.was_originally_identifier and (target == .e_identifier and - target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)); - } - - if (wrap) { - p.printWhitespacer(ws("(0, ")); - } - p.addSourceMapping(expr.loc); - p.printNamespaceAlias(import_record.*, namespace); - - if (wrap) { - p.print(")"); - } - } else if (import_record.was_originally_require and import_record.path.is_disabled) { - p.addSourceMapping(expr.loc); - - if (import_record.handles_import_errors) { - p.printRequireError(import_record.path.text); - } else { - p.printDisabledImport(); - } - didPrint = true; - } - } + const ref = p.symbols().follow(e.ref); + const symbol = p.symbols().get(ref).?; - if (!didPrint) { + if (symbol.import_item_status == .missing) { + p.addSourceMapping(expr.loc); + p.printUndefined(level); + didPrint = true; + } else if (symbol.namespace_alias) |namespace| { + if (namespace.import_record_index < p.import_records.len) { + const import_record = p.importRecord(namespace.import_record_index); + if ((comptime is_inside_bundle) or import_record.is_legacy_bundled or namespace.was_originally_property_access) { + var wrap = false; didPrint = true; - const wrap = if (p.call_target) |target| - e.was_originally_identifier and (target == .e_identifier and - target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)) - else - false; + if (p.call_target) |target| { + wrap = e.was_originally_identifier and (target == .e_identifier and + target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)); + } if (wrap) { p.printWhitespacer(ws("(0, ")); } - - p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); - p.printSymbol(namespace.namespace_ref); - const alias = namespace.alias; - if (p.canPrintIdentifier(alias)) { - p.print("."); - // TODO: addSourceMappingForName - p.printIdentifier(alias); - } else { - p.print("["); - // TODO: addSourceMappingForName - // p.addSourceMappingForName(alias); - p.printQuotedUTF8(alias, true); - p.print("]"); - } + p.printNamespaceAlias(import_record.*, namespace); if (wrap) { p.print(")"); } + } else if (import_record.was_originally_require and import_record.path.is_disabled) { + p.addSourceMapping(expr.loc); + + if (import_record.handles_import_errors) { + p.printRequireError(import_record.path.text); + } else { + p.printDisabledImport(); + } + didPrint = true; + } + } + + if (!didPrint) { + didPrint = true; + + const wrap = if (p.call_target) |target| + e.was_originally_identifier and (target == .e_identifier and + target.e_identifier.ref.eql(expr.data.e_import_identifier.ref)) + else + false; + + if (wrap) { + p.printWhitespacer(ws("(0, ")); + } + + p.printSpaceBeforeIdentifier(); + p.addSourceMapping(expr.loc); + p.printSymbol(namespace.namespace_ref); + const alias = namespace.alias; + if (p.canPrintIdentifier(alias)) { + p.print("."); + // TODO: addSourceMappingForName + p.printIdentifier(alias); + } else { + p.print("["); + // TODO: addSourceMappingForName + // p.addSourceMappingForName(alias); + p.printQuotedUTF8(alias, true); + p.print("]"); + } + + if (wrap) { + p.print(")"); } } + } else if (p.options.const_values.count() > 0) { + if (p.options.const_values.get(ref)) |const_value| { + p.printSpaceBeforeIdentifier(); + // TODO: addSourceMappingForName + // p.addSourceMappingForName(renamer.nameForSymbol(e.ref)); + p.addSourceMapping(expr.loc); + p.printExpr(const_value, level, flags); + didPrint = true; + } } if (!didPrint) { @@ -3176,8 +3189,11 @@ fn NewPrinter( } } }, - .e_import_identifier => |e| { + .e_import_identifier => |e| inner: { const ref = p.symbols().follow(e.ref); + if (p.options.const_values.count() > 0 and p.options.const_values.contains(ref)) + break :inner; + if (p.symbols().get(ref)) |symbol| { if (symbol.namespace_alias == null and strings.eql(key.data, p.renamer.nameForSymbol(e.ref))) { if (item.initializer) |initial| { @@ -3213,8 +3229,12 @@ fn NewPrinter( } // if (strings) {} }, - .e_import_identifier => |e| { + .e_import_identifier => |e| inner: { const ref = p.symbols().follow(e.ref); + + if (p.options.const_values.count() > 0 and p.options.const_values.contains(ref)) + break :inner; + if (p.symbols().get(ref)) |symbol| { if (symbol.namespace_alias == null and strings.utf16EqlString(key.slice16(), p.renamer.nameForSymbol(e.ref))) { if (item.initializer) |initial| { diff --git a/src/options.zig b/src/options.zig index 9d96890b9..f59784d0a 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1410,6 +1410,7 @@ pub const BundleOptions = struct { inlining: bool = false, minify_whitespace: bool = false, + minify_syntax: bool = false, pub fn setProduction(this: *BundleOptions, value: bool) void { this.production = value; diff --git a/src/runtime.zig b/src/runtime.zig index 36aff3ab6..8a0c4611b 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -296,6 +296,8 @@ pub const Runtime = struct { commonjs_named_exports: bool = true, + minify_syntax: bool = false, + /// Instead of jsx("div", {}, void 0) /// -> /// { -- cgit v1.2.3