aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-21 23:19:06 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-21 23:19:06 -0700
commit636d2e486f7770b9cf59aed1d66dfe75f45fa2a5 (patch)
treeef0bbb8ccb01dff4fd4dceed4eb6c6a837e1e6ec
parent83c83949abc894251d671ad59146f043a32d4085 (diff)
downloadbun-636d2e486f7770b9cf59aed1d66dfe75f45fa2a5.tar.gz
bun-636d2e486f7770b9cf59aed1d66dfe75f45fa2a5.tar.zst
bun-636d2e486f7770b9cf59aed1d66dfe75f45fa2a5.zip
implement step 6
-rw-r--r--src/baby_list.zig9
-rw-r--r--src/bundler/bundle_v2.zig460
-rw-r--r--src/fs.zig27
-rw-r--r--src/import_record.zig8
-rw-r--r--src/js_parser/js_parser.zig20
-rw-r--r--src/logger.zig19
-rw-r--r--src/runtime.js38
-rw-r--r--src/string_immutable.zig73
-rw-r--r--src/string_mutable.zig7
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 "_";