mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 14:22:01 +00:00
implement step 6
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
27
src/fs.zig
27
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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 "_";
|
||||
|
||||
Reference in New Issue
Block a user