aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/schema.d.ts3
-rw-r--r--src/api/schema.js4
-rw-r--r--src/api/schema.peechy1
-rw-r--r--src/api/schema.zig3
-rw-r--r--src/bunfig.zig66
-rw-r--r--src/cli.zig31
-rw-r--r--src/http.zig3
-rw-r--r--src/js_ast.zig19
-rw-r--r--src/js_parser/js_parser.zig974
-rw-r--r--src/options.zig8
-rw-r--r--src/resolver/package_json.zig2
-rw-r--r--src/resolver/resolver.zig3
-rw-r--r--src/resolver/tsconfig_json.zig11
-rw-r--r--src/string_immutable.zig24
-rw-r--r--src/string_mutable.zig90
15 files changed, 1203 insertions, 39 deletions
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts
index f2f6d5b8f..d9713fdd2 100644
--- a/src/api/schema.d.ts
+++ b/src/api/schema.d.ts
@@ -158,12 +158,15 @@ export const CSSInJSBehaviorKeys: {
export const enum JSXRuntime {
automatic = 1,
classic = 2,
+ solid = 3,
}
export const JSXRuntimeKeys: {
1: "automatic";
automatic: "automatic";
2: "classic";
classic: "classic";
+ 3: "solid";
+ solid: "solid";
};
export const enum ScanDependencyMode {
app = 1,
diff --git a/src/api/schema.js b/src/api/schema.js
index 1db65f2ca..ac28f56ab 100644
--- a/src/api/schema.js
+++ b/src/api/schema.js
@@ -581,14 +581,18 @@ const CSSInJSBehaviorKeys = {
const JSXRuntime = {
1: 1,
2: 2,
+ 3: 3,
automatic: 1,
classic: 2,
+ solid: 3,
};
const JSXRuntimeKeys = {
1: "automatic",
2: "classic",
+ 3: "solid",
automatic: "automatic",
classic: "classic",
+ solid: "solid",
};
function decodeJSX(bb) {
diff --git a/src/api/schema.peechy b/src/api/schema.peechy
index f9188987a..225fcaac3 100644
--- a/src/api/schema.peechy
+++ b/src/api/schema.peechy
@@ -124,6 +124,7 @@ smol CSSInJSBehavior {
smol JSXRuntime {
automatic = 1;
classic = 2;
+ solid = 3;
}
struct JSX {
diff --git a/src/api/schema.zig b/src/api/schema.zig
index f6837c58d..e16c4ed79 100644
--- a/src/api/schema.zig
+++ b/src/api/schema.zig
@@ -842,6 +842,9 @@ pub const Api = struct {
/// classic
classic,
+ /// solid
+ solid,
+
_,
pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void {
diff --git a/src/bunfig.zig b/src/bunfig.zig
index 950c5c8b0..b0c763498 100644
--- a/src/bunfig.zig
+++ b/src/bunfig.zig
@@ -416,6 +416,72 @@ pub const Bunfig = struct {
}
}
+ var jsx_factory: string = "";
+ var jsx_fragment: string = "";
+ var jsx_import_source: string = "";
+ var jsx_runtime = Api.JsxRuntime.automatic;
+ var jsx_dev = true;
+
+ if (json.get("jsx")) |expr| {
+ if (expr.asString(allocator)) |value| {
+ if (strings.eqlComptime(value, "react")) {
+ jsx_runtime = Api.JsxRuntime.classic;
+ } else if (strings.eqlComptime(value, "solid")) {
+ jsx_runtime = Api.JsxRuntime.solid;
+ } else if (strings.eqlComptime(value, "react-jsx")) {
+ jsx_runtime = Api.JsxRuntime.automatic;
+ jsx_dev = false;
+ } else if (strings.eqlComptime(value, "react-jsxDEV")) {
+ jsx_runtime = Api.JsxRuntime.automatic;
+ jsx_dev = true;
+ } else {
+ try this.addError(expr.loc, "Invalid jsx runtime, only 'react', 'solid', 'react-jsx', and 'react-jsxDEV' are supported");
+ }
+ }
+ }
+
+ if (json.get("jsxImportSource")) |expr| {
+ if (expr.asString(allocator)) |value| {
+ jsx_import_source = value;
+ }
+ }
+
+ if (json.get("jsxFragment")) |expr| {
+ if (expr.asString(allocator)) |value| {
+ jsx_fragment = value;
+ }
+ }
+
+ if (json.get("jsxFactory")) |expr| {
+ if (expr.asString(allocator)) |value| {
+ jsx_factory = value;
+ }
+ }
+
+ if (this.bunfig.jsx == null) {
+ this.bunfig.jsx = Api.Jsx{
+ .factory = bun.constStrToU8(jsx_factory),
+ .fragment = bun.constStrToU8(jsx_fragment),
+ .import_source = bun.constStrToU8(jsx_import_source),
+ .runtime = jsx_runtime,
+ .development = jsx_dev,
+ .react_fast_refresh = false,
+ };
+ } else {
+ var jsx: *Api.Jsx = &this.bunfig.jsx.?;
+ if (jsx_factory.len > 0) {
+ jsx.factory = bun.constStrToU8(jsx_factory);
+ }
+ if (jsx_fragment.len > 0) {
+ jsx.fragment = bun.constStrToU8(jsx_fragment);
+ }
+ if (jsx_import_source.len > 0) {
+ jsx.import_source = bun.constStrToU8(jsx_import_source);
+ }
+ jsx.runtime = jsx_runtime;
+ jsx.development = jsx_dev;
+ }
+
switch (comptime cmd) {
.AutoCommand, .DevCommand, .BuildCommand, .BunCommand => {
if (json.get("publicDir")) |public_dir| {
diff --git a/src/cli.zig b/src/cli.zig
index 02f2d18f9..e9bac1aac 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -149,8 +149,10 @@ pub const Arguments = struct {
pub fn resolve_jsx_runtime(str: string) !Api.JsxRuntime {
if (strings.eqlComptime(str, "automatic")) {
return Api.JsxRuntime.automatic;
- } else if (strings.eqlComptime(str, "fallback")) {
+ } else if (strings.eqlComptime(str, "fallback") or strings.eqlComptime(str, "classic")) {
return Api.JsxRuntime.classic;
+ } else if (strings.eqlComptime(str, "solid")) {
+ return Api.JsxRuntime.solid;
} else {
return error.InvalidJSXRuntime;
}
@@ -587,14 +589,25 @@ pub const Arguments = struct {
var default_factory = "".*;
var default_fragment = "".*;
var default_import_source = "".*;
- opts.jsx = Api.Jsx{
- .factory = constStrToU8(jsx_factory orelse &default_factory),
- .fragment = constStrToU8(jsx_fragment orelse &default_fragment),
- .import_source = constStrToU8(jsx_import_source orelse &default_import_source),
- .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else Api.JsxRuntime.automatic,
- .development = !jsx_production,
- .react_fast_refresh = react_fast_refresh,
- };
+ if (opts.jsx == null) {
+ opts.jsx = Api.Jsx{
+ .factory = constStrToU8(jsx_factory orelse &default_factory),
+ .fragment = constStrToU8(jsx_fragment orelse &default_fragment),
+ .import_source = constStrToU8(jsx_import_source orelse &default_import_source),
+ .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else Api.JsxRuntime.automatic,
+ .development = !jsx_production,
+ .react_fast_refresh = react_fast_refresh,
+ };
+ } else {
+ opts.jsx = Api.Jsx{
+ .factory = constStrToU8(jsx_factory orelse opts.jsx.?.factory),
+ .fragment = constStrToU8(jsx_fragment orelse opts.jsx.?.fragment),
+ .import_source = constStrToU8(jsx_import_source orelse opts.jsx.?.import_source),
+ .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else opts.jsx.?.runtime,
+ .development = jsx_production,
+ .react_fast_refresh = react_fast_refresh,
+ };
+ }
}
if (args.option("--use")) |entry| {
diff --git a/src/http.zig b/src/http.zig
index 861995d52..f45477d9b 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -3904,6 +3904,9 @@ pub const Server = struct {
}
pub fn detectFastRefresh(this: *Server) void {
+ if (this.bundler.options.jsx.runtime == .solid)
+ return;
+
defer this.bundler.resetStore();
const runtime = this.bundler.options.jsx.refresh_runtime;
diff --git a/src/js_ast.zig b/src/js_ast.zig
index b3cd3a6f6..7c5603f28 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -345,8 +345,10 @@ pub const AssignTarget = enum(u2) {
pub const LocRef = struct { loc: logger.Loc, ref: ?Ref = null };
pub const Flags = struct {
- pub const JSXElement = struct {
- is_key_before_rest: bool = false,
+ pub const JSXElement = enum {
+ is_key_before_rest,
+ has_any_dynamic,
+ pub const Bitset = std.enums.EnumSet(JSXElement);
};
pub const Property = enum {
@@ -1296,7 +1298,7 @@ pub const E = struct {
/// key is the key prop like <ListItem key="foo">
key: ?ExprNodeIndex = null,
- flags: Flags.JSXElement = Flags.JSXElement{},
+ flags: Flags.JSXElement.Bitset = Flags.JSXElement.Bitset{},
close_tag_loc: logger.Loc = logger.Loc.Empty,
@@ -1698,6 +1700,7 @@ pub const E = struct {
rope_len: u32 = 0,
is_utf16: bool = false,
+ pub var class = E.String{ .data = "class" };
pub fn push(this: *String, other: *String) void {
std.debug.assert(this.isUTF8());
std.debug.assert(other.isUTF8());
@@ -1850,6 +1853,16 @@ pub const E = struct {
strings.eqlComptimeUTF16(s.slice16(), value);
}
+ pub fn hasPrefixComptime(s: *const String, comptime value: anytype) bool {
+ if (s.data.len < value.len)
+ return false;
+
+ return if (s.isUTF8())
+ strings.eqlComptime(s.data[0..value.len], value)
+ else
+ strings.eqlComptimeUTF16(s.slice16()[0..value.len], value);
+ }
+
pub fn string(s: *const String, allocator: std.mem.Allocator) !bun.string {
if (s.isUTF8()) {
return s.data;
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index c37a97fc7..2cdabe46f 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -18,7 +18,7 @@ const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
-const MutableString = bun.MutableString;
+const MutableString = @import("../string_mutable.zig").MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
@@ -200,6 +200,13 @@ const TransposeState = struct {
loc: logger.Loc,
};
+var true_args = &[_]Expr{
+ .{
+ .data = .{ .e_boolean = .{ .value = true } },
+ .loc = logger.Loc.Empty,
+ },
+};
+
const JSXTag = struct {
pub const TagType = enum { fragment, tag };
pub const Data = union(TagType) {
@@ -1010,6 +1017,15 @@ const StaticSymbolName = struct {
pub const __exportValue = NewStaticSymbol("__exportValue");
pub const __exportDefault = NewStaticSymbol("__exportDefault");
pub const hmr = NewStaticSymbol("hmr");
+
+ pub const insert = NewStaticSymbol("insert");
+ pub const template = NewStaticSymbol("template");
+ pub const wrap = NewStaticSymbol("wrap");
+ pub const createComponent = NewStaticSymbol("createComponent");
+ pub const setAttribute = NewStaticSymbol("setAttribute");
+ pub const effect = NewStaticSymbol("effect");
+ pub const delegateEvents = NewStaticSymbol("delegateEvents");
+ pub const Solid = NewStaticSymbol("Solid");
};
};
@@ -2297,11 +2313,11 @@ pub const Parser = struct {
if (!self.options.ts and self.options.features.is_macro_runtime) return try self._parse(JSParserMacro);
if (self.options.ts and self.options.jsx.parse) {
- return try self._parse(TSXParser);
+ return if (self.options.jsx.runtime != .solid) try self._parse(TSXParser) else try self._parse(SolidTSXParser);
} else if (self.options.ts) {
return try self._parse(TypeScriptParser);
} else if (self.options.jsx.parse) {
- return try self._parse(JSXParser);
+ return if (self.options.jsx.runtime != .solid) try self._parse(JSXParser) else try self._parse(SolidJSXParser);
} else {
return try self._parse(JavaScriptParser);
}
@@ -2903,6 +2919,106 @@ pub const Parser = struct {
}) catch unreachable;
}
}
+ } else if (comptime ParserType.jsx_transform_type == .solid) {
+ p.resolveGeneratedSymbol(&p.solid.wrap);
+ p.resolveGeneratedSymbol(&p.solid.insert);
+ p.resolveGeneratedSymbol(&p.solid.template);
+ p.resolveGeneratedSymbol(&p.solid.delegateEvents);
+ p.resolveGeneratedSymbol(&p.solid.createComponent);
+ p.resolveGeneratedSymbol(&p.solid.setAttribute);
+ p.resolveGeneratedSymbol(&p.solid.effect);
+ p.resolveGeneratedSymbol(&p.solid.namespace);
+
+ const import_count =
+ @as(usize, @boolToInt(p.symbols.items[p.solid.wrap.ref.innerIndex()].use_count_estimate > 0)) +
+ @as(usize, @boolToInt(p.symbols.items[p.solid.insert.ref.innerIndex()].use_count_estimate > 0)) +
+ @as(usize, @boolToInt(p.symbols.items[p.solid.template.ref.innerIndex()].use_count_estimate > 0)) +
+ @as(usize, @boolToInt(p.symbols.items[p.solid.delegateEvents.ref.innerIndex()].use_count_estimate > 0)) +
+ @as(usize, @boolToInt(p.symbols.items[p.solid.createComponent.ref.innerIndex()].use_count_estimate > 0)) +
+ @as(usize, @boolToInt(p.symbols.items[p.solid.setAttribute.ref.innerIndex()].use_count_estimate > 0)) +
+ @as(usize, @boolToInt(p.symbols.items[p.solid.effect.ref.innerIndex()].use_count_estimate > 0));
+ var import_items = try p.allocator.alloc(js_ast.ClauseItem, import_count);
+
+ // 1. Inject the part containing template declarations and Solid's import statement
+ var stmts_to_inject = p.allocator.alloc(Stmt, @as(usize, @boolToInt(p.solid.template_decls.items.len > 0)) + @as(usize, @boolToInt(import_count > 0))) catch unreachable;
+ var j: usize = 0;
+ const order = .{
+ "createComponent",
+ "delegateEvents",
+ "effect",
+ "insert",
+ "setAttribute",
+ "template",
+ "wrap",
+ };
+
+ try p.named_imports.ensureUnusedCapacity(import_count);
+ try p.is_import_item.ensureUnusedCapacity(p.allocator, @intCast(u32, import_count));
+
+ if (import_count > 0) {
+ const import_record_id = p.addImportRecord(.stmt, logger.Loc.Empty, p.options.jsx.import_source);
+ var declared_symbols = p.allocator.alloc(js_ast.DeclaredSymbol, p.solid.template_decls.items.len) catch unreachable;
+
+ inline for (order) |field_name| {
+ const ref = @field(p.solid, field_name).ref;
+ if (p.symbols.items[ref.innerIndex()].use_count_estimate > 0) {
+ import_items[j] = js_ast.ClauseItem{
+ .alias = field_name,
+ .name = .{ .loc = logger.Loc.Empty, .ref = ref },
+ .alias_loc = logger.Loc.Empty,
+ .original_name = "",
+ };
+
+ p.named_imports.putAssumeCapacity(
+ ref,
+ js_ast.NamedImport{
+ .alias = p.symbols.items[ref.innerIndex()].original_name,
+ .alias_is_star = false,
+ .alias_loc = logger.Loc.Empty,
+ .namespace_ref = p.solid.namespace.ref,
+ .import_record_index = import_record_id,
+ },
+ );
+ p.is_import_item.putAssumeCapacity(ref, .{});
+ j += 1;
+ }
+ }
+
+ p.import_records.items[import_record_id].tag = .jsx_import;
+ stmts_to_inject[0] = p.s(
+ S.Import{
+ .namespace_ref = p.solid.namespace.ref,
+ .star_name_loc = null,
+ .is_single_line = true,
+ .import_record_index = import_record_id,
+ .items = import_items,
+ },
+ logger.Loc.Empty,
+ );
+ if (p.solid.template_decls.items.len > 0) {
+ for (p.solid.template_decls.items) |_, i| {
+ declared_symbols[i] = js_ast.DeclaredSymbol{
+ .ref = p.solid.template_decls.items[i].binding.data.b_identifier.ref,
+ .is_top_level = true,
+ };
+ }
+ stmts_to_inject[1] = p.s(
+ S.Local{
+ .decls = p.solid.template_decls.items,
+ },
+ logger.Loc.Empty,
+ );
+ }
+ var import_record_ids = p.allocator.alloc(u32, 1) catch unreachable;
+ import_record_ids[0] = import_record_id;
+
+ before.append(js_ast.Part{
+ .stmts = stmts_to_inject,
+ .declared_symbols = declared_symbols,
+ .import_record_indices = import_record_ids,
+ .tag = .jsx_import,
+ }) catch unreachable;
+ }
}
if (p.options.enable_bundling) p.resolveBundlingSymbols();
@@ -3192,8 +3308,234 @@ const JSXTransformType = enum {
none,
react,
macro,
+ solid,
};
+const SolidJS = struct {
+ namespace: GeneratedSymbol = undefined,
+ wrap: GeneratedSymbol = undefined,
+ insert: GeneratedSymbol = undefined,
+ template: GeneratedSymbol = undefined,
+ delegateEvents: GeneratedSymbol = undefined,
+ createComponent: GeneratedSymbol = undefined,
+ setAttribute: GeneratedSymbol = undefined,
+ effect: GeneratedSymbol = undefined,
+
+ component_body: std.ArrayListUnmanaged(Stmt) = .{},
+ component_body_decls: std.ArrayListUnmanaged(G.Decl) = .{},
+ last_template_id: E.Identifier = .{},
+ last_element_id: E.Identifier = .{},
+ prev_had_dynamic: bool = false,
+ temporary_scope: Scope = Scope{
+ .kind = .function_body,
+ .parent = null,
+ },
+ prev_scope: ?*Scope = null,
+ node_count: u32 = 0,
+
+ template_decls: std.ArrayListUnmanaged(G.Decl) = .{},
+ current_template_string: MutableString = .{
+ .allocator = undefined,
+ .list = .{},
+ },
+ buffered_writer: MutableString.BufferedWriter = undefined,
+
+ is_in_jsx_component: bool = false,
+
+ events_to_delegate: Events.Bitset = .{},
+ element_counter: u32 = 0,
+
+ const prefilled_element_names = [_]string{
+ "_el",
+ "_el$1",
+ "_el$2",
+ "_el$3",
+ "_el$4",
+ "_el$5",
+ "_el$6",
+ "_el$7",
+ "_el$8",
+ "_el$9",
+ "_el$10",
+ "_el$11",
+ "_el$12",
+ "_el$13",
+ "_el$14",
+ "_el$15",
+ "_el$16",
+ "_el$17",
+ "_el$18",
+ "_el$19",
+ "_el$20",
+ "_el$21",
+ };
+ const prefilled_template_names = [_]string{
+ "_tmpl",
+ "_tmpl$1",
+ "_tmpl$2",
+ "_tmpl$3",
+ "_tmpl$4",
+ "_tmpl$5",
+ "_tmpl$6",
+ "_tmpl$7",
+ "_tmpl$8",
+ "_tmpl$9",
+ "_tmpl$10",
+ "_tmpl$11",
+ "_tmpl$12",
+ "_tmpl$13",
+ "_tmpl$14",
+ "_tmpl$15",
+ "_tmpl$16",
+ "_tmpl$17",
+ "_tmpl$18",
+ "_tmpl$19",
+ "_tmpl$20",
+ "_tmpl$21",
+ };
+
+ pub fn generateElementName(this: *SolidJS, allocator: std.mem.Allocator) string {
+ if (this.component_body_decls.items.len <= prefilled_element_names.len) {
+ return prefilled_element_names[this.component_body_decls.items.len];
+ }
+ return std.fmt.allocPrint(allocator, "_el${d}", .{this.component_body_decls.items.len}) catch unreachable;
+ }
+
+ pub fn generateTemplateName(this: *SolidJS, allocator: std.mem.Allocator) string {
+ if (this.template_decls.items.len <= prefilled_template_names.len) {
+ return prefilled_template_names[this.template_decls.items.len];
+ }
+ return std.fmt.allocPrint(allocator, "_tmpl${d}", .{this.template_decls.items.len}) catch unreachable;
+ }
+
+ pub fn generateElement(solid: *SolidJS, p: anytype, template_expression: Expr, value_loc: logger.Loc) !E.Identifier {
+ var name = solid.generateElementName(p.allocator);
+
+ var prev_scope = p.current_scope;
+ p.current_scope = &solid.temporary_scope;
+ const ref = p.declareSymbolMaybeGenerated(.import, value_loc, name, true) catch unreachable;
+ p.current_scope = prev_scope;
+ const element = .{ .ref = ref };
+ var decl_value: Expr = undefined;
+ switch (solid.component_body_decls.items.len) {
+ 0 => {
+ decl_value = p.e(
+ E.Call{
+ .target = p.e(
+ E.Dot{
+ .name = "cloneNode",
+ .name_loc = value_loc,
+ .target = template_expression,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ },
+ template_expression.loc,
+ ),
+ .args = ExprNodeList.init(true_args),
+ .can_be_unwrapped_if_unused = true,
+ },
+ value_loc,
+ );
+ p.recordUsage(template_expression.data.e_identifier.ref);
+ },
+ 1 => {
+ const ident = E.Identifier{ .ref = solid.component_body_decls.items[solid.component_body_decls.items.len - 1].binding.data.b_identifier.ref };
+ decl_value = p.e(
+ E.Dot{
+ .target = .{
+ .data = .{ .e_identifier = ident },
+ .loc = value_loc,
+ },
+ .name = "firstChild",
+ .name_loc = template_expression.loc,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ },
+ value_loc,
+ );
+ p.recordUsage(ident.ref);
+ },
+ else => {
+ const ident = E.Identifier{ .ref = solid.component_body_decls.items[solid.component_body_decls.items.len - 1].binding.data.b_identifier.ref };
+ decl_value = p.e(E.Dot{
+ .target = .{
+ .data = .{ .e_identifier = ident },
+ .loc = value_loc,
+ },
+ .name_loc = template_expression.loc,
+ .name = "nextSibling",
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ }, value_loc);
+ p.recordUsage(ident.ref);
+ },
+ }
+ try solid.component_body_decls.append(
+ p.allocator,
+ G.Decl{ .binding = p.b(B.Identifier{ .ref = ref }, template_expression.loc), .value = decl_value },
+ );
+ return element;
+ }
+
+ pub const Events = enum {
+ Click,
+ Change,
+ Input,
+ Submit,
+ KeyDown,
+ KeyUp,
+ KeyPress,
+ MouseDown,
+ MouseUp,
+ MouseMove,
+ MouseEnter,
+ MouseLeave,
+ MouseOver,
+ MouseOut,
+ Focus,
+ Blur,
+ Scroll,
+ Wheel,
+ TouchStart,
+ TouchMove,
+ TouchEnd,
+ TouchCancel,
+ PointerDown,
+ PointerUp,
+ PointerMove,
+ PointerCancel,
+ PointerEnter,
+ PointerLeave,
+ PointerOver,
+ PointerOut,
+ GotPointerCapture,
+ LostPointerCapture,
+ Select,
+ ContextMenu,
+ DragStart,
+ Drag,
+ DragEnd,
+ DragEnter,
+ DragLeave,
+ DragOver,
+ Drop,
+ Copy,
+ Cut,
+ Paste,
+ CompositionStart,
+ CompositionUpdate,
+ CompositionEnd,
+
+ pub const Bitset = std.enums.EnumSet(Events);
+ };
+};
+
+fn GetSolidJSSymbols(comptime jsx: JSXTransformType) type {
+ if (jsx != .solid)
+ return void;
+
+ return SolidJS;
+}
const ParserFeatures = struct {
typescript: bool = false,
jsx: JSXTransformType = JSXTransformType.none,
@@ -3244,7 +3586,7 @@ const ParserFeatures = struct {
//
//
//
- react_fast_refresh: bool = false,
+ // react_fast_refresh: bool = false,
};
// Our implementation diverges somewhat from the official implementation
@@ -3276,20 +3618,17 @@ fn NewParser(
parser_features.typescript,
parser_features.jsx,
parser_features.scan_only,
- parser_features.react_fast_refresh,
);
}
fn NewParser_(
comptime parser_feature__typescript: bool,
comptime parser_feature__jsx: JSXTransformType,
comptime parser_feature__scan_only: bool,
- comptime parser_feature__react_fast_refresh: bool,
) type {
const js_parser_features: ParserFeatures = .{
.typescript = parser_feature__typescript,
.jsx = parser_feature__jsx,
.scan_only = parser_feature__scan_only,
- .react_fast_refresh = parser_feature__react_fast_refresh,
};
// P is for Parser!
@@ -3407,6 +3746,8 @@ fn NewParser_(
// only applicable when is_react_fast_refresh_enabled
jsx_refresh_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None },
+ solid: GetSolidJSSymbols(jsx_transform_type) = if (jsx_transform_type == JSXTransformType.solid) SolidJS{} else void{},
+
bun_jsx_ref: Ref = Ref.None,
// Imports (both ES6 and CommonJS) are tracked at the top level
@@ -4288,7 +4629,7 @@ fn NewParser_(
try p.named_imports.put(ref, js_ast.NamedImport{
.alias = alias_name,
.alias_loc = logger.Loc{},
- .namespace_ref = namespace_ref,
+ .namespace_ref = null,
.import_record_index = import_record_i,
});
}
@@ -4431,6 +4772,18 @@ fn NewParser_(
p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable;
}
},
+ .solid => {
+ p.solid.insert = p.declareGeneratedSymbol(.other, "insert") catch unreachable;
+ p.solid.template = p.declareGeneratedSymbol(.other, "template") catch unreachable;
+ p.solid.wrap = p.declareGeneratedSymbol(.other, "wrap") catch unreachable;
+ p.solid.namespace = p.declareGeneratedSymbol(.other, "Solid") catch unreachable;
+ p.solid.delegateEvents = p.declareGeneratedSymbol(.other, "delegateEvents") catch unreachable;
+ p.solid.createComponent = p.declareGeneratedSymbol(.other, "createComponent") catch unreachable;
+ p.solid.setAttribute = p.declareGeneratedSymbol(.other, "setAttribute") catch unreachable;
+ p.solid.effect = p.declareGeneratedSymbol(.other, "effect") catch unreachable;
+ p.solid.current_template_string = MutableString.initEmpty(p.allocator);
+ p.solid.buffered_writer = p.solid.current_template_string.bufferedWriter();
+ },
.macro => {
p.bun_jsx_ref = p.declareSymbol(.other, logger.Loc.Empty, "bunJSX") catch unreachable;
BunJSX.bun_jsx_identifier = E.Identifier{
@@ -11475,6 +11828,7 @@ fn NewParser_(
// Use Expect() not ExpectInsideJSXElement() so we can parse expression tokens
try p.lexer.expect(.t_open_brace);
const value = try p.parseExpr(.lowest);
+
try p.lexer.expectInsideJSXElement(.t_close_brace);
return value;
}
@@ -11496,7 +11850,7 @@ fn NewParser_(
var previous_string_with_backslash_loc = logger.Loc{};
var properties = G.Property.List{};
var key_prop: ?ExprNodeIndex = null;
- var flags = Flags.JSXElement{};
+ var flags = Flags.JSXElement.Bitset{};
var start_tag: ?ExprNodeIndex = null;
// Fragments don't have props
@@ -11543,6 +11897,14 @@ fn NewParser_(
value = p.e(E.Boolean{ .value = true }, logger.Loc{ .start = key_range.loc.start + key_range.len });
} else {
value = try p.parseJSXPropValueIdentifier(&previous_string_with_backslash_loc);
+ if (comptime jsx_transform_type == .solid) {
+ switch (value.knownPrimitive()) {
+ .unknown => {
+ flags.insert(.has_any_dynamic);
+ },
+ else => {},
+ }
+ }
}
try props.append(G.Property{ .key = prop_name, .value = value });
@@ -11593,6 +11955,15 @@ fn NewParser_(
return error.SyntaxError;
};
+ if (comptime jsx_transform_type == .solid) {
+ switch (expr.knownPrimitive()) {
+ .unknown => {
+ flags.insert(.has_any_dynamic);
+ },
+ else => {},
+ }
+ }
+
try props.append(G.Property{ .value = expr, .key = key, .kind = .normal });
},
// This implements
@@ -11618,8 +11989,9 @@ fn NewParser_(
}
}
- flags.is_key_before_rest = key_prop_i > -1 and spread_prop_i > key_prop_i;
- if (flags.is_key_before_rest and p.options.jsx.runtime == .automatic and !p.has_classic_runtime_warned) {
+ const is_key_before_rest = key_prop_i > -1 and spread_prop_i > key_prop_i;
+ flags.setPresent(.is_key_before_rest, is_key_before_rest);
+ if (is_key_before_rest and p.options.jsx.runtime == .automatic and !p.has_classic_runtime_warned) {
try p.log.addWarning(p.source, spread_loc, "\"key\" prop before a {...spread} is deprecated in JSX. Falling back to classic runtime.");
p.has_classic_runtime_warned = true;
}
@@ -11669,6 +12041,7 @@ fn NewParser_(
// Use ExpectJSXElementChild() so we parse child strings
try p.lexer.expectJSXElementChild(.t_greater_than);
var children = ListManaged(Expr).init(p.allocator);
+ // var last_element_i: usize = 0;
while (true) {
switch (p.lexer.token) {
@@ -11687,7 +12060,18 @@ fn NewParser_(
// The expression is optional, and may be absent
if (p.lexer.token != .t_close_brace) {
- try children.append(try p.parseExpr(.lowest));
+ if (comptime jsx_transform_type == .solid) {
+ const child = try p.parseExpr(.lowest);
+ switch (child.knownPrimitive()) {
+ .unknown => {
+ flags.insert(.has_any_dynamic);
+ },
+ else => {},
+ }
+ try children.append(child);
+ } else {
+ try children.append(try p.parseExpr(.lowest));
+ }
}
// Use ExpectJSXElementChild() so we parse child strings
@@ -11699,7 +12083,43 @@ fn NewParser_(
if (p.lexer.token != .t_slash) {
// This is a child element
- children.append(try p.parseJSXElement(less_than_loc)) catch unreachable;
+ const child = try p.parseJSXElement(less_than_loc);
+ if (comptime jsx_transform_type == .solid) {
+ // if (!flags.contains(.has_dynamic_children)) {
+ // if (@as(Expr.Tag, child.data) == .e_jsx_element) {
+ // if (child.data.e_jsx_element.flags.contains(.has_dynamic_children) or child.data.e_jsx_element.flags.contains(.has_dynamic_prop)) {
+ // flags.insert(.has_dynamic_children);
+
+ // }
+ // } else {
+ // switch (child.knownPrimitive()) {
+ // .unknown => {
+ // flags.insert(.has_dynamic_children);
+ // },
+ // else => {},
+ // }
+ // }
+ // }
+
+ if (!flags.contains(.has_any_dynamic)) {
+ if (@as(Expr.Tag, child.data) == .e_jsx_element) {
+ if (child.data.e_jsx_element.flags.contains(.has_any_dynamic)) {
+ flags.insert(.has_any_dynamic);
+ }
+ } else {
+ switch (child.knownPrimitive()) {
+ .unknown => {
+ flags.insert(.has_any_dynamic);
+ },
+ else => {},
+ }
+ }
+ }
+
+ children.append(child) catch unreachable;
+ } else {
+ children.append(p.parseJSXElement(less_than_loc) catch unreachable) catch unreachable;
+ }
// The call to parseJSXElement() above doesn't consume the last
// TGreaterThan because the caller knows what Next() function to call.
@@ -12200,6 +12620,524 @@ fn NewParser_(
var writer = WriterType.initWriter(p, &BunJSX.bun_jsx_identifier);
return writer.writeFunctionCall(e_.*);
},
+ .solid => {
+ // The rules:
+ // 1. Every JSX element with an identifier gets wrapped in a createComponent() call
+ // 2. HTML string literals of static elements are generated & escaped, injected at the top of the file
+ // 2a. Static elements are contiguous in the HTML, but dynamic elements get a marker string during if client-side hydration
+ // Each element becomes a declaration in the top-level scope of the JSX expression (i.e. either the anonymous IIFE or an array)
+ // Those elements may be markers
+ // The final count of the markers is passed to the template function
+ // 3. The first element in a a group of elements becomes .cloneNode(true)
+ // Subsequent elements call .nextSibling on the previous element.
+ // The specific function differs if SSR is enabled and if client-side hydration is enabled.
+ // 4. Non-static JSX children are added like this:
+ // insert(topElement, createComponent(MyComponent, props), markerElement)
+ //
+ var solid = &p.solid;
+ const old_is_in_jsx_component = solid.is_in_jsx_component;
+ solid.is_in_jsx_component = true;
+ defer solid.is_in_jsx_component = old_is_in_jsx_component;
+
+ if (!old_is_in_jsx_component) {
+ solid.current_template_string.reset();
+ solid.buffered_writer.pos = 0;
+ solid.component_body.clearRetainingCapacity();
+ solid.component_body_decls.clearRetainingCapacity();
+
+ // prepend an empty statement
+ // this will later become an S.Local for the decls
+ solid.component_body.append(p.allocator, p.s(S.Empty{}, expr.loc)) catch unreachable;
+
+ solid.last_element_id = E.Identifier{};
+ solid.prev_had_dynamic = false;
+ solid.prev_scope = p.current_scope;
+ solid.temporary_scope.reset();
+ solid.node_count = 0;
+ solid.temporary_scope.kind = .function_body;
+ solid.temporary_scope.parent = p.current_scope;
+
+ solid.last_template_id.ref = Ref.None;
+ }
+
+ var writer = &solid.buffered_writer;
+
+ const tag: Expr = tagger: {
+ if (e_.tag) |_tag| {
+ break :tagger p.visitExpr(_tag);
+ } else {
+ break :tagger p.e(E.Array{}, expr.loc);
+ }
+ };
+
+ const jsx_props = e_.properties.slice();
+
+ var template_expression = Expr{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } };
+ var element: ?E.Identifier = null;
+ var needs_end_bracket = false;
+ var children = e_.children.slice();
+ defer {
+ if (old_is_in_jsx_component) {
+ if (element) |el| {
+ solid.last_element_id = el;
+ }
+ }
+ }
+ switch (tag.data) {
+ .e_string => {
+ // write the template
+ _ = writer.writeAll("<") catch unreachable;
+ _ = writer.writeString(tag.data.e_string) catch unreachable;
+ needs_end_bracket = true;
+
+ var wrote_anything = false;
+ var had_any_dynamic_content = false;
+ for (jsx_props) |*property, i| {
+ if (property.kind != .spread) {
+ property.key = p.visitExpr(e_.properties.ptr[i].key.?);
+ }
+
+ if (property.value != null) {
+ property.value = p.visitExpr(e_.properties.ptr[i].value.?);
+
+ if (property.kind != .spread) {
+ var key = property.key.?.data.e_string;
+
+ const is_event_listener = key.hasPrefixComptime("on:");
+ const is_class = !is_event_listener and
+ // TODO: should this be case-insensitive?
+ (key.eqlComptime("class") or key.eqlComptime("className"));
+
+ const primitive = property.value.?.knownPrimitive();
+ const is_dynamic = !(primitive == .string or primitive == .number or primitive == .boolean or primitive == .@"null" or primitive == .@"undefined");
+ const appears_in_template = !is_event_listener and !is_dynamic;
+ if (appears_in_template) {
+ _ = writer.writeAll(" ") catch unreachable;
+ wrote_anything = true;
+ }
+
+ if (is_class and !is_dynamic) {
+ _ = writer.writeAll("class") catch unreachable;
+ } else if (!is_dynamic) {
+ _ = writer.writeString(key) catch unreachable;
+ }
+ if (appears_in_template) {
+ switch (primitive) {
+ .number, .string => {
+ if (property.value.?.data == .e_string) {
+ const str = property.value.?.data.e_string;
+ if (str.len() > 0) {
+ _ = writer.writeAll("=") catch unreachable;
+ writer.writeHTMLAttributeValueString(str) catch unreachable;
+ }
+ } else {
+ writer.writer().print("={d}", .{property.value.?.data.e_number.value}) catch unreachable;
+ }
+ },
+ // TODO: should "null" be written?
+ // TODO: should "undefined" be written?
+ .@"null", .@"undefined" => {},
+ .boolean => {
+ if (!property.value.?.data.e_boolean.value) {
+ _ = writer.writeAll("=false") catch unreachable;
+ }
+ },
+
+ else => unreachable,
+ }
+ } else {
+ if (template_expression.data.e_identifier.ref.isNull()) {
+ var new_template_name = solid.generateTemplateName(p.allocator);
+ // declare the template in the module scope
+ p.current_scope = p.module_scope;
+ solid.last_template_id = .{
+ .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ };
+ p.current_scope = solid.prev_scope.?;
+ template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } };
+ }
+ had_any_dynamic_content = true;
+ if (element == null) {
+ element = solid.generateElement(
+ p,
+ template_expression,
+ property.value.?.loc,
+ ) catch unreachable;
+ }
+
+ var stmt: Stmt = undefined;
+ if (!is_event_listener) {
+ var args = p.allocator.alloc(Expr, 3) catch unreachable;
+ args[0] = template_expression;
+ if (is_class) {
+ args[1] = p.e(E.String.init("className"), property.key.?.loc);
+ } else {
+ args[1] = property.key.?;
+ }
+
+ args[2] = property.value.?;
+
+ // setAttribute(template_expression, key, value);
+ const setAttr = p.e(
+ E.Call{
+ .target = p.e(
+ E.Identifier{
+ .ref = solid.setAttribute.ref,
+ .can_be_removed_if_unused = false,
+ .call_can_be_unwrapped_if_unused = false,
+ },
+ property.value.?.loc,
+ ),
+ .args = ExprNodeList.init(args),
+ },
+ property.key.?.loc,
+ );
+
+ p.recordUsage(solid.setAttribute.ref);
+ if (args[2].data == .e_identifier or args[2].data == .e_import_identifier) {
+ if (args[2].data == .e_identifier) p.recordUsage(args[2].data.e_identifier.ref);
+ if (args[2].data == .e_import_identifier) p.recordUsage(args[2].data.e_import_identifier.ref);
+ stmt = p.s(S.SExpr{ .value = setAttr }, property.value.?.loc);
+ } else {
+ var stmts = p.allocator.alloc(Stmt, 1) catch unreachable;
+ stmts[0] = p.s(S.Return{ .value = setAttr }, property.value.?.loc);
+ var arrow = p.e(
+ E.Arrow{
+ .args = &[_]G.Arg{},
+ .body = G.FnBody{
+ .stmts = stmts,
+ .loc = args[2].loc,
+ },
+ },
+ property.value.?.loc,
+ );
+ stmt = p.s(S.SExpr{ .value = arrow }, property.value.?.loc);
+ }
+ } else {
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+
+ // on:MyEvent => MyEvent
+ property.key.?.data.e_string.data = property.key.?.data.e_string.data[3..];
+ args[0] = property.key.?;
+ args[1] = property.value.?;
+ // $element.addEventListener("MyEvent", (e) => { ... });
+ const addEventListener = p.e(
+ E.Call{
+ .target = p.e(
+ E.Dot{
+ .target = p.e(
+ element.?,
+ expr.loc,
+ ),
+ .name = "addEventListener",
+ .name_loc = property.key.?.loc,
+ },
+ property.key.?.loc,
+ ),
+ .args = ExprNodeList.init(args),
+ },
+ property.key.?.loc,
+ );
+
+ p.recordUsage(element.?.ref);
+ stmt = p.s(S.SExpr{ .value = addEventListener }, property.value.?.loc);
+ }
+
+ solid.component_body.append(p.allocator, stmt) catch unreachable;
+ }
+ } else {}
+ }
+
+ if (property.initializer != null) {
+ property.initializer = p.visitExpr(e_.properties.ptr[i].initializer.?);
+ }
+ }
+
+ var wrote_any_children = false;
+ for (children) |*el, k| {
+ if (needs_end_bracket and el.data == .e_jsx_element) {
+ _ = writer.writeAll(">") catch unreachable;
+ solid.node_count += 1;
+
+ needs_end_bracket = false;
+ }
+
+ const child = p.visitExpr(el.*);
+ switch (child.data) {
+ // skip it
+ .e_missing => {},
+
+ // we need to serialize it to HTML
+ // it's probably a text node
+ .e_string => |str| {
+ if (str.len() > 0) {
+ if (needs_end_bracket) {
+ _ = writer.writeAll(">") catch unreachable;
+ solid.node_count += 1;
+ needs_end_bracket = false;
+ }
+ writer.writeHTMLAttributeValueString(str) catch unreachable;
+ wrote_any_children = true;
+ }
+ },
+ .e_number => |str| {
+ if (needs_end_bracket) {
+ _ = writer.writeAll(">") catch unreachable;
+ needs_end_bracket = false;
+ }
+ writer.writer().print("{d}", .{str.value}) catch unreachable;
+ wrote_any_children = true;
+ },
+
+ // debug assertion that we don't get here
+ .e_jsx_element => unreachable,
+
+ else => {
+ if (template_expression.data.e_identifier.ref.isNull()) {
+ var new_template_name = solid.generateTemplateName(p.allocator);
+ // declare the template in the module scope
+ p.current_scope = p.module_scope;
+ solid.last_template_id = .{
+ .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ };
+ p.current_scope = solid.prev_scope.?;
+ template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } };
+ }
+ p.recordUsage(solid.insert.ref);
+ p.recordUsage(template_expression.data.e_identifier.ref);
+ var args = p.allocator.alloc(Expr, 3) catch unreachable;
+ args[0] = template_expression;
+ args[1] = child;
+ args[2] = if (k != children.len - 1 and !solid.last_element_id.ref.eql(Ref.None))
+ p.e(solid.last_element_id, expr.loc)
+ else
+ p.e(E.Null{}, expr.loc);
+ solid.node_count += 1;
+ solid.component_body.append(
+ p.allocator,
+ p.s(
+ S.SExpr{
+ .value = p.e(
+ E.Call{
+ .target = p.e(E.ImportIdentifier{ .ref = solid.insert.ref }, child.loc),
+ .args = ExprNodeList.init(args),
+ },
+ child.loc,
+ ),
+ },
+ child.loc,
+ ),
+ ) catch unreachable;
+ },
+ }
+ }
+
+ if (wrote_any_children) {
+ solid.node_count += 1;
+ _ = writer.writeAll("</") catch unreachable;
+ _ = writer.writeString(tag.data.e_string) catch unreachable;
+ _ = writer.writeAll(">") catch unreachable;
+ } else if (needs_end_bracket) {
+ _ = writer.writeAll("/>") catch unreachable;
+ }
+
+ // this is the root of a template tag, we just finished
+ // <div>
+ // /* some stuff in here */
+ // </div>
+ // ^
+ // we are here!
+ if (!old_is_in_jsx_component) {
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+
+ if (writer.pos < writer.buffer.len and writer.context.list.items.len == 0) {
+ args[0] = p.e(E.String.init(p.allocator.dupe(u8, writer.buffer[0..writer.pos]) catch unreachable), expr.loc);
+ } else if (writer.pos == 0 and writer.context.list.items.len == 0) {
+ args[0] = p.e(E.String.init(""), expr.loc);
+ } else {
+ const total = writer.context.list.items.len + writer.pos;
+ var buffer = p.allocator.alloc(u8, total) catch unreachable;
+ @memcpy(buffer.ptr, writer.context.list.items.ptr, writer.context.list.items.len);
+ @memcpy(buffer.ptr + writer.context.list.items.len, &writer.buffer, writer.buffer.len);
+ args[0] = p.e(E.String.init(buffer), expr.loc);
+ }
+
+ args[1] = p.e(E.Number{ .value = @intToFloat(f64, solid.node_count) }, expr.loc);
+ solid.node_count = 0;
+
+ if (template_expression.data.e_identifier.ref.isNull()) {
+ var new_template_name = solid.generateTemplateName(p.allocator);
+ // declare the template in the module scope
+ p.current_scope = p.module_scope;
+ solid.last_template_id = .{
+ .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ };
+ p.current_scope = solid.prev_scope.?;
+ template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } };
+ }
+
+ solid.template_decls.append(
+ p.allocator,
+ G.Decl{
+ .binding = p.b(B.Identifier{ .ref = template_expression.data.e_identifier.ref }, template_expression.loc),
+ .value = p.e(
+ E.Call{
+ .args = ExprNodeList.init(args),
+ .target = p.e(
+ E.ImportIdentifier{
+ .ref = solid.template.ref,
+ },
+ expr.loc,
+ ),
+ .can_be_unwrapped_if_unused = true,
+ },
+ template_expression.loc,
+ ),
+ },
+ ) catch unreachable;
+ p.recordUsage(solid.template.ref);
+
+ if (p.is_control_flow_dead) {
+ return p.e(E.Missing{}, expr.loc);
+ }
+
+ // 1 means it was actually static
+ // that means we can just turn it into a single $template.cloneNode(true)
+ if (solid.component_body.items.len == 1) {
+ return p.e(E.Call{
+ .target = p.e(
+ E.Dot{
+ .name = "cloneNode",
+ .name_loc = expr.loc,
+ .target = template_expression,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ },
+ template_expression.loc,
+ ),
+ .args = ExprNodeList.init(true_args),
+ .can_be_unwrapped_if_unused = true,
+ }, expr.loc);
+ }
+ if (solid.component_body_decls.items.len == 0) {
+ solid.component_body_decls.ensureTotalCapacityPrecise(p.allocator, 1) catch unreachable;
+ solid.component_body_decls.appendAssumeCapacity(G.Decl{
+ .binding = p.b(B.Identifier{ .ref = solid.last_template_id.ref }, expr.loc),
+ .value = p.e(E.Call{
+ .target = p.e(
+ E.Dot{
+ .name = "cloneNode",
+ .name_loc = expr.loc,
+ .target = template_expression,
+ .can_be_removed_if_unused = true,
+ .call_can_be_unwrapped_if_unused = true,
+ },
+ template_expression.loc,
+ ),
+ .args = ExprNodeList.init(true_args),
+ .can_be_unwrapped_if_unused = true,
+ }, expr.loc),
+ });
+ }
+
+ // we need to wrap the template in a function
+ const ret = p.e(E.Identifier{ .ref = solid.component_body_decls.items[0].binding.data.b_identifier.ref }, expr.loc);
+ solid.component_body.items[0] = p.s(S.Local{ .decls = solid.component_body_decls.toOwnedSlice(p.allocator) }, expr.loc);
+ solid.component_body.append(p.allocator, p.s(S.Return{ .value = ret }, expr.loc)) catch unreachable;
+ return p.e(
+ E.Arrow{ .args = &[_]G.Arg{}, .body = G.FnBody{ .stmts = solid.component_body.toOwnedSlice(p.allocator), .loc = expr.loc } },
+ expr.loc,
+ );
+ // we don't need to return anything because it's a static element that will live in the template
+ } else {
+ return p.e(E.Missing{}, expr.loc);
+ }
+ },
+ .e_import_identifier, .e_identifier => {
+ var out_props = p.allocator.alloc(G.Property, jsx_props.len + @as(usize, @boolToInt(e_.key != null)) + @as(usize, @boolToInt(e_.children.len > 0))) catch unreachable;
+ var out_props_i: usize = 0;
+ for (jsx_props) |property, i| {
+ if (property.kind != .spread) {
+ e_.properties.ptr[i].key = p.visitExpr(e_.properties.ptr[i].key.?);
+ }
+
+ if (property.value != null) {
+ e_.properties.ptr[i].value = p.visitExpr(e_.properties.ptr[i].value.?);
+ }
+
+ if (property.initializer != null) {
+ e_.properties.ptr[i].initializer = p.visitExpr(e_.properties.ptr[i].initializer.?);
+ }
+
+ if (property.kind != .spread) {
+ const kind = if (property.value.?.data == .e_arrow or property.value.?.data == .e_function) G.Property.Kind.get else G.Property.Kind.normal;
+ out_props[out_props_i] = G.Property{
+ .key = property.key,
+ .value = property.value,
+ .kind = kind,
+ };
+ out_props_i += 1;
+ }
+ }
+
+ if (e_.key) |k| {
+ const key = p.visitExpr(k);
+ if (key.data != .e_missing) {
+ const kind = if (key.data == .e_arrow or key.data == .e_function) Property.Kind.get else Property.Kind.normal;
+ out_props[out_props_i] = G.Property{
+ .key = p.e(Prefill.String.Key, k.loc),
+ .value = key,
+ .kind = kind,
+ };
+ out_props_i += 1;
+ }
+ }
+
+ var out_child_i: usize = 0;
+ for (children) |child, j| {
+ children[j] = p.visitExpr(child);
+ if (children[j].data != .e_missing) {
+ children[out_child_i] = children[j];
+ out_child_i += 1;
+ }
+ }
+
+ if (out_child_i > 0) {
+ const kind = Property.Kind.get;
+
+ out_props[out_props_i] = G.Property{
+ .key = p.e(Prefill.String.Children, expr.loc),
+ .value = p.e(E.Array{ .items = ExprNodeList.init(children[0..out_child_i]) }, expr.loc),
+ .kind = kind,
+ };
+ out_props_i += 1;
+ }
+
+ var args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = tag;
+ args[1] = p.e(E.Object{
+ .properties = G.Property.List.init(out_props[0..out_props_i]),
+ }, expr.loc);
+ p.recordUsage(solid.createComponent.ref);
+ return p.e(
+ E.Call{
+ .target = p.e(E.ImportIdentifier{ .ref = solid.createComponent.ref }, expr.loc),
+ .args = ExprNodeList.init(args),
+ .close_paren_loc = e_.close_tag_loc,
+ },
+ expr.loc,
+ );
+ },
+ .e_array => {},
+ else => unreachable,
+ }
+ },
.react => {
const tag: Expr = tagger: {
if (e_.tag) |_tag| {
@@ -12228,7 +13166,7 @@ fn NewParser_(
e_.key = p.visitExpr(key);
}
- const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.is_key_before_rest) options.JSX.Runtime.automatic else options.JSX.Runtime.classic;
+ const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.contains(.is_key_before_rest)) options.JSX.Runtime.automatic else options.JSX.Runtime.classic;
var children_count = e_.children.len;
const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.slice(p.allocator));
@@ -16199,7 +17137,6 @@ fn NewParser_(
}
break :list_getter &visited;
};
-
try p.visitAndAppendStmt(list, stmt);
}
@@ -17213,6 +18150,8 @@ const JavaScriptParser = NewParser(.{});
const JSXParser = NewParser(.{ .jsx = .react });
const TSXParser = NewParser(.{ .jsx = .react, .typescript = true });
const TypeScriptParser = NewParser(.{ .typescript = true });
+const SolidJSXParser = NewParser(.{ .jsx = .solid });
+const SolidTSXParser = NewParser(.{ .jsx = .solid, .typescript = true });
const JSParserMacro = NewParser(.{
.jsx = .macro,
@@ -17222,11 +18161,6 @@ const TSParserMacro = NewParser(.{
.typescript = true,
});
-const JavaScriptParserFastRefresh = NewParser(.{ .react_fast_refresh = true });
-const JSXParserFastRefresh = NewParser(.{ .jsx = .react, .react_fast_refresh = true });
-const TSXParserFastRefresh = NewParser(.{ .jsx = .react, .typescript = true, .react_fast_refresh = true });
-const TypeScriptParserFastRefresh = NewParser(.{ .typescript = true, .react_fast_refresh = true });
-
const JavaScriptImportScanner = NewParser(.{ .scan_only = true });
const JSXImportScanner = NewParser(.{ .jsx = .react, .scan_only = true });
const TSXImportScanner = NewParser(.{ .jsx = .react, .typescript = true, .scan_only = true });
diff --git a/src/options.zig b/src/options.zig
index bb5af5a27..52352e323 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -903,8 +903,14 @@ pub const JSX = struct {
pragma.factory = try memberListToComponentsIfDifferent(allocator, pragma.factory, jsx.factory);
}
+ pragma.runtime = jsx.runtime;
+
if (jsx.import_source.len > 0) {
pragma.import_source = jsx.import_source;
+ if (jsx.import_source.len > "solid-js".len and strings.eqlComptime(jsx.import_source[0.."solid-js".len], "solid-js")) {
+ pragma.runtime = .solid;
+ pragma.supports_fast_refresh = false;
+ }
pragma.package_name = parsePackageName(pragma.import_source);
} else if (jsx.development) {
pragma.import_source = Defaults.ImportSourceDev;
@@ -915,8 +921,8 @@ pub const JSX = struct {
pragma.jsx = Defaults.JSXFunction;
}
+ pragma.supports_fast_refresh = if (pragma.runtime == .solid) false else pragma.supports_fast_refresh;
pragma.development = jsx.development;
- pragma.runtime = jsx.runtime;
pragma.parse = true;
return pragma;
}
diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig
index 280b27258..69dc2e8dc 100644
--- a/src/resolver/package_json.zig
+++ b/src/resolver/package_json.zig
@@ -1366,7 +1366,7 @@ pub const ESModule = struct {
.key = keys[last_map_entry_i],
.value = slice.items(.value)[last_map_entry_i],
// key_range is unused, so we don't need to pull up the array for it.
- .key_range = undefined,
+ .key_range = logger.Range.None,
};
if (did_find_map_entry and
last_map_entry.value.data == .map and
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 39235a746..62ea74a15 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -1232,9 +1232,8 @@ pub const Resolver = struct {
const esmodule = ESModule{
.conditions = switch (kind) {
- ast.ImportKind.stmt, ast.ImportKind.dynamic => r.opts.conditions.import,
ast.ImportKind.require, ast.ImportKind.require_resolve => r.opts.conditions.require,
- else => r.opts.conditions.default,
+ else => r.opts.conditions.import,
},
.allocator = r.allocator,
.debug_logs = if (r.debug_logs) |*debug| debug else null,
diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig
index cf9b366e2..bcfa5b176 100644
--- a/src/resolver/tsconfig_json.zig
+++ b/src/resolver/tsconfig_json.zig
@@ -152,10 +152,15 @@ pub const TSConfigJSON = struct {
// Parse "jsxImportSource"
if (compiler_opts.expr.asProperty("jsxImportSource")) |jsx_prop| {
if (jsx_prop.expr.asString(allocator)) |str| {
- if (is_jsx_development) {
- result.jsx.import_source = std.fmt.allocPrint(allocator, "{s}/jsx-dev-runtime", .{str}) catch unreachable;
+ if (str.len >= "solid-js".len and strings.eqlComptime(str[0.."solid-js".len], "solid-js")) {
+ result.jsx.import_source = str;
+ result.jsx.runtime = .solid;
} else {
- result.jsx.import_source = std.fmt.allocPrint(allocator, "{s}/jsx-runtime", .{str}) catch unreachable;
+ if (is_jsx_development) {
+ result.jsx.import_source = std.fmt.allocPrint(allocator, "{s}/jsx-dev-runtime", .{str}) catch unreachable;
+ } else {
+ result.jsx.import_source = std.fmt.allocPrint(allocator, "{s}/jsx-runtime", .{str}) catch unreachable;
+ }
}
result.jsx.package_name = options.JSX.Pragma.parsePackageName(str);
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index ac41dcc42..c6bfdbc65 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -14,6 +14,30 @@ pub inline fn containsChar(self: string, char: u8) bool {
pub inline fn contains(self: string, str: string) bool {
return std.mem.indexOf(u8, self, str) != null;
}
+
+const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1);
+pub fn indexOfAny(self: string, comptime str: anytype) ?OptionalUsize {
+ for (self) |c, i| {
+ inline for (str) |a| {
+ if (c == a) {
+ return @intCast(OptionalUsize, i);
+ }
+ }
+ }
+
+ return null;
+}
+pub fn indexOfAny16(self: []const u16, comptime str: anytype) ?OptionalUsize {
+ for (self) |c, i| {
+ inline for (str) |a| {
+ if (c == a) {
+ return @intCast(OptionalUsize, i);
+ }
+ }
+ }
+
+ return null;
+}
pub inline fn containsComptime(self: string, comptime str: string) bool {
var remain = self;
const Int = std.meta.Int(.unsigned, str.len * 8);
diff --git a/src/string_mutable.zig b/src/string_mutable.zig
index 7ef05fbe7..7be903d93 100644
--- a/src/string_mutable.zig
+++ b/src/string_mutable.zig
@@ -283,6 +283,96 @@ pub const MutableString = struct {
return pending.len;
}
+ const E = @import("./js_ast.zig").E;
+ pub fn writeString(this: *BufferedWriter, bytes: *E.String) anyerror!usize {
+ if (bytes.isUTF8()) {
+ return try this.writeAll(bytes.slice(this.context.allocator));
+ }
+
+ return try this.writeAll16(bytes.slice16());
+ }
+
+ pub fn writeAll16(this: *BufferedWriter, bytes: []const u16) anyerror!usize {
+ var pending = bytes;
+
+ if (pending.len >= max) {
+ try this.flush();
+ try this.context.list.ensureUnusedCapacity(this.context.allocator, bytes.len * 2);
+ const decoded = strings.copyUTF16IntoUTF8(
+ this.remain()[0 .. bytes.len * 2],
+ []const u16,
+ bytes,
+ );
+ this.context.list.items.len += @as(usize, decoded.written);
+ return pending.len;
+ }
+
+ if (pending.len > 0) {
+ if ((pending.len * 2) + this.pos > max) {
+ try this.flush();
+ }
+ const decoded = strings.copyUTF16IntoUTF8(
+ this.remain()[0 .. bytes.len * 2],
+ []const u16,
+ bytes,
+ );
+ this.pos += @as(usize, decoded.written);
+ }
+
+ return pending.len;
+ }
+
+ pub fn writeHTMLAttributeValueString(this: *BufferedWriter, str: *E.String) anyerror!void {
+ if (str.isUTF8()) {
+ try this.writeHTMLAttributeValue(str.slice(this.context.allocator));
+ return;
+ }
+
+ try this.writeHTMLAttributeValue16(str.slice16());
+ }
+
+ pub fn writeHTMLAttributeValue(this: *BufferedWriter, bytes: []const u8) anyerror!void {
+ var slice = bytes;
+ while (slice.len > 0) {
+ if (strings.indexOfAny(slice, "\"<>")) |j| {
+ _ = try this.writeAll(slice[0..j]);
+ _ = switch (slice[j]) {
+ '"' => try this.writeAll("&quot;"),
+ '<' => try this.writeAll("&lt;"),
+ '>' => try this.writeAll("&gt;"),
+ else => unreachable,
+ };
+
+ slice = slice[j + 1 ..];
+ continue;
+ }
+
+ _ = try this.writeAll(slice);
+ break;
+ }
+ }
+
+ pub fn writeHTMLAttributeValue16(this: *BufferedWriter, bytes: []const u16) anyerror!void {
+ var slice = bytes;
+ while (slice.len > 0) {
+ if (strings.indexOfAny16(slice, "\"<>")) |j| {
+ _ = try this.writeAll16(slice[0..j]);
+ _ = switch (slice[j]) {
+ '"' => try this.writeAll("&quot;"),
+ '<' => try this.writeAll("&lt;"),
+ '>' => try this.writeAll("&gt;"),
+ else => unreachable,
+ };
+
+ slice = slice[j + 1 ..];
+ continue;
+ }
+
+ _ = try this.writeAll16(slice);
+ break;
+ }
+ }
+
pub fn writer(this: *BufferedWriter) BufferedWriter.Writer {
return BufferedWriter.Writer{ .context = this };
}