aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-04-15 05:17:12 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-04-15 05:17:12 -0700
commitdf88b998d682ec4ca4ee77e64fbf6efc5d8cf9fa (patch)
tree1acd205a7f302b284b21300834c6413991aa5cab
parentb687c3ab886c19c4e933586bbb922a3c2e00456f (diff)
downloadbun-df88b998d682ec4ca4ee77e64fbf6efc5d8cf9fa.tar.gz
bun-df88b998d682ec4ca4ee77e64fbf6efc5d8cf9fa.tar.zst
bun-df88b998d682ec4ca4ee77e64fbf6efc5d8cf9fa.zip
Mostly implement cross-module constant inlining, but disable it
There are some test failures
-rw-r--r--src/bundler.zig4
-rw-r--r--src/bundler/bundle_v2.zig84
-rw-r--r--src/cli.zig6
-rw-r--r--src/cli/build_command.zig5
-rw-r--r--src/js_printer.zig140
-rw-r--r--src/options.zig1
-rw-r--r--src/runtime.zig2
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)
/// ->
/// {