@dinoreic/fez 0.4.0 → 0.5.2
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/README.md +723 -198
- package/bin/fez +16 -6
- package/bin/fez-compile +347 -0
- package/bin/fez-debug +25 -0
- package/bin/fez-index +16 -4
- package/bin/refactor +699 -0
- package/dist/fez.js +142 -33
- package/dist/fez.js.map +4 -4
- package/fez.d.ts +533 -0
- package/package.json +25 -15
- package/src/fez/compile.js +396 -164
- package/src/fez/connect.js +250 -143
- package/src/fez/defaults.js +275 -84
- package/src/fez/instance.js +673 -514
- package/src/fez/lib/await-helper.js +64 -0
- package/src/fez/lib/global-state.js +22 -4
- package/src/fez/lib/index.js +140 -0
- package/src/fez/lib/localstorage.js +44 -0
- package/src/fez/lib/n.js +38 -23
- package/src/fez/lib/pubsub.js +208 -0
- package/src/fez/lib/svelte-template-lib.js +339 -0
- package/src/fez/lib/svelte-template.js +472 -0
- package/src/fez/lib/template.js +114 -119
- package/src/fez/morph.js +384 -0
- package/src/fez/root.js +284 -164
- package/src/fez/utility.js +319 -149
- package/src/fez/utils/dump.js +114 -84
- package/src/fez/utils/highlight_all.js +1 -1
- package/src/fez.js +65 -43
- package/src/rollup.js +1 -1
- package/src/svelte-cde-adapter.coffee +21 -12
- package/src/fez/vendor/idiomorph.js +0 -860
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// Template utility functions for svelte-template parser
|
|
2
|
+
// Extracted to keep main parser file smaller
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse loop binding to get params and detect object iteration
|
|
6
|
+
*/
|
|
7
|
+
export function parseLoopBinding(binding) {
|
|
8
|
+
const isDestructured = binding.startsWith("[");
|
|
9
|
+
|
|
10
|
+
if (isDestructured) {
|
|
11
|
+
const match = binding.match(/^\[([^\]]+)\](?:\s*,\s*(\w+))?$/);
|
|
12
|
+
if (match) {
|
|
13
|
+
return {
|
|
14
|
+
params: match[1].split(",").map((s) => s.trim()),
|
|
15
|
+
indexParam: match[2] || null,
|
|
16
|
+
isDestructured: true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parts = binding.split(",").map((s) => s.trim());
|
|
22
|
+
|
|
23
|
+
// 2 params without brackets = destructuring
|
|
24
|
+
// Runtime auto-converts: Array.isArray(c) ? c : Object.entries(c)
|
|
25
|
+
if (parts.length === 2) {
|
|
26
|
+
return { params: parts, indexParam: null, isDestructured: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { params: parts, indexParam: null, isDestructured: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get loop variable names from binding
|
|
34
|
+
*/
|
|
35
|
+
export function getLoopVarNames(binding) {
|
|
36
|
+
const parsed = parseLoopBinding(binding);
|
|
37
|
+
const names = [...parsed.params];
|
|
38
|
+
if (parsed.indexParam) names.push(parsed.indexParam);
|
|
39
|
+
// Add implicit i for single-param
|
|
40
|
+
if (parsed.params.length === 1 && !names.includes("i")) names.push("i");
|
|
41
|
+
return names;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get loop item variables (non-index) from binding
|
|
46
|
+
* These are variables that could be objects/arrays (not primitives like indices)
|
|
47
|
+
*/
|
|
48
|
+
export function getLoopItemVars(binding) {
|
|
49
|
+
const parsed = parseLoopBinding(binding);
|
|
50
|
+
// For 2-param destructuring: [value, index] - only first is item var
|
|
51
|
+
if (parsed.isDestructured && parsed.params.length === 2) {
|
|
52
|
+
return [parsed.params[0]];
|
|
53
|
+
}
|
|
54
|
+
// For other destructured bindings, all params are item vars
|
|
55
|
+
if (parsed.isDestructured) {
|
|
56
|
+
return parsed.params;
|
|
57
|
+
}
|
|
58
|
+
// For {#each items as item, index}, only 'item' is the item var
|
|
59
|
+
// For {#each obj as key, value, index}, 'key' and 'value' are item vars
|
|
60
|
+
if (parsed.params.length >= 3) {
|
|
61
|
+
// Last param is index, rest are item vars
|
|
62
|
+
return parsed.params.slice(0, -1);
|
|
63
|
+
}
|
|
64
|
+
if (parsed.params.length === 2) {
|
|
65
|
+
// Could be "item, index" - first is item, second is index
|
|
66
|
+
return [parsed.params[0]];
|
|
67
|
+
}
|
|
68
|
+
// Single param is the item var
|
|
69
|
+
return parsed.params;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build collection expression for iteration
|
|
74
|
+
*/
|
|
75
|
+
export function buildCollectionExpr(collection, binding) {
|
|
76
|
+
const parsed = parseLoopBinding(binding);
|
|
77
|
+
|
|
78
|
+
// 2-param destructuring uses Fez.toPairs for unified array/object handling
|
|
79
|
+
// Array: ['a', 'b'] → [['a', 0], ['b', 1]] (value, index)
|
|
80
|
+
// Object: {x: 1} → [['x', 1]] (key, value)
|
|
81
|
+
if (parsed.isDestructured && parsed.params.length === 2) {
|
|
82
|
+
return `Fez.toPairs(${collection})`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 3+ params: object iteration with explicit index
|
|
86
|
+
if (parsed.isDestructured || parsed.params.length >= 3) {
|
|
87
|
+
return `((_c)=>Array.isArray(_c)?_c:(_c&&typeof _c==="object")?Object.entries(_c):[])(${collection})`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return `(${collection}||[])`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build loop callback params
|
|
95
|
+
*/
|
|
96
|
+
export function buildLoopParams(binding) {
|
|
97
|
+
const parsed = parseLoopBinding(binding);
|
|
98
|
+
|
|
99
|
+
if (parsed.isDestructured) {
|
|
100
|
+
const destructure = "[" + parsed.params.join(", ") + "]";
|
|
101
|
+
const indexName =
|
|
102
|
+
parsed.indexParam || (parsed.params.includes("i") ? "_i" : "i");
|
|
103
|
+
return destructure + ", " + indexName;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (parsed.params.length >= 3) {
|
|
107
|
+
const params = [...parsed.params];
|
|
108
|
+
const index = params.pop();
|
|
109
|
+
return "[" + params.join(", ") + "], " + index;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (parsed.params.length === 2) {
|
|
113
|
+
return parsed.params.join(", ");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// If loop var is 'i', use '_i' for index to avoid collision
|
|
117
|
+
const indexName = parsed.params[0] === "i" ? "_i" : "i";
|
|
118
|
+
return parsed.params[0] + ", " + indexName;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if expression is an arrow function
|
|
123
|
+
*/
|
|
124
|
+
export function isArrowFunction(expr) {
|
|
125
|
+
// Match: () => ..., (e) => ..., (e, foo) => ..., e => ...
|
|
126
|
+
return /^\s*(\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/.test(expr);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Transform arrow function to onclick-compatible string
|
|
131
|
+
* Input: "(e) => removeTask(index)" with loopVars = ['item', 'index', 'i']
|
|
132
|
+
*
|
|
133
|
+
* For loop variables that are item references (not indices), we store the handler
|
|
134
|
+
* in fezGlobals to capture the value at render time. For index-only references,
|
|
135
|
+
* we use simple interpolation since indices are primitives.
|
|
136
|
+
*
|
|
137
|
+
* Output for index-only: "fez.removeTask(${index})"
|
|
138
|
+
* Output for item refs: "${'Fez(' + UID + ').fezGlobals.delete(' + fez.fezGlobals.set(() => fez.removeTask(item)) + ')()'}"
|
|
139
|
+
*/
|
|
140
|
+
export function transformArrowToHandler(
|
|
141
|
+
expr,
|
|
142
|
+
loopVars = [],
|
|
143
|
+
loopItemVars = [],
|
|
144
|
+
) {
|
|
145
|
+
// Extract the arrow function body
|
|
146
|
+
const arrowMatch = expr.match(
|
|
147
|
+
/^\s*(?:\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>\s*(.+)$/s,
|
|
148
|
+
);
|
|
149
|
+
if (!arrowMatch) return expr;
|
|
150
|
+
|
|
151
|
+
let body = arrowMatch[1].trim();
|
|
152
|
+
|
|
153
|
+
// Check if arrow has event param: (e) => or (event) => or e =>
|
|
154
|
+
const paramMatch = expr.match(
|
|
155
|
+
/^\s*\(?\s*([a-zA-Z_$][a-zA-Z0-9_$]*)?\s*(?:,\s*[^)]+)?\)?\s*=>/,
|
|
156
|
+
);
|
|
157
|
+
const eventParam = paramMatch?.[1];
|
|
158
|
+
const hasEventParam = eventParam && ["e", "event", "ev"].includes(eventParam);
|
|
159
|
+
|
|
160
|
+
// Check if body references loop item variables (non-index vars that could be objects)
|
|
161
|
+
const usedItemVars = loopItemVars.filter((varName) => {
|
|
162
|
+
const varRegex = new RegExp(`\\b${varName}\\b`);
|
|
163
|
+
return varRegex.test(body);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// If arrow function uses item variables (not just indices), store the function in fezGlobals
|
|
167
|
+
// This ensures object references are captured at render time
|
|
168
|
+
if (usedItemVars.length > 0) {
|
|
169
|
+
// Replace event param with 'event' in the body if needed
|
|
170
|
+
if (hasEventParam && eventParam !== "event") {
|
|
171
|
+
const eventRegex = new RegExp(`\\b${eventParam}\\b`, "g");
|
|
172
|
+
body = body.replace(eventRegex, "event");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Prefix bare function calls with fez.
|
|
176
|
+
body = body.replace(
|
|
177
|
+
/(?<![.\w])([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g,
|
|
178
|
+
(match, funcName) => {
|
|
179
|
+
const globals = [
|
|
180
|
+
"console",
|
|
181
|
+
"window",
|
|
182
|
+
"document",
|
|
183
|
+
"Math",
|
|
184
|
+
"JSON",
|
|
185
|
+
"Date",
|
|
186
|
+
"Array",
|
|
187
|
+
"Object",
|
|
188
|
+
"String",
|
|
189
|
+
"Number",
|
|
190
|
+
"Boolean",
|
|
191
|
+
"parseInt",
|
|
192
|
+
"parseFloat",
|
|
193
|
+
"setTimeout",
|
|
194
|
+
"setInterval",
|
|
195
|
+
"clearTimeout",
|
|
196
|
+
"clearInterval",
|
|
197
|
+
"alert",
|
|
198
|
+
"confirm",
|
|
199
|
+
"prompt",
|
|
200
|
+
"fetch",
|
|
201
|
+
"event",
|
|
202
|
+
];
|
|
203
|
+
if (globals.includes(funcName)) {
|
|
204
|
+
return match;
|
|
205
|
+
}
|
|
206
|
+
return `fez.${funcName}(`;
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Store the function with captured loop vars, retrieve and call at click time
|
|
211
|
+
// Uses IIFE to build the string at render time with UID and set() evaluated
|
|
212
|
+
return `\${'Fez(' + UID + ').fezGlobals.delete(' + fez.fezGlobals.set(() => ${body}) + ')()'}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// No item variables - use simple interpolation for indices (original behavior)
|
|
216
|
+
// Replace event param with 'event' (the actual DOM event)
|
|
217
|
+
if (hasEventParam && eventParam !== "event") {
|
|
218
|
+
const eventRegex = new RegExp(`\\b${eventParam}\\b`, "g");
|
|
219
|
+
body = body.replace(eventRegex, "event");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Interpolate loop variables - they need to be evaluated at render time
|
|
223
|
+
// e.g., removeTask(index) becomes removeTask(${index})
|
|
224
|
+
for (const varName of loopVars) {
|
|
225
|
+
// Match the variable as a standalone identifier (not part of another word)
|
|
226
|
+
// and not already inside a template literal
|
|
227
|
+
const varRegex = new RegExp(`(?<!\\$\\{)\\b${varName}\\b(?![^{]*\\})`, "g");
|
|
228
|
+
body = body.replace(varRegex, `\${${varName}}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Prefix bare function calls with fez.
|
|
232
|
+
body = body.replace(
|
|
233
|
+
/(?<![.\w])([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g,
|
|
234
|
+
(match, funcName) => {
|
|
235
|
+
const globals = [
|
|
236
|
+
"console",
|
|
237
|
+
"window",
|
|
238
|
+
"document",
|
|
239
|
+
"Math",
|
|
240
|
+
"JSON",
|
|
241
|
+
"Date",
|
|
242
|
+
"Array",
|
|
243
|
+
"Object",
|
|
244
|
+
"String",
|
|
245
|
+
"Number",
|
|
246
|
+
"Boolean",
|
|
247
|
+
"parseInt",
|
|
248
|
+
"parseFloat",
|
|
249
|
+
"setTimeout",
|
|
250
|
+
"setInterval",
|
|
251
|
+
"clearTimeout",
|
|
252
|
+
"clearInterval",
|
|
253
|
+
"alert",
|
|
254
|
+
"confirm",
|
|
255
|
+
"prompt",
|
|
256
|
+
"fetch",
|
|
257
|
+
"event",
|
|
258
|
+
];
|
|
259
|
+
if (globals.includes(funcName)) {
|
|
260
|
+
return match;
|
|
261
|
+
}
|
|
262
|
+
return `fez.${funcName}(`;
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
return body;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Extract a braced expression with proper depth counting
|
|
271
|
+
*/
|
|
272
|
+
export function extractBracedExpression(text, startIndex) {
|
|
273
|
+
let depth = 0;
|
|
274
|
+
let i = startIndex;
|
|
275
|
+
|
|
276
|
+
while (i < text.length) {
|
|
277
|
+
const char = text[i];
|
|
278
|
+
if (char === "{") {
|
|
279
|
+
depth++;
|
|
280
|
+
} else if (char === "}") {
|
|
281
|
+
depth--;
|
|
282
|
+
if (depth === 0) {
|
|
283
|
+
return { expression: text.slice(startIndex + 1, i), endIndex: i };
|
|
284
|
+
}
|
|
285
|
+
} else if (char === '"' || char === "'" || char === "`") {
|
|
286
|
+
// Skip string literals
|
|
287
|
+
const quote = char;
|
|
288
|
+
i++;
|
|
289
|
+
while (i < text.length && text[i] !== quote) {
|
|
290
|
+
if (text[i] === "\\") i++;
|
|
291
|
+
i++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
i++;
|
|
295
|
+
}
|
|
296
|
+
throw new Error(`Unmatched brace at ${startIndex}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Check if position is inside an attribute (attr={...})
|
|
301
|
+
* Returns the attribute name if inside one, null otherwise
|
|
302
|
+
*/
|
|
303
|
+
export function getAttributeContext(text, pos) {
|
|
304
|
+
// Look backwards for pattern like: attr={
|
|
305
|
+
// We need to find the last '=' before pos that's preceded by an attribute name
|
|
306
|
+
let j = pos - 1;
|
|
307
|
+
// Skip whitespace and opening brace
|
|
308
|
+
while (j >= 0 && (text[j] === "{" || text[j] === " " || text[j] === "\t"))
|
|
309
|
+
j--;
|
|
310
|
+
if (j >= 0 && text[j] === "=") {
|
|
311
|
+
// Found '=', now look for attribute name
|
|
312
|
+
j--;
|
|
313
|
+
while (j >= 0 && (text[j] === " " || text[j] === "\t")) j--;
|
|
314
|
+
// Extract attribute name
|
|
315
|
+
let attrEnd = j + 1;
|
|
316
|
+
while (j >= 0 && /[a-zA-Z0-9_:-]/.test(text[j])) j--;
|
|
317
|
+
const attrName = text.slice(j + 1, attrEnd);
|
|
318
|
+
if (
|
|
319
|
+
attrName &&
|
|
320
|
+
/^[a-zA-Z]/.test(attrName) &&
|
|
321
|
+
(j < 0 || /\s/.test(text[j]))
|
|
322
|
+
) {
|
|
323
|
+
return attrName.toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Check if position is inside an event attribute (onclick=, onchange=, etc.)
|
|
331
|
+
* Returns the attribute name if inside one, null otherwise
|
|
332
|
+
*/
|
|
333
|
+
export function getEventAttributeContext(text, pos) {
|
|
334
|
+
const attr = getAttributeContext(text, pos);
|
|
335
|
+
if (attr && /^on[a-z]+$/.test(attr)) {
|
|
336
|
+
return attr;
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|