mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
kit: implement server components dev server (#14025)
This commit is contained in:
@@ -1061,7 +1061,7 @@ pub const ImportScanner = struct {
|
||||
stmts: []Stmt,
|
||||
will_transform_to_common_js: bool,
|
||||
comptime hot_module_reloading_transformations: bool,
|
||||
hot_module_reloading_context: if (hot_module_reloading_transformations) *P.ConvertESMExportsForHmr else void,
|
||||
hot_module_reloading_context: if (hot_module_reloading_transformations) *ConvertESMExportsForHmr else void,
|
||||
) !ImportScanner {
|
||||
var scanner = ImportScanner{};
|
||||
var stmts_end: usize = 0;
|
||||
@@ -1077,7 +1077,7 @@ pub const ImportScanner = struct {
|
||||
st__.* = st;
|
||||
}
|
||||
|
||||
var record: *ImportRecord = &p.import_records.items[st.import_record_index];
|
||||
const record: *ImportRecord = &p.import_records.items[st.import_record_index];
|
||||
|
||||
if (record.path.isMacro()) {
|
||||
record.is_unused = true;
|
||||
@@ -1272,7 +1272,7 @@ pub const ImportScanner = struct {
|
||||
result.* = alias;
|
||||
}
|
||||
strings.sortDesc(sorted);
|
||||
p.named_imports.ensureUnusedCapacity(sorted.len) catch unreachable;
|
||||
p.named_imports.ensureUnusedCapacity(p.allocator, sorted.len) catch bun.outOfMemory();
|
||||
|
||||
// Create named imports for these property accesses. This will
|
||||
// cause missing imports to generate useful warnings.
|
||||
@@ -1283,6 +1283,7 @@ pub const ImportScanner = struct {
|
||||
for (sorted) |alias| {
|
||||
const item = existing_items.get(alias).?;
|
||||
p.named_imports.put(
|
||||
p.allocator,
|
||||
item.ref.?,
|
||||
js_ast.NamedImport{
|
||||
.alias = alias,
|
||||
@@ -1290,7 +1291,7 @@ pub const ImportScanner = struct {
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
) catch unreachable;
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
const name: LocRef = item;
|
||||
const name_ref = name.ref.?;
|
||||
@@ -1314,8 +1315,9 @@ pub const ImportScanner = struct {
|
||||
}
|
||||
|
||||
p.named_imports.ensureUnusedCapacity(
|
||||
p.allocator,
|
||||
st.items.len + @as(usize, @intFromBool(st.default_name != null)) + @as(usize, @intFromBool(st.star_name_loc != null)),
|
||||
) catch unreachable;
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
if (st.star_name_loc) |loc| {
|
||||
p.named_imports.putAssumeCapacity(
|
||||
@@ -1370,7 +1372,7 @@ pub const ImportScanner = struct {
|
||||
const name: LocRef = item.name;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
try p.named_imports.put(name_ref, js_ast.NamedImport{
|
||||
try p.named_imports.put(p.allocator, name_ref, js_ast.NamedImport{
|
||||
.alias = item.alias,
|
||||
.alias_loc = name.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
@@ -1486,9 +1488,9 @@ pub const ImportScanner = struct {
|
||||
// Rewrite this export to be:
|
||||
// exports.default =
|
||||
// But only if it's anonymous
|
||||
if (!hot_module_reloading_transformations and will_transform_to_common_js) {
|
||||
if (!hot_module_reloading_transformations and will_transform_to_common_js and P != bun.bundle_v2.AstBuilder) {
|
||||
const expr = st.value.toExpr();
|
||||
var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||||
var export_default_args = try p.allocator.alloc(Expr, 2);
|
||||
export_default_args[0] = p.@"module.exports"(expr.loc);
|
||||
export_default_args[1] = expr;
|
||||
stmt = p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc);
|
||||
@@ -1504,7 +1506,7 @@ pub const ImportScanner = struct {
|
||||
|
||||
if (st.alias) |alias| {
|
||||
// "export * as ns from 'path'"
|
||||
try p.named_imports.put(st.namespace_ref, js_ast.NamedImport{
|
||||
try p.named_imports.put(p.allocator, st.namespace_ref, js_ast.NamedImport{
|
||||
.alias = null,
|
||||
.alias_is_star = true,
|
||||
.alias_loc = alias.loc,
|
||||
@@ -1522,13 +1524,13 @@ pub const ImportScanner = struct {
|
||||
},
|
||||
.s_export_from => |st| {
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
p.named_imports.ensureUnusedCapacity(st.items.len) catch unreachable;
|
||||
p.named_imports.ensureUnusedCapacity(p.allocator, st.items.len) catch unreachable;
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref orelse p.panic("Expected export from item to have a name {any}", .{st});
|
||||
// Note that the imported alias is not item.Alias, which is the
|
||||
// exported alias. This is somewhat confusing because each
|
||||
// SExportFrom statement is basically SImport + SExportClause in one.
|
||||
try p.named_imports.put(ref, js_ast.NamedImport{
|
||||
try p.named_imports.put(p.allocator, ref, js_ast.NamedImport{
|
||||
.alias_is_star = false,
|
||||
.alias = item.original_name,
|
||||
.alias_loc = item.name.loc,
|
||||
@@ -2842,7 +2844,7 @@ pub const ScanPassResult = struct {
|
||||
pub fn init(allocator: Allocator) ScanPassResult {
|
||||
return .{
|
||||
.import_records = ListManaged(ImportRecord).init(allocator),
|
||||
.named_imports = js_ast.Ast.NamedImports.init(allocator),
|
||||
.named_imports = .{},
|
||||
.used_symbols = ParsePassSymbolUsageMap.init(allocator),
|
||||
.import_records_to_keep = ListManaged(u32).init(allocator),
|
||||
.approximate_newline_count = 0,
|
||||
@@ -3715,7 +3717,6 @@ pub const Parser = struct {
|
||||
part.symbol_uses = .{};
|
||||
return js_ast.Result{
|
||||
.ast = js_ast.Ast{
|
||||
.allocator = p.allocator,
|
||||
.import_records = ImportRecord.List.init(p.import_records.items),
|
||||
.redirect_import_record_index = id,
|
||||
.named_imports = p.named_imports,
|
||||
@@ -4421,7 +4422,7 @@ const ParserFeatures = struct {
|
||||
scan_only: bool = false,
|
||||
};
|
||||
|
||||
const ImportItemForNamespaceMap = bun.StringArrayHashMap(LocRef);
|
||||
pub const ImportItemForNamespaceMap = bun.StringArrayHashMap(LocRef);
|
||||
|
||||
pub const KnownGlobal = enum {
|
||||
WeakSet,
|
||||
@@ -5797,7 +5798,7 @@ fn NewParser_(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) anyerror!void {
|
||||
pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) !void {
|
||||
if (p.named_exports.get(alias)) |name| {
|
||||
// Duplicate exports are an error
|
||||
var notes = try p.allocator.alloc(logger.Data, 1);
|
||||
@@ -5814,7 +5815,7 @@ fn NewParser_(
|
||||
.{std.mem.trim(u8, alias, "\"'")},
|
||||
);
|
||||
} else if (!p.isDeoptimizedCommonJS()) {
|
||||
try p.named_exports.put(alias, js_ast.NamedExport{ .alias_loc = loc, .ref = ref });
|
||||
try p.named_exports.put(p.allocator, alias, js_ast.NamedExport{ .alias_loc = loc, .ref = ref });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5865,7 +5866,6 @@ fn NewParser_(
|
||||
},
|
||||
.e_private_identifier => |private| {
|
||||
return p.loadNameFromRef(private.ref);
|
||||
// return p.loadNameFromRef()
|
||||
},
|
||||
else => {
|
||||
return "property";
|
||||
@@ -6052,7 +6052,7 @@ fn NewParser_(
|
||||
};
|
||||
declared_symbols.appendAssumeCapacity(.{ .ref = ref, .is_top_level = true });
|
||||
try p.is_import_item.put(allocator, ref, {});
|
||||
try p.named_imports.put(ref, js_ast.NamedImport{
|
||||
try p.named_imports.put(allocator, ref, js_ast.NamedImport{
|
||||
.alias = alias_name,
|
||||
.alias_loc = logger.Loc{},
|
||||
.namespace_ref = namespace_ref,
|
||||
@@ -6147,7 +6147,7 @@ fn NewParser_(
|
||||
declared_symbols.appendAssumeCapacity(.{ .ref = entry.ref, .is_top_level = true });
|
||||
try p.module_scope.generated.push(allocator, entry.ref);
|
||||
try p.is_import_item.put(allocator, entry.ref, {});
|
||||
try p.named_imports.put(entry.ref, .{
|
||||
try p.named_imports.put(allocator, entry.ref, .{
|
||||
.alias = entry.name,
|
||||
.alias_loc = logger.Loc.Empty,
|
||||
.namespace_ref = namespace_ref,
|
||||
@@ -9200,6 +9200,8 @@ fn NewParser_(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (import_tag == .kit_resolve_to_ssr_graph) {
|
||||
p.import_records.items[stmt.import_record_index].tag = import_tag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10206,7 +10208,7 @@ fn NewParser_(
|
||||
isForAwait = false;
|
||||
} else {
|
||||
// TODO: improve error handling here
|
||||
// didGenerateError := p.markSyntaxFeature(compat.ForAwait, awaitRange)
|
||||
// didGenerateError := p.markSyntaxFeature(compat.ForAwait, awaitRange)
|
||||
if (p.fn_or_arrow_data_parse.is_top_level) {
|
||||
p.top_level_await_keyword = await_range;
|
||||
// p.markSyntaxFeature(compat.TopLevelAwait, awaitRange)
|
||||
@@ -12209,6 +12211,7 @@ fn NewParser_(
|
||||
const SupportedAttribute = enum {
|
||||
type,
|
||||
embed,
|
||||
bunKitGraph,
|
||||
};
|
||||
|
||||
var has_seen_embed_true = false;
|
||||
@@ -12217,21 +12220,17 @@ fn NewParser_(
|
||||
const supported_attribute: ?SupportedAttribute = brk: {
|
||||
// Parse the key
|
||||
if (p.lexer.isIdentifierOrKeyword()) {
|
||||
if (strings.eqlComptime(p.lexer.identifier, "type")) {
|
||||
break :brk .type;
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(p.lexer.identifier, "embed")) {
|
||||
break :brk .embed;
|
||||
inline for (comptime std.enums.values(SupportedAttribute)) |t| {
|
||||
if (strings.eqlComptime(p.lexer.identifier, @tagName(t))) {
|
||||
break :brk t;
|
||||
}
|
||||
}
|
||||
} else if (p.lexer.token == .t_string_literal) {
|
||||
if (p.lexer.string_literal_is_ascii) {
|
||||
if (strings.eqlComptime(p.lexer.string_literal_slice, "type")) {
|
||||
break :brk .type;
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(p.lexer.string_literal_slice, "embed")) {
|
||||
break :brk .embed;
|
||||
inline for (comptime std.enums.values(SupportedAttribute)) |t| {
|
||||
if (strings.eqlComptime(p.lexer.string_literal_slice, @tagName(t))) {
|
||||
break :brk t;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -12275,6 +12274,13 @@ fn NewParser_(
|
||||
}
|
||||
}
|
||||
},
|
||||
.bunKitGraph => {
|
||||
if (strings.eqlComptime(p.lexer.string_literal_slice, "ssr")) {
|
||||
path.import_tag = .kit_resolve_to_ssr_graph;
|
||||
} else {
|
||||
try p.lexer.addRangeError(p.lexer.range(), "'bunKitGraph' can only be set to 'ssr'", .{}, true);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16225,10 +16231,10 @@ fn NewParser_(
|
||||
return exp;
|
||||
}
|
||||
|
||||
// // Capture "this" inside arrow functions that will be lowered into normal
|
||||
// // Capture "this" inside arrow functions that will be lowered into normal
|
||||
// // function expressions for older language environments
|
||||
// if p.fnOrArrowDataVisit.isArrow && p.options.unsupportedJSFeatures.Has(compat.Arrow) && p.fnOnlyDataVisit.isThisNested {
|
||||
// return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.captureThis()}}, exprOut{}
|
||||
// return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.captureThis()}}, exprOut{}
|
||||
// }
|
||||
},
|
||||
.e_import_meta => {
|
||||
@@ -19931,7 +19937,7 @@ fn NewParser_(
|
||||
data.cases[i].value = p.visitExpr(val);
|
||||
// TODO: error messages
|
||||
// Check("case", *c.Value, c.Value.Loc)
|
||||
// p.warnAboutTypeofAndString(s.Test, *c.Value)
|
||||
// p.warnAboutTypeofAndString(s.Test, *c.Value)
|
||||
}
|
||||
var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, case.body);
|
||||
p.visitStmts(&_stmts, StmtsKind.none) catch unreachable;
|
||||
@@ -23488,15 +23494,14 @@ fn NewParser_(
|
||||
parts_list.cap = @intCast(input_parts.len);
|
||||
|
||||
return .{
|
||||
.allocator = p.allocator,
|
||||
.runtime_imports = p.runtime_imports,
|
||||
.parts = parts_list,
|
||||
.module_scope = p.module_scope.*,
|
||||
.symbols = js_ast.Symbol.List.init(p.symbols.items),
|
||||
.symbols = js_ast.Symbol.List.fromList(p.symbols),
|
||||
.exports_ref = p.exports_ref,
|
||||
.wrapper_ref = wrapper_ref,
|
||||
.module_ref = p.module_ref,
|
||||
.import_records = ImportRecord.List.init(p.import_records.items),
|
||||
.import_records = ImportRecord.List.fromList(p.import_records),
|
||||
.export_star_import_records = p.export_star_import_records.items,
|
||||
.approximate_newline_count = p.lexer.approximate_newline_count,
|
||||
.exports_kind = exports_kind,
|
||||
@@ -23588,317 +23593,6 @@ fn NewParser_(
|
||||
return false;
|
||||
}
|
||||
|
||||
const ConvertESMExportsForHmr = struct {
|
||||
last_part: *js_ast.Part,
|
||||
imports_seen: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
|
||||
export_props: std.ArrayListUnmanaged(G.Property) = .{},
|
||||
stmts: std.ArrayListUnmanaged(Stmt) = .{},
|
||||
|
||||
fn convertStmt(ctx: *ConvertESMExportsForHmr, p: *P, stmt: Stmt) !void {
|
||||
const new_stmt = switch (stmt.data) {
|
||||
else => stmt,
|
||||
.s_local => |st| stmt: {
|
||||
if (!st.is_export) break :stmt stmt;
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
if (st.kind.isReassignable()) {
|
||||
for (st.decls.slice()) |decl| {
|
||||
try ctx.visitBindingForKitModuleExports(p, decl.binding, true);
|
||||
}
|
||||
} else {
|
||||
// TODO: remove this dupe
|
||||
var dupe_decls = try std.ArrayListUnmanaged(G.Decl).initCapacity(p.allocator, st.decls.len);
|
||||
|
||||
for (st.decls.slice()) |decl| {
|
||||
bun.assert(decl.value != null); // const must be initialized
|
||||
|
||||
switch (decl.binding.data) {
|
||||
.b_missing => @panic("binding missing"),
|
||||
|
||||
.b_identifier => |id| {
|
||||
const symbol = p.symbols.items[id.ref.inner_index];
|
||||
|
||||
// if the symbol is not used, we don't need to preserve
|
||||
// a binding in this scope. we can move it to the exports object.
|
||||
if (symbol.use_count_estimate != 0 or !decl.value.?.canBeMoved()) {
|
||||
dupe_decls.appendAssumeCapacity(decl);
|
||||
}
|
||||
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = symbol.original_name }, decl.binding.loc),
|
||||
.value = decl.value,
|
||||
});
|
||||
},
|
||||
|
||||
else => {
|
||||
dupe_decls.appendAssumeCapacity(decl);
|
||||
try ctx.visitBindingForKitModuleExports(p, decl.binding, false);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (dupe_decls.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
st.decls = G.Decl.List.fromList(dupe_decls);
|
||||
}
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_default => |st| stmt: {
|
||||
// Simple case: we can move this to the default property of the exports object
|
||||
if (st.canBeMoved()) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
});
|
||||
// no statement emitted
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we need a temporary
|
||||
const temp_id = p.generateTempRef("default_export");
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = temp_id, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, temp_id, .{ .count_estimate = 1 });
|
||||
try p.module_scope.generated.push(p.allocator, temp_id);
|
||||
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(temp_id, stmt.loc),
|
||||
});
|
||||
|
||||
break :stmt Stmt.alloc(S.Local, .{
|
||||
.kind = .k_const,
|
||||
.decls = try G.Decl.List.fromSlice(p.allocator, &.{
|
||||
.{
|
||||
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = temp_id }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
},
|
||||
}),
|
||||
}, stmt.loc);
|
||||
},
|
||||
.s_class => |st| stmt: {
|
||||
// Strip the "export" keyword
|
||||
if (!st.is_export) break :stmt stmt;
|
||||
|
||||
// Export as CommonJS
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = p.symbols.items[st.class.class_name.?.ref.?.inner_index].original_name,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(st.class.class_name.?.ref.?, stmt.loc),
|
||||
});
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_function => |st| stmt: {
|
||||
// Strip the "export" keyword
|
||||
if (!st.func.flags.contains(.is_export)) break :stmt stmt;
|
||||
|
||||
st.func.flags.remove(.is_export);
|
||||
|
||||
// Export as CommonJS
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = p.symbols.items[st.func.name.?.ref.?.inner_index].original_name,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(st.func.name.?.ref.?, stmt.loc),
|
||||
});
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_clause => |st| {
|
||||
for (st.items) |item| {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = item.alias,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(item.name.ref.?, item.name.loc),
|
||||
});
|
||||
}
|
||||
|
||||
return; // do not emit a statement here
|
||||
},
|
||||
|
||||
.s_export_from => |st| {
|
||||
_ = st; // autofix
|
||||
@panic("TODO s_export_from");
|
||||
},
|
||||
.s_export_star => |st| {
|
||||
_ = st; // autofix
|
||||
@panic("TODO s_export_star");
|
||||
},
|
||||
|
||||
// De-duplicate import statements. It is okay to disregard
|
||||
// named/default imports here as we always rewrite them as
|
||||
// full qualified property accesses (need to so live-bindings)
|
||||
.s_import => |st| stmt: {
|
||||
const gop = try ctx.imports_seen.getOrPut(p.allocator, st.import_record_index);
|
||||
if (gop.found_existing) return;
|
||||
break :stmt stmt;
|
||||
},
|
||||
};
|
||||
|
||||
try ctx.stmts.append(p.allocator, new_stmt);
|
||||
}
|
||||
|
||||
fn visitBindingForKitModuleExports(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: *P,
|
||||
binding: Binding,
|
||||
is_live_binding: bool,
|
||||
) !void {
|
||||
switch (binding.data) {
|
||||
.b_missing => @panic("missing!"),
|
||||
.b_identifier => |id| {
|
||||
try ctx.visitRefForKitModuleExports(p, id.ref, binding.loc, is_live_binding);
|
||||
},
|
||||
.b_array => |array| {
|
||||
for (array.items) |item| {
|
||||
try ctx.visitBindingForKitModuleExports(p, item.binding, is_live_binding);
|
||||
}
|
||||
},
|
||||
.b_object => |object| {
|
||||
for (object.properties) |item| {
|
||||
try ctx.visitBindingForKitModuleExports(p, item.value, is_live_binding);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visitRefForKitModuleExports(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: *P,
|
||||
ref: Ref,
|
||||
loc: logger.Loc,
|
||||
is_live_binding: bool,
|
||||
) !void {
|
||||
const symbol = p.symbols.items[ref.inner_index];
|
||||
const id = Expr.initIdentifier(ref, loc);
|
||||
if (is_live_binding) {
|
||||
const key = Expr.init(E.String, .{
|
||||
.data = symbol.original_name,
|
||||
}, loc);
|
||||
|
||||
// This is technically incorrect in that we've marked this as a
|
||||
// top level symbol. but all we care about is preventing name
|
||||
// collisions, not necessarily the best minificaiton (dev only)
|
||||
const arg1 = p.generateTempRef(symbol.original_name);
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = arg1, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, arg1, .{ .count_estimate = 1 });
|
||||
try p.module_scope.generated.push(p.allocator, arg1);
|
||||
|
||||
// Live bindings need to update the value internally and externally.
|
||||
// 'get abc() { return abc }'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.kind = .get,
|
||||
.key = key,
|
||||
.value = Expr.init(E.Function, .{ .func = .{
|
||||
.body = .{
|
||||
.stmts = try p.allocator.dupe(Stmt, &.{
|
||||
Stmt.alloc(S.Return, .{ .value = id }, loc),
|
||||
}),
|
||||
.loc = loc,
|
||||
},
|
||||
} }, loc),
|
||||
});
|
||||
// 'set abc(abc2) { abc = abc2 }'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.kind = .set,
|
||||
.key = key,
|
||||
.value = Expr.init(E.Function, .{ .func = .{
|
||||
.args = try p.allocator.dupe(G.Arg, &.{.{
|
||||
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = arg1 }, loc),
|
||||
}}),
|
||||
.body = .{
|
||||
.stmts = try p.allocator.dupe(Stmt, &.{
|
||||
Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(id, Expr.initIdentifier(arg1, loc)),
|
||||
}, loc),
|
||||
}),
|
||||
.loc = loc,
|
||||
},
|
||||
} }, loc),
|
||||
});
|
||||
} else {
|
||||
// 'abc,'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = symbol.original_name,
|
||||
}, loc),
|
||||
.value = id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(ctx: *ConvertESMExportsForHmr, p: *P, all_parts: []js_ast.Part) ![]js_ast.Part {
|
||||
if (ctx.export_props.items.len > 0) {
|
||||
// add a marker for the client runtime to tell that this is an ES module
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(
|
||||
Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.module_ref, logger.Loc.Empty),
|
||||
.name = "__esModule",
|
||||
.name_loc = logger.Loc.Empty,
|
||||
}, logger.Loc.Empty),
|
||||
Expr.init(E.Boolean, .{ .value = true }, logger.Loc.Empty),
|
||||
),
|
||||
}, logger.Loc.Empty));
|
||||
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(
|
||||
Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.module_ref, logger.Loc.Empty),
|
||||
.name = "exports",
|
||||
.name_loc = logger.Loc.Empty,
|
||||
}, logger.Loc.Empty),
|
||||
Expr.init(E.Object, .{
|
||||
.properties = G.Property.List.fromList(ctx.export_props),
|
||||
}, logger.Loc.Empty),
|
||||
),
|
||||
}, logger.Loc.Empty));
|
||||
|
||||
// mark a dependency on module_ref so it is renamed
|
||||
try ctx.last_part.symbol_uses.put(p.allocator, p.module_ref, .{ .count_estimate = 1 });
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = p.module_ref, .is_top_level = true });
|
||||
}
|
||||
|
||||
// TODO: this is a tiny mess. it is honestly trying to hard to merge all parts into one
|
||||
for (all_parts[0 .. all_parts.len - 1]) |*part| {
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, part.declared_symbols);
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, part.import_record_indices.slice());
|
||||
for (part.symbol_uses.keys(), part.symbol_uses.values()) |k, v| {
|
||||
const gop = try ctx.last_part.symbol_uses.getOrPut(p.allocator, k);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = v;
|
||||
} else {
|
||||
gop.value_ptr.count_estimate += v.count_estimate;
|
||||
}
|
||||
}
|
||||
part.stmts = &.{};
|
||||
part.declared_symbols.entries.len = 0;
|
||||
part.tag = .dead_due_to_inlining;
|
||||
part.dependencies.clearRetainingCapacity();
|
||||
try part.dependencies.push(p.allocator, .{
|
||||
.part_index = @intCast(all_parts.len - 1),
|
||||
.source_index = p.source.index,
|
||||
});
|
||||
}
|
||||
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, p.import_records_for_current_part.items);
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, p.declared_symbols);
|
||||
|
||||
ctx.last_part.stmts = ctx.stmts.items;
|
||||
ctx.last_part.tag = .none;
|
||||
|
||||
return all_parts;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
log: *logger.Log,
|
||||
@@ -23933,7 +23627,7 @@ fn NewParser_(
|
||||
.define = define,
|
||||
.import_records = undefined,
|
||||
.named_imports = undefined,
|
||||
.named_exports = js_ast.Ast.NamedExports.init(allocator),
|
||||
.named_exports = .{},
|
||||
.log = log,
|
||||
.allocator = allocator,
|
||||
.options = opts,
|
||||
@@ -23979,7 +23673,7 @@ fn NewParser_(
|
||||
|
||||
if (comptime !only_scan_imports_and_do_not_visit) {
|
||||
this.import_records = @TypeOf(this.import_records).init(allocator);
|
||||
this.named_imports = NamedImportsType.init(allocator);
|
||||
this.named_imports = .{};
|
||||
}
|
||||
|
||||
this.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(this);
|
||||
@@ -24095,6 +23789,316 @@ const WrapMode = enum {
|
||||
bun_commonjs,
|
||||
};
|
||||
|
||||
pub const ConvertESMExportsForHmr = struct {
|
||||
last_part: *js_ast.Part,
|
||||
imports_seen: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
|
||||
export_props: std.ArrayListUnmanaged(G.Property) = .{},
|
||||
stmts: std.ArrayListUnmanaged(Stmt) = .{},
|
||||
|
||||
fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void {
|
||||
const new_stmt = switch (stmt.data) {
|
||||
else => stmt,
|
||||
.s_local => |st| stmt: {
|
||||
if (!st.is_export) break :stmt stmt;
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
if (st.kind.isReassignable()) {
|
||||
for (st.decls.slice()) |decl| {
|
||||
try ctx.visitBindingForKitModuleExports(p, decl.binding, true);
|
||||
}
|
||||
} else {
|
||||
// TODO: remove this dupe
|
||||
var dupe_decls = try std.ArrayListUnmanaged(G.Decl).initCapacity(p.allocator, st.decls.len);
|
||||
|
||||
for (st.decls.slice()) |decl| {
|
||||
bun.assert(decl.value != null); // const must be initialized
|
||||
|
||||
switch (decl.binding.data) {
|
||||
.b_missing => {},
|
||||
|
||||
.b_identifier => |id| {
|
||||
const symbol = p.symbols.items[id.ref.inner_index];
|
||||
|
||||
// if the symbol is not used, we don't need to preserve
|
||||
// a binding in this scope. we can move it to the exports object.
|
||||
if (symbol.use_count_estimate == 0 and decl.value.?.canBeMoved()) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = symbol.original_name }, decl.binding.loc),
|
||||
.value = decl.value,
|
||||
});
|
||||
} else {
|
||||
dupe_decls.appendAssumeCapacity(decl);
|
||||
try ctx.visitBindingForKitModuleExports(p, decl.binding, false);
|
||||
}
|
||||
},
|
||||
|
||||
else => {
|
||||
dupe_decls.appendAssumeCapacity(decl);
|
||||
try ctx.visitBindingForKitModuleExports(p, decl.binding, false);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (dupe_decls.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
st.decls = G.Decl.List.fromList(dupe_decls);
|
||||
}
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_default => |st| stmt: {
|
||||
// Simple case: we can move this to the default property of the exports object
|
||||
if (st.canBeMoved()) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
});
|
||||
// no statement emitted
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we need a temporary
|
||||
const temp_id = p.generateTempRef("default_export");
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = temp_id, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, temp_id, .{ .count_estimate = 1 });
|
||||
try p.current_scope.generated.push(p.allocator, temp_id);
|
||||
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(temp_id, stmt.loc),
|
||||
});
|
||||
|
||||
break :stmt Stmt.alloc(S.Local, .{
|
||||
.kind = .k_const,
|
||||
.decls = try G.Decl.List.fromSlice(p.allocator, &.{
|
||||
.{
|
||||
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = temp_id }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
},
|
||||
}),
|
||||
}, stmt.loc);
|
||||
},
|
||||
.s_class => |st| stmt: {
|
||||
// Strip the "export" keyword
|
||||
if (!st.is_export) break :stmt stmt;
|
||||
|
||||
// Export as CommonJS
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = p.symbols.items[st.class.class_name.?.ref.?.inner_index].original_name,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(st.class.class_name.?.ref.?, stmt.loc),
|
||||
});
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_function => |st| stmt: {
|
||||
// Strip the "export" keyword
|
||||
if (!st.func.flags.contains(.is_export)) break :stmt stmt;
|
||||
|
||||
st.func.flags.remove(.is_export);
|
||||
|
||||
// Export as CommonJS
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = p.symbols.items[st.func.name.?.ref.?.inner_index].original_name,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(st.func.name.?.ref.?, stmt.loc),
|
||||
});
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_clause => |st| {
|
||||
for (st.items) |item| {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = item.alias,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(item.name.ref.?, item.name.loc),
|
||||
});
|
||||
}
|
||||
|
||||
return; // do not emit a statement here
|
||||
},
|
||||
|
||||
.s_export_from => {
|
||||
bun.todoPanic(@src(), "hot-module-reloading instrumentation for 'export {{ ... }} from'", .{});
|
||||
},
|
||||
.s_export_star => {
|
||||
bun.todoPanic(@src(), "hot-module-reloading instrumentation for 'export * from'", .{});
|
||||
},
|
||||
|
||||
// De-duplicate import statements. It is okay to disregard
|
||||
// named/default imports here as we always rewrite them as
|
||||
// full qualified property accesses (need to so live-bindings)
|
||||
.s_import => |st| stmt: {
|
||||
const gop = try ctx.imports_seen.getOrPut(p.allocator, st.import_record_index);
|
||||
if (gop.found_existing) return;
|
||||
break :stmt stmt;
|
||||
},
|
||||
};
|
||||
|
||||
try ctx.stmts.append(p.allocator, new_stmt);
|
||||
}
|
||||
|
||||
fn visitBindingForKitModuleExports(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
binding: Binding,
|
||||
is_live_binding: bool,
|
||||
) !void {
|
||||
switch (binding.data) {
|
||||
.b_missing => {},
|
||||
.b_identifier => |id| {
|
||||
try ctx.visitRefForKitModuleExports(p, id.ref, binding.loc, is_live_binding);
|
||||
},
|
||||
.b_array => |array| {
|
||||
for (array.items) |item| {
|
||||
try ctx.visitBindingForKitModuleExports(p, item.binding, is_live_binding);
|
||||
}
|
||||
},
|
||||
.b_object => |object| {
|
||||
for (object.properties) |item| {
|
||||
try ctx.visitBindingForKitModuleExports(p, item.value, is_live_binding);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visitRefForKitModuleExports(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
ref: Ref,
|
||||
loc: logger.Loc,
|
||||
is_live_binding: bool,
|
||||
) !void {
|
||||
const symbol = p.symbols.items[ref.inner_index];
|
||||
const id = Expr.initIdentifier(ref, loc);
|
||||
if (is_live_binding) {
|
||||
const key = Expr.init(E.String, .{
|
||||
.data = symbol.original_name,
|
||||
}, loc);
|
||||
|
||||
// This is technically incorrect in that we've marked this as a
|
||||
// top level symbol. but all we care about is preventing name
|
||||
// collisions, not necessarily the best minificaiton (dev only)
|
||||
const arg1 = p.generateTempRef(symbol.original_name);
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = arg1, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, arg1, .{ .count_estimate = 1 });
|
||||
try p.current_scope.generated.push(p.allocator, arg1);
|
||||
|
||||
// Live bindings need to update the value internally and externally.
|
||||
// 'get abc() { return abc }'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.kind = .get,
|
||||
.key = key,
|
||||
.value = Expr.init(E.Function, .{ .func = .{
|
||||
.body = .{
|
||||
.stmts = try p.allocator.dupe(Stmt, &.{
|
||||
Stmt.alloc(S.Return, .{ .value = id }, loc),
|
||||
}),
|
||||
.loc = loc,
|
||||
},
|
||||
} }, loc),
|
||||
});
|
||||
// 'set abc(abc2) { abc = abc2 }'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.kind = .set,
|
||||
.key = key,
|
||||
.value = Expr.init(E.Function, .{ .func = .{
|
||||
.args = try p.allocator.dupe(G.Arg, &.{.{
|
||||
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = arg1 }, loc),
|
||||
}}),
|
||||
.body = .{
|
||||
.stmts = try p.allocator.dupe(Stmt, &.{
|
||||
Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(id, Expr.initIdentifier(arg1, loc)),
|
||||
}, loc),
|
||||
}),
|
||||
.loc = loc,
|
||||
},
|
||||
} }, loc),
|
||||
});
|
||||
} else {
|
||||
// 'abc,'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = symbol.original_name,
|
||||
}, loc),
|
||||
.value = id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(ctx: *ConvertESMExportsForHmr, p: anytype, all_parts: []js_ast.Part) ![]js_ast.Part {
|
||||
if (ctx.export_props.items.len > 0) {
|
||||
// add a marker for the client runtime to tell that this is an ES module
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(
|
||||
Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.module_ref, logger.Loc.Empty),
|
||||
.name = "__esModule",
|
||||
.name_loc = logger.Loc.Empty,
|
||||
}, logger.Loc.Empty),
|
||||
Expr.init(E.Boolean, .{ .value = true }, logger.Loc.Empty),
|
||||
),
|
||||
}, logger.Loc.Empty));
|
||||
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(
|
||||
Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.module_ref, logger.Loc.Empty),
|
||||
.name = "exports",
|
||||
.name_loc = logger.Loc.Empty,
|
||||
}, logger.Loc.Empty),
|
||||
Expr.init(E.Object, .{
|
||||
.properties = G.Property.List.fromList(ctx.export_props),
|
||||
}, logger.Loc.Empty),
|
||||
),
|
||||
}, logger.Loc.Empty));
|
||||
|
||||
// mark a dependency on module_ref so it is renamed
|
||||
try ctx.last_part.symbol_uses.put(p.allocator, p.module_ref, .{ .count_estimate = 1 });
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = p.module_ref, .is_top_level = true });
|
||||
}
|
||||
|
||||
// TODO: this is a tiny mess. it is honestly trying to hard to merge all parts into one
|
||||
for (all_parts[0 .. all_parts.len - 1]) |*part| {
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, part.declared_symbols);
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, part.import_record_indices.slice());
|
||||
for (part.symbol_uses.keys(), part.symbol_uses.values()) |k, v| {
|
||||
const gop = try ctx.last_part.symbol_uses.getOrPut(p.allocator, k);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = v;
|
||||
} else {
|
||||
gop.value_ptr.count_estimate += v.count_estimate;
|
||||
}
|
||||
}
|
||||
part.stmts = &.{};
|
||||
part.declared_symbols.entries.len = 0;
|
||||
part.tag = .dead_due_to_inlining;
|
||||
part.dependencies.clearRetainingCapacity();
|
||||
try part.dependencies.push(p.allocator, .{
|
||||
.part_index = @intCast(all_parts.len - 1),
|
||||
.source_index = p.source.index,
|
||||
});
|
||||
}
|
||||
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, p.import_records_for_current_part.items);
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, p.declared_symbols);
|
||||
|
||||
ctx.last_part.stmts = ctx.stmts.items;
|
||||
ctx.last_part.tag = .none;
|
||||
|
||||
return all_parts;
|
||||
}
|
||||
};
|
||||
|
||||
/// Equivalent of esbuild's js_ast_helpers.ToInt32
|
||||
fn floatToInt32(f: f64) i32 {
|
||||
// Special-case non-finite numbers
|
||||
|
||||
Reference in New Issue
Block a user