rip-lang 3.8.10 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +14 -14
- package/docs/RIP-LANG.md +10 -1
- package/docs/dist/rip-ui.min.js +169 -169
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.min.js +163 -163
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/example/index.html +34 -0
- package/docs/example/index.json +14 -0
- package/docs/index.html +1475 -3
- package/docs/sierpinski.html +114 -0
- package/package.json +3 -3
- package/src/browser.js +18 -9
- package/src/compiler.js +4 -1
- package/src/components.js +415 -20
- package/src/grammar/grammar.rip +2 -2
- package/src/grammar/parser.js +360 -0
- package/src/lexer.js +12 -275
- package/src/parser.js +5 -6
- package/docs/demo.html +0 -342
- package/docs/dist/rip.browser.js +0 -8023
- package/docs/dist/ui.js +0 -962
- package/docs/dist/ui.min.js +0 -2
- package/docs/dist/ui.min.js.br +0 -0
- package/docs/dist/ui.rip +0 -957
- package/docs/dist/ui.rip.br +0 -0
- package/docs/playground-app.html +0 -1022
- package/docs/playground-js.html +0 -1645
- package/docs/playground-rip-ui.html +0 -1419
- package/docs/playground-rip.html +0 -1450
- package/src/parser-rd.js +0 -3242
package/src/components.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Component System — Fine-grained reactive components for Rip
|
|
2
2
|
//
|
|
3
|
-
// Architecture: installComponentSupport(CodeGenerator) adds methods to
|
|
4
|
-
//
|
|
5
|
-
// getComponentRuntime() emits runtime helpers
|
|
3
|
+
// Architecture: installComponentSupport(CodeGenerator, Lexer) adds methods to
|
|
4
|
+
// both prototypes — render rewriting on the Lexer, component code generation
|
|
5
|
+
// on the CodeGenerator. A separate getComponentRuntime() emits runtime helpers
|
|
6
|
+
// only when components are used.
|
|
6
7
|
//
|
|
7
8
|
// Naming: All render-tree generators use generate* (consistent with compiler).
|
|
8
9
|
|
|
@@ -50,11 +51,381 @@ function getMemberName(target) {
|
|
|
50
51
|
return null;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Detect fragment root and collect direct child variables for proper removal.
|
|
56
|
+
* After insertBefore, a DocumentFragment is empty — .remove() is a no-op.
|
|
57
|
+
* Callers must remove each child element individually.
|
|
58
|
+
*/
|
|
59
|
+
function getFragChildren(rootVar, createLines, localizeVar) {
|
|
60
|
+
const root = localizeVar(rootVar);
|
|
61
|
+
if (!/_frag\d+$/.test(root)) return null;
|
|
62
|
+
const children = [];
|
|
63
|
+
const re = new RegExp(`^${root.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.appendChild\\(([^)]+)\\);`);
|
|
64
|
+
for (const line of createLines) {
|
|
65
|
+
const m = localizeVar(line).match(re);
|
|
66
|
+
if (m) children.push(m[1]);
|
|
67
|
+
}
|
|
68
|
+
return children.length > 0 ? children : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
// ============================================================================
|
|
54
72
|
// Prototype Installation
|
|
55
73
|
// ============================================================================
|
|
56
74
|
|
|
57
|
-
export function installComponentSupport(CodeGenerator) {
|
|
75
|
+
export function installComponentSupport(CodeGenerator, Lexer) {
|
|
76
|
+
|
|
77
|
+
// ==========================================================================
|
|
78
|
+
// Lexer: Render block rewriter
|
|
79
|
+
// ==========================================================================
|
|
80
|
+
// Transforms template syntax inside render blocks:
|
|
81
|
+
// - Implicit div for class-only selectors: .card → div.card
|
|
82
|
+
// - Combine #id selectors: div # main → div#main
|
|
83
|
+
// - Two-way binding: value <=> username → __bind_value__: username
|
|
84
|
+
// - Event modifiers: @click.prevent: → [@click.prevent]:
|
|
85
|
+
// - Dynamic classes: div.('card', x && 'active') → div.__clsx(...)
|
|
86
|
+
// - Implicit nesting: inject -> before INDENT for template elements
|
|
87
|
+
// - Hyphenated attributes: data-foo: "x" → "data-foo": "x"
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
|
|
90
|
+
Lexer.prototype.rewriteRender = function() {
|
|
91
|
+
let gen = (tag, val, origin) => {
|
|
92
|
+
let t = [tag, val];
|
|
93
|
+
t.pre = 0;
|
|
94
|
+
t.data = null;
|
|
95
|
+
t.loc = origin?.loc ?? {r: 0, c: 0, n: 0};
|
|
96
|
+
t.spaced = false;
|
|
97
|
+
t.newLine = false;
|
|
98
|
+
t.generated = true;
|
|
99
|
+
if (origin) t.origin = origin;
|
|
100
|
+
return t;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
let inRender = false;
|
|
104
|
+
let renderIndentLevel = 0;
|
|
105
|
+
let currentIndent = 0;
|
|
106
|
+
let pendingCallEnds = [];
|
|
107
|
+
|
|
108
|
+
let isHtmlTag = (name) => {
|
|
109
|
+
let tagPart = name.split('#')[0];
|
|
110
|
+
return TEMPLATE_TAGS.has(tagPart);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let isComponent = (name) => {
|
|
114
|
+
if (!name || typeof name !== 'string') return false;
|
|
115
|
+
return /^[A-Z]/.test(name);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let isTemplateTag = (name) => {
|
|
119
|
+
return isHtmlTag(name) || isComponent(name);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let startsWithTag = (tokens, i) => {
|
|
123
|
+
let j = i;
|
|
124
|
+
while (j > 0) {
|
|
125
|
+
let pt = tokens[j - 1][0];
|
|
126
|
+
if (pt === 'TERMINATOR' || pt === 'RENDER') {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (pt === 'INDENT' || pt === 'OUTDENT') {
|
|
130
|
+
let jt = tokens[j][0];
|
|
131
|
+
if (jt === 'CALL_END' || jt === ')') {
|
|
132
|
+
let open = jt === 'CALL_END' ? 'CALL_START' : '(';
|
|
133
|
+
let depth = 1;
|
|
134
|
+
let k = j - 1;
|
|
135
|
+
while (k >= 0 && depth > 0) {
|
|
136
|
+
let kt = tokens[k][0];
|
|
137
|
+
if (kt === jt) depth++;
|
|
138
|
+
else if (kt === open) depth--;
|
|
139
|
+
if (depth > 0) k--;
|
|
140
|
+
}
|
|
141
|
+
j = k;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
if (pt === 'CALL_END' || pt === ')') {
|
|
147
|
+
let open = pt === 'CALL_END' ? 'CALL_START' : '(';
|
|
148
|
+
let depth = 1;
|
|
149
|
+
let k = j - 2;
|
|
150
|
+
while (k >= 0 && depth > 0) {
|
|
151
|
+
let kt = tokens[k][0];
|
|
152
|
+
if (kt === 'CALL_END' || kt === ')') depth++;
|
|
153
|
+
else if (kt === 'CALL_START' || kt === '(') depth--;
|
|
154
|
+
if (depth > 0) k--;
|
|
155
|
+
}
|
|
156
|
+
j = k;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
j--;
|
|
160
|
+
}
|
|
161
|
+
return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isTemplateTag(tokens[j][1]);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
this.scanTokens(function(token, i, tokens) {
|
|
165
|
+
let tag = token[0];
|
|
166
|
+
let nextToken = i < tokens.length - 1 ? tokens[i + 1] : null;
|
|
167
|
+
|
|
168
|
+
// Track entering render blocks
|
|
169
|
+
if (tag === 'RENDER') {
|
|
170
|
+
inRender = true;
|
|
171
|
+
renderIndentLevel = currentIndent + 1;
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Track indentation
|
|
176
|
+
if (tag === 'INDENT') {
|
|
177
|
+
currentIndent++;
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (tag === 'OUTDENT') {
|
|
182
|
+
currentIndent--;
|
|
183
|
+
|
|
184
|
+
// Insert pending CALL_END(s) after this OUTDENT
|
|
185
|
+
let inserted = 0;
|
|
186
|
+
while (pendingCallEnds.length > 0 && pendingCallEnds[pendingCallEnds.length - 1] > currentIndent) {
|
|
187
|
+
let callEndToken = gen('CALL_END', ')', token);
|
|
188
|
+
tokens.splice(i + 1 + inserted, 0, callEndToken);
|
|
189
|
+
pendingCallEnds.pop();
|
|
190
|
+
inserted++;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Exit render block when we outdent past where it started
|
|
194
|
+
if (inRender && currentIndent < renderIndentLevel) {
|
|
195
|
+
inRender = false;
|
|
196
|
+
}
|
|
197
|
+
return 1 + inserted;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Only process if we're inside a render block
|
|
201
|
+
if (!inRender) return 1;
|
|
202
|
+
|
|
203
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
204
|
+
// Hyphenated attributes
|
|
205
|
+
// data-lucide: "search" → "data-lucide": "search"
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
207
|
+
if (tag === 'IDENTIFIER' && !token.spaced) {
|
|
208
|
+
let parts = [token[1]];
|
|
209
|
+
let j = i + 1;
|
|
210
|
+
while (j + 1 < tokens.length) {
|
|
211
|
+
let hyphen = tokens[j];
|
|
212
|
+
let nextPart = tokens[j + 1];
|
|
213
|
+
if (hyphen[0] === '-' && !hyphen.spaced &&
|
|
214
|
+
(nextPart[0] === 'IDENTIFIER' || nextPart[0] === 'PROPERTY')) {
|
|
215
|
+
parts.push(nextPart[1]);
|
|
216
|
+
j += 2;
|
|
217
|
+
if (nextPart[0] === 'PROPERTY') break;
|
|
218
|
+
} else {
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (parts.length > 1 && j > i + 1 && tokens[j - 1][0] === 'PROPERTY') {
|
|
223
|
+
token[0] = 'STRING';
|
|
224
|
+
token[1] = `"${parts.join('-')}"`;
|
|
225
|
+
tokens.splice(i + 1, j - i - 1);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
231
|
+
// Implicit div for class-only or bare dot selectors
|
|
232
|
+
// .card → div.card | . (with children) → div
|
|
233
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
234
|
+
if (tag === '.') {
|
|
235
|
+
let prevToken = i > 0 ? tokens[i - 1] : null;
|
|
236
|
+
let prevTag = prevToken ? prevToken[0] : null;
|
|
237
|
+
if (prevTag === 'INDENT' || prevTag === 'TERMINATOR') {
|
|
238
|
+
if (nextToken && nextToken[0] === 'PROPERTY') {
|
|
239
|
+
let divToken = gen('IDENTIFIER', 'div', token);
|
|
240
|
+
tokens.splice(i, 0, divToken);
|
|
241
|
+
return 2;
|
|
242
|
+
}
|
|
243
|
+
// Skip .('classes') — handled by dynamic classes handler below
|
|
244
|
+
if (!nextToken || nextToken[0] !== '(') {
|
|
245
|
+
token[0] = 'IDENTIFIER';
|
|
246
|
+
token[1] = 'div';
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
253
|
+
// Combine #id selectors
|
|
254
|
+
// div # main → div#main
|
|
255
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
256
|
+
if (tag === 'IDENTIFIER' || tag === 'PROPERTY') {
|
|
257
|
+
let next = tokens[i + 1];
|
|
258
|
+
let nextNext = tokens[i + 2];
|
|
259
|
+
if (next && next[0] === '#' && nextNext && (nextNext[0] === 'PROPERTY' || nextNext[0] === 'IDENTIFIER')) {
|
|
260
|
+
token[1] = token[1] + '#' + nextNext[1];
|
|
261
|
+
if (nextNext.spaced) token.spaced = true;
|
|
262
|
+
tokens.splice(i + 1, 2);
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
268
|
+
// Two-way binding
|
|
269
|
+
// value <=> username → __bind_value__: username
|
|
270
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
271
|
+
if (tag === 'BIND') {
|
|
272
|
+
let prevToken = i > 0 ? tokens[i - 1] : null;
|
|
273
|
+
let nextBindToken = tokens[i + 1];
|
|
274
|
+
if (prevToken && (prevToken[0] === 'IDENTIFIER' || prevToken[0] === 'PROPERTY') &&
|
|
275
|
+
nextBindToken && nextBindToken[0] === 'IDENTIFIER') {
|
|
276
|
+
prevToken[1] = `__bind_${prevToken[1]}__`;
|
|
277
|
+
token[0] = ':';
|
|
278
|
+
token[1] = ':';
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
284
|
+
// Event modifiers
|
|
285
|
+
// @click.prevent: handler → [@click.prevent]: handler
|
|
286
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
287
|
+
if (tag === '@') {
|
|
288
|
+
let j = i + 1;
|
|
289
|
+
if (j < tokens.length && tokens[j][0] === 'PROPERTY') {
|
|
290
|
+
j++;
|
|
291
|
+
while (j + 1 < tokens.length && tokens[j][0] === '.' && tokens[j + 1][0] === 'PROPERTY') {
|
|
292
|
+
j += 2;
|
|
293
|
+
}
|
|
294
|
+
if (j > i + 2 && j < tokens.length && tokens[j][0] === ':') {
|
|
295
|
+
let openBracket = gen('[', '[', token);
|
|
296
|
+
tokens.splice(i, 0, openBracket);
|
|
297
|
+
let closeBracket = gen(']', ']', tokens[j + 1]);
|
|
298
|
+
tokens.splice(j + 1, 0, closeBracket);
|
|
299
|
+
return 2;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
305
|
+
// Dynamic classes
|
|
306
|
+
// div.('card', x && 'active') → div.__clsx('card', x && 'active')
|
|
307
|
+
// .('card') → div.__clsx('card')
|
|
308
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
309
|
+
if (tag === '.' && nextToken && nextToken[0] === '(') {
|
|
310
|
+
let prevToken = i > 0 ? tokens[i - 1] : null;
|
|
311
|
+
let prevTag = prevToken ? prevToken[0] : null;
|
|
312
|
+
let atLineStart = prevTag === 'INDENT' || prevTag === 'TERMINATOR';
|
|
313
|
+
|
|
314
|
+
let cxToken = gen('PROPERTY', '__clsx', token);
|
|
315
|
+
nextToken[0] = 'CALL_START';
|
|
316
|
+
let depth = 1;
|
|
317
|
+
for (let j = i + 2; j < tokens.length && depth > 0; j++) {
|
|
318
|
+
if (tokens[j][0] === '(' || tokens[j][0] === 'CALL_START') depth++;
|
|
319
|
+
else if (tokens[j][0] === ')') {
|
|
320
|
+
depth--;
|
|
321
|
+
if (depth === 0) tokens[j][0] = 'CALL_END';
|
|
322
|
+
} else if (tokens[j][0] === 'CALL_END') depth--;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (atLineStart) {
|
|
326
|
+
let divToken = gen('IDENTIFIER', 'div', token);
|
|
327
|
+
tokens.splice(i, 0, divToken);
|
|
328
|
+
tokens.splice(i + 2, 0, cxToken);
|
|
329
|
+
return 3;
|
|
330
|
+
} else {
|
|
331
|
+
tokens.splice(i + 1, 0, cxToken);
|
|
332
|
+
return 2;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
337
|
+
// Implicit nesting (inject -> before INDENT)
|
|
338
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
339
|
+
if (nextToken && nextToken[0] === 'INDENT') {
|
|
340
|
+
if (tag === '->' || tag === '=>' || tag === 'CALL_START' || tag === '(') {
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let isTemplateElement = false;
|
|
345
|
+
let prevTag = i > 0 ? tokens[i - 1][0] : null;
|
|
346
|
+
let isAfterControlFlow = prevTag === 'IF' || prevTag === 'UNLESS' || prevTag === 'WHILE' || prevTag === 'UNTIL' || prevTag === 'WHEN';
|
|
347
|
+
|
|
348
|
+
// Detect __clsx CALL_END early — OUTDENT tokens inside multi-line .()
|
|
349
|
+
// args prevent startsWithTag from seeing the template tag, so we check
|
|
350
|
+
// for __clsx ownership first by counting balanced CALL_START/CALL_END.
|
|
351
|
+
let isClsxCallEnd = false;
|
|
352
|
+
if (tag === 'CALL_END') {
|
|
353
|
+
let depth = 1;
|
|
354
|
+
for (let j = i - 1; j >= 0 && depth > 0; j--) {
|
|
355
|
+
if (tokens[j][0] === 'CALL_END') depth++;
|
|
356
|
+
else if (tokens[j][0] === 'CALL_START') {
|
|
357
|
+
depth--;
|
|
358
|
+
if (depth === 0 && j > 0 && tokens[j - 1][0] === 'PROPERTY' && tokens[j - 1][1] === '__clsx') {
|
|
359
|
+
isClsxCallEnd = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (isClsxCallEnd) {
|
|
366
|
+
isTemplateElement = true;
|
|
367
|
+
} else if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
|
|
368
|
+
isTemplateElement = true;
|
|
369
|
+
} else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'STRING_END' || tag === 'CALL_END' || tag === ')') {
|
|
370
|
+
isTemplateElement = startsWithTag(tokens, i);
|
|
371
|
+
}
|
|
372
|
+
else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
|
|
373
|
+
if (startsWithTag(tokens, i)) {
|
|
374
|
+
let commaToken = gen(',', ',', token);
|
|
375
|
+
let arrowToken = gen('->', '->', token);
|
|
376
|
+
arrowToken.newLine = true;
|
|
377
|
+
tokens.splice(i + 1, 0, commaToken, arrowToken);
|
|
378
|
+
return 3;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (isTemplateElement) {
|
|
383
|
+
let isClassOrIdTail = tag === 'PROPERTY' && i > 0 && (tokens[i - 1][0] === '.' || tokens[i - 1][0] === '#');
|
|
384
|
+
|
|
385
|
+
if (isClsxCallEnd) {
|
|
386
|
+
let callStartToken = gen('CALL_START', '(', token);
|
|
387
|
+
let arrowToken = gen('->', '->', token);
|
|
388
|
+
arrowToken.newLine = true;
|
|
389
|
+
tokens.splice(i + 1, 0, callStartToken, arrowToken);
|
|
390
|
+
pendingCallEnds.push(currentIndent + 1);
|
|
391
|
+
return 3;
|
|
392
|
+
} else if ((tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail) {
|
|
393
|
+
// Bare tag or tag.class/tag#id (no other args): inject CALL_START -> and manage CALL_END
|
|
394
|
+
let callStartToken = gen('CALL_START', '(', token);
|
|
395
|
+
let arrowToken = gen('->', '->', token);
|
|
396
|
+
arrowToken.newLine = true;
|
|
397
|
+
tokens.splice(i + 1, 0, callStartToken, arrowToken);
|
|
398
|
+
pendingCallEnds.push(currentIndent + 1);
|
|
399
|
+
return 3;
|
|
400
|
+
} else {
|
|
401
|
+
// Tag with args: inject , -> (call wrapping handled by addImplicitBracesAndParens)
|
|
402
|
+
let commaToken = gen(',', ',', token);
|
|
403
|
+
let arrowToken = gen('->', '->', token);
|
|
404
|
+
arrowToken.newLine = true;
|
|
405
|
+
tokens.splice(i + 1, 0, commaToken, arrowToken);
|
|
406
|
+
return 3;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
412
|
+
// Bare component reference (PascalCase, no children, no args)
|
|
413
|
+
// Counter → Counter() so it gets treated as a component instantiation
|
|
414
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
415
|
+
if (tag === 'IDENTIFIER' && isComponent(token[1]) &&
|
|
416
|
+
nextToken && (nextToken[0] === 'OUTDENT' || nextToken[0] === 'TERMINATOR')) {
|
|
417
|
+
tokens.splice(i + 1, 0, gen('CALL_START', '(', token), gen('CALL_END', ')', token));
|
|
418
|
+
return 3;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return 1;
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// ==========================================================================
|
|
426
|
+
// CodeGenerator: Component compilation
|
|
427
|
+
// ==========================================================================
|
|
428
|
+
|
|
58
429
|
const proto = CodeGenerator.prototype;
|
|
59
430
|
|
|
60
431
|
// ==========================================================================
|
|
@@ -67,7 +438,7 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
67
438
|
*/
|
|
68
439
|
proto.localizeVar = function(line) {
|
|
69
440
|
let result = line.replace(/this\.(_el\d+|_t\d+|_anchor\d+|_frag\d+|_slot\d+|_c\d+|_inst\d+|_empty\d+)/g, '$1');
|
|
70
|
-
result = result.replace(/\bthis
|
|
441
|
+
result = result.replace(/\bthis\b/g, 'ctx');
|
|
71
442
|
return result;
|
|
72
443
|
};
|
|
73
444
|
|
|
@@ -266,8 +637,14 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
266
637
|
|
|
267
638
|
// Computed (derived)
|
|
268
639
|
for (const { name, expr } of derivedVars) {
|
|
269
|
-
|
|
270
|
-
|
|
640
|
+
if (this.is(expr, 'block') && expr.length > 2) {
|
|
641
|
+
const transformed = this.transformComponentMembers(expr);
|
|
642
|
+
const body = this.generateFunctionBody(transformed);
|
|
643
|
+
lines.push(` this.${name} = __computed(() => ${body});`);
|
|
644
|
+
} else {
|
|
645
|
+
const val = this.generateInComponent(expr, 'value');
|
|
646
|
+
lines.push(` this.${name} = __computed(() => ${val});`);
|
|
647
|
+
}
|
|
271
648
|
}
|
|
272
649
|
|
|
273
650
|
// Effects
|
|
@@ -643,10 +1020,10 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
643
1020
|
const eventName = key[2];
|
|
644
1021
|
// Bind method references to this
|
|
645
1022
|
if (typeof value === 'string' && this.componentMembers?.has(value)) {
|
|
646
|
-
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => this.${value}(e));`);
|
|
1023
|
+
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => this.${value}(e)));`);
|
|
647
1024
|
} else {
|
|
648
1025
|
const handlerCode = this.generateInComponent(value, 'value');
|
|
649
|
-
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => (${handlerCode})(e));`);
|
|
1026
|
+
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => (${handlerCode})(e)));`);
|
|
650
1027
|
}
|
|
651
1028
|
continue;
|
|
652
1029
|
}
|
|
@@ -860,7 +1237,7 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
860
1237
|
/__effect\(\(\) => \{/g,
|
|
861
1238
|
'disposers.push(__effect(() => {'
|
|
862
1239
|
).replace(
|
|
863
|
-
/\}\);$/
|
|
1240
|
+
/\}\);$/gm,
|
|
864
1241
|
'}));'
|
|
865
1242
|
);
|
|
866
1243
|
factoryLines.push(` ${wrappedLine}`);
|
|
@@ -873,7 +1250,14 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
873
1250
|
if (hasEffects) {
|
|
874
1251
|
factoryLines.push(` disposers.forEach(d => d());`);
|
|
875
1252
|
}
|
|
876
|
-
|
|
1253
|
+
const condFragChildren = getFragChildren(rootVar, createLines, localizeVar);
|
|
1254
|
+
if (condFragChildren) {
|
|
1255
|
+
for (const child of condFragChildren) {
|
|
1256
|
+
factoryLines.push(` if (detaching) ${child}.remove();`);
|
|
1257
|
+
}
|
|
1258
|
+
} else {
|
|
1259
|
+
factoryLines.push(` if (detaching) ${localizeVar(rootVar)}.remove();`);
|
|
1260
|
+
}
|
|
877
1261
|
factoryLines.push(` }`);
|
|
878
1262
|
|
|
879
1263
|
factoryLines.push(` };`);
|
|
@@ -963,9 +1347,16 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
963
1347
|
}
|
|
964
1348
|
factoryLines.push(` },`);
|
|
965
1349
|
|
|
966
|
-
// m() - mount
|
|
1350
|
+
// m() - mount (also repositions already-mounted blocks)
|
|
1351
|
+
const loopFragChildren = getFragChildren(itemNode, itemCreateLines, localizeVar);
|
|
967
1352
|
factoryLines.push(` m(target, anchor) {`);
|
|
968
|
-
|
|
1353
|
+
if (loopFragChildren) {
|
|
1354
|
+
for (const child of loopFragChildren) {
|
|
1355
|
+
factoryLines.push(` target.insertBefore(${child}, anchor);`);
|
|
1356
|
+
}
|
|
1357
|
+
} else {
|
|
1358
|
+
factoryLines.push(` target.insertBefore(${localizeVar(itemNode)}, anchor);`);
|
|
1359
|
+
}
|
|
969
1360
|
factoryLines.push(` },`);
|
|
970
1361
|
|
|
971
1362
|
// p() - update
|
|
@@ -979,7 +1370,7 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
979
1370
|
/__effect\(\(\) => \{/g,
|
|
980
1371
|
'disposers.push(__effect(() => {'
|
|
981
1372
|
).replace(
|
|
982
|
-
/\}\);$/
|
|
1373
|
+
/\}\);$/gm,
|
|
983
1374
|
'}));'
|
|
984
1375
|
);
|
|
985
1376
|
factoryLines.push(` ${wrappedLine}`);
|
|
@@ -992,7 +1383,13 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
992
1383
|
if (hasEffects) {
|
|
993
1384
|
factoryLines.push(` disposers.forEach(d => d());`);
|
|
994
1385
|
}
|
|
995
|
-
|
|
1386
|
+
if (loopFragChildren) {
|
|
1387
|
+
for (const child of loopFragChildren) {
|
|
1388
|
+
factoryLines.push(` if (detaching) ${child}.remove();`);
|
|
1389
|
+
}
|
|
1390
|
+
} else {
|
|
1391
|
+
factoryLines.push(` if (detaching) ${localizeVar(itemNode)}.remove();`);
|
|
1392
|
+
}
|
|
996
1393
|
factoryLines.push(` }`);
|
|
997
1394
|
|
|
998
1395
|
factoryLines.push(` };`);
|
|
@@ -1015,14 +1412,12 @@ export function installComponentSupport(CodeGenerator) {
|
|
|
1015
1412
|
setupLines.push(` const ${itemVar} = items[${indexVar}];`);
|
|
1016
1413
|
setupLines.push(` const key = ${keyExpr};`);
|
|
1017
1414
|
setupLines.push(` let block = map.get(key);`);
|
|
1018
|
-
setupLines.push(` if (block) {`);
|
|
1019
|
-
setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
|
|
1020
|
-
setupLines.push(` } else {`);
|
|
1415
|
+
setupLines.push(` if (!block) {`);
|
|
1021
1416
|
setupLines.push(` block = ${blockName}(this, ${itemVar}, ${indexVar});`);
|
|
1022
1417
|
setupLines.push(` block.c();`);
|
|
1023
|
-
setupLines.push(` block.m(parent, anchor);`);
|
|
1024
|
-
setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
|
|
1025
1418
|
setupLines.push(` }`);
|
|
1419
|
+
setupLines.push(` block.m(parent, anchor);`);
|
|
1420
|
+
setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
|
|
1026
1421
|
setupLines.push(` newMap.set(key, block);`);
|
|
1027
1422
|
setupLines.push(` }`);
|
|
1028
1423
|
setupLines.push(``);
|
package/src/grammar/grammar.rip
CHANGED
|
@@ -197,7 +197,7 @@ grammar =
|
|
|
197
197
|
ComputedAssign: [
|
|
198
198
|
o 'Assignable COMPUTED_ASSIGN Expression' , '["computed", 1, 3]'
|
|
199
199
|
o 'Assignable COMPUTED_ASSIGN TERMINATOR Expression' , '["computed", 1, 4]'
|
|
200
|
-
o 'Assignable COMPUTED_ASSIGN
|
|
200
|
+
o 'Assignable COMPUTED_ASSIGN Block' , '["computed", 1, 3]'
|
|
201
201
|
]
|
|
202
202
|
|
|
203
203
|
# Reactive readonly (=!) — constants that cannot be reassigned
|
|
@@ -443,7 +443,7 @@ grammar =
|
|
|
443
443
|
Invocation: [
|
|
444
444
|
o 'Value String' , '["tagged-template", 1, 2]' # Tagged template
|
|
445
445
|
o 'Value Arguments' , '[1, ...2]' # Regular call
|
|
446
|
-
o 'Value ES6_OPTIONAL_CALL Arguments' , '["optcall", 1, ...3]'
|
|
446
|
+
o 'Value ES6_OPTIONAL_CALL Arguments' , '["optcall", 1, ...3]' # Optional call: x?.(args)
|
|
447
447
|
o 'SUPER Arguments' , '["super", ...2]' # Super call
|
|
448
448
|
o 'DYNAMIC_IMPORT Arguments' , '[1, ...2]' # Dynamic import()
|
|
449
449
|
]
|