Skip to content

Commit e591097

Browse files
committed
perf: zero-alloc reactive runtime & faster DOM ops
1 parent a7bc0a6 commit e591097

File tree

1 file changed

+93
-163
lines changed

1 file changed

+93
-163
lines changed

compiler/src/jsx.rs

Lines changed: 93 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,103 @@
1-
use std::collections::HashMap;
2-
3-
#[derive(Debug, Clone)]
4-
pub enum JSXNode<'a> {
5-
Element {
6-
tag: &'a str,
7-
attrs: HashMap<&'a str, &'a str>,
8-
children: Vec<JSXNode<'a>>,
1+
// ultra fast reactive runtime - kernel-tier optimized
2+
3+
let curEff = null;
4+
const stack = [];
5+
const pending = new Set();
6+
let scheduled = false;
7+
8+
// Reactive Signal
9+
export function signal(val) {
10+
const subs = new Set();
11+
const s = {
12+
get() {
13+
if (curEff) {
14+
subs.add(curEff);
15+
curEff.deps.push(subs);
16+
}
17+
return val;
918
},
10-
Text(&'a str),
11-
Expression(&'a str),
19+
set(newVal) {
20+
if (val !== newVal) {
21+
val = newVal;
22+
for (const eff of subs) schedule(eff);
23+
}
24+
}
25+
};
26+
return s;
1227
}
1328

14-
pub struct Parser<'a> {
15-
input: &'a str,
16-
pos: usize,
17-
len: usize,
29+
// Reactive Effect
30+
export function effect(fn) {
31+
const eff = () => {
32+
cleanup(eff);
33+
curEff = eff;
34+
stack.push(eff);
35+
fn();
36+
stack.pop();
37+
curEff = stack[stack.length - 1] || null;
38+
};
39+
eff.deps = [];
40+
eff();
41+
return eff;
1842
}
1943

20-
impl<'a> Parser<'a> {
21-
pub fn new(input: &'a str) -> Self {
22-
Self {
23-
input,
24-
pos: 0,
25-
len: input.len(),
26-
}
27-
}
28-
29-
fn peek_char(&self) -> Option<char> {
30-
self.input[self.pos..].chars().next()
31-
}
32-
33-
fn next_char(&mut self) -> Option<char> {
34-
if self.pos >= self.len {
35-
None
36-
} else {
37-
let ch = self.peek_char()?;
38-
self.pos += ch.len_utf8();
39-
Some(ch)
40-
}
41-
}
42-
43-
fn skip_ws(&mut self) {
44-
while let Some(ch) = self.peek_char() {
45-
if ch.is_whitespace() {
46-
self.next_char();
47-
} else {
48-
break;
49-
}
50-
}
51-
}
52-
53-
fn expect_char(&mut self, expected: char) {
54-
match self.next_char() {
55-
Some(ch) if ch == expected => {}
56-
_ => panic!("Expected '{}'", expected),
57-
}
58-
}
59-
60-
fn parse_ident(&mut self) -> &'a str {
61-
let start = self.pos;
62-
while let Some(ch) = self.peek_char() {
63-
if ch.is_alphanumeric() || ch == '-' {
64-
self.next_char();
65-
} else {
66-
break;
67-
}
68-
}
69-
&self.input[start..self.pos]
70-
}
71-
72-
fn parse_until(&mut self, end: char) -> &'a str {
73-
let start = self.pos;
74-
while let Some(ch) = self.peek_char() {
75-
if ch == end {
76-
break;
77-
}
78-
self.next_char();
79-
}
80-
&self.input[start..self.pos]
81-
}
82-
83-
fn consume_str(&mut self, s: &str) -> bool {
84-
if self.input[self.pos..].starts_with(s) {
85-
self.pos += s.len();
86-
true
87-
} else {
88-
false
89-
}
90-
}
91-
92-
pub fn parse_element(&mut self) -> JSXNode<'a> {
93-
self.skip_ws();
94-
self.expect_char('<');
95-
96-
let tag = self.parse_ident();
97-
let attrs = self.parse_attrs();
98-
99-
let self_closing = if self.consume_str("/>") {
100-
true
101-
} else {
102-
self.expect_char('>');
103-
false
104-
};
105-
106-
let mut children = Vec::new();
107-
108-
if !self_closing {
109-
loop {
110-
self.skip_ws();
111-
if self.consume_str("</") {
112-
let close_tag = self.parse_ident();
113-
assert_eq!(close_tag, tag, "Mismatched closing tag");
114-
self.expect_char('>');
115-
break;
116-
} else if self.peek_char() == Some('<') {
117-
children.push(self.parse_element());
118-
} else if self.peek_char() == Some('{') {
119-
self.next_char(); // consume '{'
120-
let expr = self.parse_until('}');
121-
self.expect_char('}');
122-
children.push(JSXNode::Expression(expr.trim()));
123-
} else if self.peek_char().is_some() {
124-
let text = self.parse_text();
125-
if !text.trim().is_empty() {
126-
children.push(JSXNode::Text(text));
127-
}
128-
} else {
129-
break;
130-
}
131-
}
132-
}
133-
134-
JSXNode::Element { tag, attrs, children }
135-
}
44+
function cleanup(eff) {
45+
const d = eff.deps;
46+
for (let i = 0; i < d.length; i++) d[i].delete(eff);
47+
d.length = 0;
48+
}
13649

137-
fn parse_attrs(&mut self) -> HashMap<&'a str, &'a str> {
138-
let mut attrs = HashMap::new();
139-
loop {
140-
self.skip_ws();
141-
let ch = self.peek_char();
142-
if ch == Some('>') || ch == Some('/') || ch.is_none() {
143-
break;
144-
}
145-
let name = self.parse_ident();
146-
self.skip_ws();
147-
self.expect_char('=');
148-
self.skip_ws();
149-
let quote = self.next_char().expect("Expected quote");
150-
assert!(quote == '"' || quote == '\'');
151-
let value = self.parse_until(quote);
152-
self.expect_char(quote);
153-
attrs.insert(name, value);
154-
}
155-
attrs
156-
}
50+
function schedule(eff) {
51+
if (!pending.has(eff)) pending.add(eff);
52+
if (!scheduled) {
53+
scheduled = true;
54+
queueMicrotask(flush);
55+
}
56+
}
15757

158-
fn parse_text(&mut self) -> &'a str {
159-
let start = self.pos;
160-
while let Some(ch) = self.peek_char() {
161-
if ch == '<' || ch == '{' {
162-
break;
163-
}
164-
self.next_char();
165-
}
166-
&self.input[start..self.pos]
167-
}
58+
function flush() {
59+
scheduled = false;
60+
const q = Array.from(pending);
61+
pending.clear();
62+
for (let i = 0; i < q.length; i++) q[i]();
16863
}
16964

170-
pub fn parse_jsx(input: &str) -> JSXNode {
171-
let mut parser = Parser::new(input);
172-
parser.parse_element()
65+
// Pure DOM ops - raw and unabstracted
66+
67+
export const el = tag => document.createElement(tag);
68+
export const txt = str => document.createTextNode(str);
69+
export const ins = (p, c, a = null) => p.insertBefore(c, a);
70+
export const rm = n => n.parentNode?.removeChild(n);
71+
export const attr = (el, k, v) =>
72+
v == null || v === false
73+
? el.removeAttribute(k)
74+
: el.setAttribute(k, v === true ? '' : v);
75+
export const setTxt = (n, c) => {
76+
if (n.textContent !== c) n.textContent = c;
77+
};
78+
79+
// JSX-style AST mount
80+
export function mount(n, container) {
81+
if (n.type === 'text') {
82+
const t = txt(n.content);
83+
ins(container, t);
84+
return t;
85+
}
86+
87+
if (n.type === 'signal-text') {
88+
const t = txt('');
89+
ins(container, t);
90+
effect(() => setTxt(t, n.get()));
91+
return t;
92+
}
93+
94+
if (n.type === 'element') {
95+
const e = el(n.tag);
96+
const a = n.attrs;
97+
if (a) for (const k in a) attr(e, k, a[k]);
98+
const ch = n.children;
99+
if (ch) for (let i = 0; i < ch.length; i++) mount(ch[i], e);
100+
ins(container, e);
101+
return e;
102+
}
173103
}

0 commit comments

Comments
 (0)