@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,472 @@
|
|
|
1
|
+
// Svelte-like template parser for Fez
|
|
2
|
+
// Compiles to a single function that returns HTML string
|
|
3
|
+
//
|
|
4
|
+
// Supports:
|
|
5
|
+
// {#if cond}...{:else if cond}...{:else}...{/if}
|
|
6
|
+
// {#unless cond}...{/unless}
|
|
7
|
+
// {#each items as item}...{/each} - implicit index `i` available
|
|
8
|
+
// {#each items as item, index}...{/each} - explicit index name
|
|
9
|
+
// {#for item in items}...{/for} - implicit index `i` available
|
|
10
|
+
// {#for item, index in items}...{/for} - explicit index name
|
|
11
|
+
// {#each obj as key, value, index} - object iteration (3 params)
|
|
12
|
+
// {#await promise}...{:then value}...{:catch error}...{/await}
|
|
13
|
+
// {@html rawContent} - unescaped HTML
|
|
14
|
+
// {@json obj} - debug JSON output
|
|
15
|
+
// {expression} - escaped expression
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
getLoopVarNames,
|
|
19
|
+
getLoopItemVars,
|
|
20
|
+
buildCollectionExpr,
|
|
21
|
+
buildLoopParams,
|
|
22
|
+
isArrowFunction,
|
|
23
|
+
transformArrowToHandler,
|
|
24
|
+
extractBracedExpression,
|
|
25
|
+
getAttributeContext,
|
|
26
|
+
getEventAttributeContext,
|
|
27
|
+
} from "./svelte-template-lib.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compile template to a function that returns HTML string
|
|
31
|
+
*
|
|
32
|
+
* @param {string} text - Template source
|
|
33
|
+
* @param {Object} opts - Options
|
|
34
|
+
* @param {string} opts.name - Component name for error messages
|
|
35
|
+
*/
|
|
36
|
+
export default function createSvelteTemplate(text, opts = {}) {
|
|
37
|
+
const componentName = opts.name || "unknown";
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Decode HTML entities that might have been encoded by browser DOM
|
|
41
|
+
text = text
|
|
42
|
+
.replaceAll("`", "`")
|
|
43
|
+
.replaceAll("<", "<")
|
|
44
|
+
.replaceAll(">", ">")
|
|
45
|
+
.replaceAll("&", "&");
|
|
46
|
+
|
|
47
|
+
// Allow Svelte-style fez:attr namespace syntax as alias for fez-attr
|
|
48
|
+
text = text.replace(/\bfez:([a-z]+)=/gi, "fez-$1=");
|
|
49
|
+
|
|
50
|
+
// Error if fez-keep is placed on a fez component (custom element with dash in tag name)
|
|
51
|
+
const keepOnComponent = text.match(
|
|
52
|
+
/<([a-z]+-[a-z][a-z0-9-]*)\b[^>]*\bfez-keep=/,
|
|
53
|
+
);
|
|
54
|
+
if (keepOnComponent) {
|
|
55
|
+
console.error(
|
|
56
|
+
`FEZ: fez:keep must be on plain HTML elements, not on fez components. Found on <${keepOnComponent[1]}> in <${componentName}>`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Process block definitions and references before parsing
|
|
61
|
+
const blocks = {};
|
|
62
|
+
text = text.replace(
|
|
63
|
+
/\{@block\s+(\w+)\}([\s\S]*?)\{\/block\}/g,
|
|
64
|
+
(_, name, content) => {
|
|
65
|
+
blocks[name] = content;
|
|
66
|
+
return "";
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
text = text.replace(/\{@block:(\w+)\}/g, (_, name) => blocks[name] || "");
|
|
70
|
+
|
|
71
|
+
// Convert :attr="expr" to use Fez(UID).fezGlobals for passing values through DOM
|
|
72
|
+
// This allows loop variables to be passed as props to child components
|
|
73
|
+
// :file="el.file" -> :file={`Fez(${UID}).fezGlobals.delete(${fez.fezGlobals.set(el.file)})`}
|
|
74
|
+
// Uses Fez(UID) so child component can find parent's fezGlobals
|
|
75
|
+
text = text.replace(/:(\w+)="([^"{}]+)"/g, (match, attr, expr) => {
|
|
76
|
+
// Only convert if expr looks like a variable access (not a string literal)
|
|
77
|
+
if (/^[\w.[\]]+$/.test(expr.trim())) {
|
|
78
|
+
return `:${attr}={\`Fez(\${UID}).fezGlobals.delete(\${fez.fezGlobals.set(${expr})})\`}`;
|
|
79
|
+
}
|
|
80
|
+
return match;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Remove HTML comments
|
|
84
|
+
text = text.replace(/<!--[\s\S]*?-->/g, "");
|
|
85
|
+
|
|
86
|
+
// Normalize whitespace between tags
|
|
87
|
+
text = text.replace(/>\s+</g, "><").trim();
|
|
88
|
+
|
|
89
|
+
// Convert self-closing custom elements to paired tags
|
|
90
|
+
// <ui-icon name="foo" /> -> <ui-icon name="foo"></ui-icon>
|
|
91
|
+
// Custom elements contain a hyphen in the tag name
|
|
92
|
+
// Uses (?:[^>]|=>) to skip => (arrow functions) inside attributes
|
|
93
|
+
text = text.replace(
|
|
94
|
+
/<([a-z][a-z0-9]*-[a-z0-9-]*)((?:=>|[^>])*)>/gi,
|
|
95
|
+
(match, tag, attrs) => {
|
|
96
|
+
if (attrs.trimEnd().endsWith("/")) {
|
|
97
|
+
return `<${tag}${attrs.replace(/\s*\/$/, "")}></${tag}>`;
|
|
98
|
+
}
|
|
99
|
+
return match;
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Convert self-closing <slot /> to <slot></slot>
|
|
104
|
+
text = text.replace(/<slot\s*\/>/gi, "<slot></slot>");
|
|
105
|
+
|
|
106
|
+
// Parse and build template literal
|
|
107
|
+
let result = "";
|
|
108
|
+
let i = 0;
|
|
109
|
+
const ifStack = []; // Track if blocks have else
|
|
110
|
+
const loopVarStack = []; // Track all loop variables for arrow function transformation
|
|
111
|
+
const loopItemVarStack = []; // Track item vars (non-index) that could be objects
|
|
112
|
+
const loopStack = []; // Track loop info for :else support
|
|
113
|
+
const awaitStack = []; // Track await blocks for :then/:catch
|
|
114
|
+
let awaitCounter = 0; // Unique ID for each await block
|
|
115
|
+
|
|
116
|
+
while (i < text.length) {
|
|
117
|
+
// Skip JavaScript template literals (backtick strings)
|
|
118
|
+
// Content inside backticks should not be processed as Fez expressions
|
|
119
|
+
if (text[i] === "`") {
|
|
120
|
+
result += "\\`";
|
|
121
|
+
i++;
|
|
122
|
+
// Copy everything until closing backtick
|
|
123
|
+
while (i < text.length && text[i] !== "`") {
|
|
124
|
+
if (text[i] === "\\") {
|
|
125
|
+
// Handle escaped characters
|
|
126
|
+
result += "\\\\";
|
|
127
|
+
i++;
|
|
128
|
+
if (i < text.length) {
|
|
129
|
+
if (text[i] === "`") {
|
|
130
|
+
result += "\\`";
|
|
131
|
+
} else if (text[i] === "$") {
|
|
132
|
+
result += "\\$";
|
|
133
|
+
} else {
|
|
134
|
+
result += text[i];
|
|
135
|
+
}
|
|
136
|
+
i++;
|
|
137
|
+
}
|
|
138
|
+
} else if (text[i] === "$" && text[i + 1] === "{") {
|
|
139
|
+
// Keep JS template literal interpolation as-is (escape $ for outer template)
|
|
140
|
+
result += "\\${";
|
|
141
|
+
i += 2;
|
|
142
|
+
// Copy until matching }
|
|
143
|
+
let depth = 1;
|
|
144
|
+
while (i < text.length && depth > 0) {
|
|
145
|
+
if (text[i] === "{") depth++;
|
|
146
|
+
else if (text[i] === "}") depth--;
|
|
147
|
+
if (depth > 0 || text[i] !== "}") {
|
|
148
|
+
if (text[i] === "`") result += "\\`";
|
|
149
|
+
else if (text[i] === "\\") result += "\\\\";
|
|
150
|
+
else result += text[i];
|
|
151
|
+
} else {
|
|
152
|
+
result += "}";
|
|
153
|
+
}
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// Regular character inside backticks - escape special chars for outer template
|
|
158
|
+
if (text[i] === "$") {
|
|
159
|
+
result += "\\$";
|
|
160
|
+
} else {
|
|
161
|
+
result += text[i];
|
|
162
|
+
}
|
|
163
|
+
i++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (i < text.length) {
|
|
167
|
+
result += "\\`";
|
|
168
|
+
i++;
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Escaped brace
|
|
174
|
+
if (text[i] === "\\" && text[i + 1] === "{") {
|
|
175
|
+
result += "{";
|
|
176
|
+
i += 2;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Expression or directive
|
|
181
|
+
if (text[i] === "{") {
|
|
182
|
+
const { expression, endIndex } = extractBracedExpression(text, i);
|
|
183
|
+
const expr = expression.trim();
|
|
184
|
+
|
|
185
|
+
// Check if this is a JavaScript object literal (e.g., {d: 'top'}, {foo: 1, bar: 2})
|
|
186
|
+
// Object literals start with key: where key is identifier or quoted string
|
|
187
|
+
if (/^(\w+|"\w+"|'\w+')\s*:/.test(expr)) {
|
|
188
|
+
// Keep object literal as-is in the output
|
|
189
|
+
result += "{" + expression + "}";
|
|
190
|
+
i = endIndex + 1;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Block directives
|
|
195
|
+
if (expr.startsWith("#if ")) {
|
|
196
|
+
const cond = expr.slice(4);
|
|
197
|
+
result += "${Fez.isTruthy(" + cond + ") ? `";
|
|
198
|
+
ifStack.push(false); // No else yet
|
|
199
|
+
} else if (expr.startsWith("#unless ")) {
|
|
200
|
+
const cond = expr.slice(8);
|
|
201
|
+
result += "${!Fez.isTruthy(" + cond + ") ? `";
|
|
202
|
+
ifStack.push(false); // No else yet
|
|
203
|
+
} else if (expr === ":else" || expr === "else") {
|
|
204
|
+
// Check if we're inside a loop (for empty list handling)
|
|
205
|
+
if (loopStack.length > 0 && !ifStack.length) {
|
|
206
|
+
// :else inside a loop - for empty array case
|
|
207
|
+
const loopInfo = loopStack[loopStack.length - 1];
|
|
208
|
+
loopInfo.hasElse = true;
|
|
209
|
+
result += '`).join("") : `';
|
|
210
|
+
} else {
|
|
211
|
+
// :else inside an if block
|
|
212
|
+
result += "` : `";
|
|
213
|
+
ifStack[ifStack.length - 1] = true; // Has else
|
|
214
|
+
}
|
|
215
|
+
} else if (
|
|
216
|
+
expr.startsWith(":else if ") ||
|
|
217
|
+
expr.startsWith("else if ") ||
|
|
218
|
+
expr.startsWith("elsif ") ||
|
|
219
|
+
expr.startsWith("elseif ")
|
|
220
|
+
) {
|
|
221
|
+
const cond = expr.startsWith(":else if ")
|
|
222
|
+
? expr.slice(9)
|
|
223
|
+
: expr.startsWith("else if ")
|
|
224
|
+
? expr.slice(8)
|
|
225
|
+
: expr.startsWith("elseif ")
|
|
226
|
+
? expr.slice(7)
|
|
227
|
+
: expr.slice(6);
|
|
228
|
+
result += "` : Fez.isTruthy(" + cond + ") ? `";
|
|
229
|
+
// Keep hasElse as false - still need final else
|
|
230
|
+
} else if (expr === "/if" || expr === "/unless") {
|
|
231
|
+
const hasElse = ifStack.pop();
|
|
232
|
+
result += hasElse ? "`}" : "` : ``}";
|
|
233
|
+
} else if (expr.startsWith("#each ") || expr.startsWith("#for ")) {
|
|
234
|
+
const isEach = expr.startsWith("#each ");
|
|
235
|
+
let collection, binding;
|
|
236
|
+
|
|
237
|
+
if (isEach) {
|
|
238
|
+
const rest = expr.slice(6);
|
|
239
|
+
const asIdx = rest.indexOf(" as ");
|
|
240
|
+
collection = rest.slice(0, asIdx).trim();
|
|
241
|
+
binding = rest.slice(asIdx + 4).trim();
|
|
242
|
+
} else {
|
|
243
|
+
const rest = expr.slice(5);
|
|
244
|
+
const inIdx = rest.indexOf(" in ");
|
|
245
|
+
binding = rest.slice(0, inIdx).trim();
|
|
246
|
+
collection = rest.slice(inIdx + 4).trim();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const collectionExpr = buildCollectionExpr(collection, binding);
|
|
250
|
+
const loopParams = buildLoopParams(binding);
|
|
251
|
+
|
|
252
|
+
// Track loop variables for arrow function transformation
|
|
253
|
+
loopVarStack.push(getLoopVarNames(binding));
|
|
254
|
+
loopItemVarStack.push(getLoopItemVars(binding));
|
|
255
|
+
|
|
256
|
+
// Track loop info for :else support
|
|
257
|
+
// Use a wrapper that allows checking length and provides else support
|
|
258
|
+
// ((_arr) => _arr.length ? _arr.map(...).join('') : elseContent)(collection)
|
|
259
|
+
loopStack.push({ collectionExpr, hasElse: false });
|
|
260
|
+
|
|
261
|
+
result +=
|
|
262
|
+
"${((_arr) => _arr.length ? _arr.map((" + loopParams + ") => `";
|
|
263
|
+
} else if (expr === "/each" || expr === "/for") {
|
|
264
|
+
loopVarStack.pop(); // Remove loop vars when exiting loop
|
|
265
|
+
loopItemVarStack.pop(); // Remove item vars when exiting loop
|
|
266
|
+
const loopInfo = loopStack.pop();
|
|
267
|
+
if (loopInfo.hasElse) {
|
|
268
|
+
// Close the else branch
|
|
269
|
+
result += "`)(" + loopInfo.collectionExpr + ")}";
|
|
270
|
+
} else {
|
|
271
|
+
// No else - just close the ternary with empty string
|
|
272
|
+
result += '`).join("") : "")(' + loopInfo.collectionExpr + ")}";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// {#await promise}...{:then value}...{:catch error}...{/await}
|
|
276
|
+
else if (expr.startsWith("#await ")) {
|
|
277
|
+
const promiseExpr = expr.slice(7).trim();
|
|
278
|
+
const awaitId = awaitCounter++;
|
|
279
|
+
awaitStack.push({
|
|
280
|
+
awaitId,
|
|
281
|
+
promiseExpr,
|
|
282
|
+
hasThen: false,
|
|
283
|
+
hasCatch: false,
|
|
284
|
+
thenVar: "_value",
|
|
285
|
+
catchVar: "_error",
|
|
286
|
+
});
|
|
287
|
+
// Start with pending block - Fez.await returns { status, value, error }
|
|
288
|
+
result += '${((_aw) => _aw.status === "pending" ? `';
|
|
289
|
+
} else if (expr.startsWith(":then")) {
|
|
290
|
+
const awaitInfo = awaitStack[awaitStack.length - 1];
|
|
291
|
+
if (awaitInfo) {
|
|
292
|
+
awaitInfo.hasThen = true;
|
|
293
|
+
// Extract optional value binding: {:then value} or just {:then}
|
|
294
|
+
awaitInfo.thenVar = expr.slice(5).trim() || "_value";
|
|
295
|
+
result +=
|
|
296
|
+
'` : _aw.status === "resolved" ? ((' +
|
|
297
|
+
awaitInfo.thenVar +
|
|
298
|
+
") => `";
|
|
299
|
+
}
|
|
300
|
+
} else if (expr.startsWith(":catch")) {
|
|
301
|
+
const awaitInfo = awaitStack[awaitStack.length - 1];
|
|
302
|
+
if (awaitInfo) {
|
|
303
|
+
awaitInfo.hasCatch = true;
|
|
304
|
+
// Extract optional error binding: {:catch error} or just {:catch}
|
|
305
|
+
awaitInfo.catchVar = expr.slice(6).trim() || "_error";
|
|
306
|
+
if (awaitInfo.hasThen) {
|
|
307
|
+
// Close the :then block, open :catch
|
|
308
|
+
result +=
|
|
309
|
+
'`)(_aw.value) : _aw.status === "rejected" ? ((' +
|
|
310
|
+
awaitInfo.catchVar +
|
|
311
|
+
") => `";
|
|
312
|
+
} else {
|
|
313
|
+
// No :then block, go directly from pending to catch (skip resolved state)
|
|
314
|
+
result +=
|
|
315
|
+
'` : _aw.status === "rejected" ? ((' +
|
|
316
|
+
awaitInfo.catchVar +
|
|
317
|
+
") => `";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else if (expr === "/await") {
|
|
321
|
+
const awaitInfo = awaitStack.pop();
|
|
322
|
+
if (awaitInfo) {
|
|
323
|
+
// Close the await expression
|
|
324
|
+
// The structure depends on which blocks were present:
|
|
325
|
+
// - pending + then + catch: pending ? ... : resolved ? ...then... : ...catch...
|
|
326
|
+
// - pending + then: pending ? ... : resolved ? ...then... : ``
|
|
327
|
+
// - pending + catch: pending ? ... : rejected ? ...catch... : ``
|
|
328
|
+
// - pending only: pending ? ... : ``
|
|
329
|
+
if (awaitInfo.hasThen && awaitInfo.hasCatch) {
|
|
330
|
+
result +=
|
|
331
|
+
"`)(_aw.error) : ``)(Fez.fezAwait(fez, " +
|
|
332
|
+
awaitInfo.awaitId +
|
|
333
|
+
", " +
|
|
334
|
+
awaitInfo.promiseExpr +
|
|
335
|
+
"))}";
|
|
336
|
+
} else if (awaitInfo.hasThen) {
|
|
337
|
+
result +=
|
|
338
|
+
"`)(_aw.value) : ``)(Fez.fezAwait(fez, " +
|
|
339
|
+
awaitInfo.awaitId +
|
|
340
|
+
", " +
|
|
341
|
+
awaitInfo.promiseExpr +
|
|
342
|
+
"))}";
|
|
343
|
+
} else if (awaitInfo.hasCatch) {
|
|
344
|
+
result +=
|
|
345
|
+
"`)(_aw.error) : ``)(Fez.fezAwait(fez, " +
|
|
346
|
+
awaitInfo.awaitId +
|
|
347
|
+
", " +
|
|
348
|
+
awaitInfo.promiseExpr +
|
|
349
|
+
"))}";
|
|
350
|
+
} else {
|
|
351
|
+
// Only pending block (no :then or :catch)
|
|
352
|
+
result +=
|
|
353
|
+
"` : ``)(Fez.fezAwait(fez, " +
|
|
354
|
+
awaitInfo.awaitId +
|
|
355
|
+
", " +
|
|
356
|
+
awaitInfo.promiseExpr +
|
|
357
|
+
"))}";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} else if (expr.startsWith("@html ")) {
|
|
361
|
+
const content = expr.slice(6);
|
|
362
|
+
result += "${" + content + "}";
|
|
363
|
+
} else if (expr.startsWith("@json ")) {
|
|
364
|
+
const content = expr.slice(6);
|
|
365
|
+
result +=
|
|
366
|
+
'${`<pre class="json">${Fez.htmlEscape(JSON.stringify(' +
|
|
367
|
+
content +
|
|
368
|
+
", null, 2))}</pre>`}";
|
|
369
|
+
} else if (isArrowFunction(expr)) {
|
|
370
|
+
// Arrow function - check if we're in an event attribute
|
|
371
|
+
const eventAttr = getEventAttributeContext(text, i);
|
|
372
|
+
if (eventAttr) {
|
|
373
|
+
// Get all current loop variables
|
|
374
|
+
const allLoopVars = loopVarStack.flat();
|
|
375
|
+
const allItemVars = loopItemVarStack.flat();
|
|
376
|
+
let handler = transformArrowToHandler(
|
|
377
|
+
expr,
|
|
378
|
+
allLoopVars,
|
|
379
|
+
allItemVars,
|
|
380
|
+
);
|
|
381
|
+
// Escape double quotes for HTML attribute
|
|
382
|
+
handler = handler.replace(/"/g, """);
|
|
383
|
+
// Output as quoted attribute value with interpolation for loop vars
|
|
384
|
+
result += '"' + handler + '"';
|
|
385
|
+
} else {
|
|
386
|
+
// Arrow function outside event attribute - just output as expression
|
|
387
|
+
result += "${" + expr + "}";
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
// Plain expression - check if inside attribute
|
|
391
|
+
const attrContext = getAttributeContext(text, i);
|
|
392
|
+
if (attrContext) {
|
|
393
|
+
// Inside attribute - wrap with quotes and escape
|
|
394
|
+
result += '"${Fez.htmlEscape(' + expr + ')}"';
|
|
395
|
+
} else {
|
|
396
|
+
// Regular content - just escape HTML
|
|
397
|
+
result += "${Fez.htmlEscape(" + expr + ")}";
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
i = endIndex + 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Escape special characters for template literal
|
|
406
|
+
if (text[i] === "$" && text[i + 1] === "{") {
|
|
407
|
+
result += "\\$";
|
|
408
|
+
} else if (text[i] === "\\") {
|
|
409
|
+
result += "\\\\";
|
|
410
|
+
} else {
|
|
411
|
+
result += text[i];
|
|
412
|
+
}
|
|
413
|
+
i++;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Auto-generate IDs for fez-this elements (static values only)
|
|
417
|
+
// This helps the DOM differ match and preserve nodes across re-renders
|
|
418
|
+
result = result.replace(
|
|
419
|
+
/(<[a-z][a-z0-9-]*\s+)([^>]*?)(fez-this="([^"{}]+)")([^>]*?)>/gi,
|
|
420
|
+
(match, tagStart, before, fezThisAttr, fezThisValue, after) => {
|
|
421
|
+
// Skip if id already exists
|
|
422
|
+
if (/\bid=/.test(before) || /\bid=/.test(after)) {
|
|
423
|
+
return match;
|
|
424
|
+
}
|
|
425
|
+
// Sanitize: replace non-alphanumeric with -
|
|
426
|
+
const sanitized = fezThisValue.replace(/[^a-zA-Z0-9]/g, "-");
|
|
427
|
+
return `${tagStart}${before}${fezThisAttr}${after} id="fez-\${UID}-${sanitized}">`;
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
// Warn about dynamic fez-this values in dev mode (won't get auto-ID)
|
|
432
|
+
if (typeof Fez !== "undefined" && Fez.LOG) {
|
|
433
|
+
const dynamicFezThis = result.match(/fez-this="[^"]*\{[^}]+\}[^"]*"/g);
|
|
434
|
+
if (dynamicFezThis) {
|
|
435
|
+
console.warn(
|
|
436
|
+
`Fez <${componentName}>: Dynamic fez-this values won't get auto-ID for DOM differ matching:`,
|
|
437
|
+
dynamicFezThis,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Build the function
|
|
443
|
+
const funcBody = `
|
|
444
|
+
const fez = this;
|
|
445
|
+
with (this) {
|
|
446
|
+
return \`${result}\`
|
|
447
|
+
}
|
|
448
|
+
`;
|
|
449
|
+
|
|
450
|
+
const tplFunc = new Function(funcBody);
|
|
451
|
+
|
|
452
|
+
return (ctx) => {
|
|
453
|
+
try {
|
|
454
|
+
return tplFunc.bind(ctx)();
|
|
455
|
+
} catch (e) {
|
|
456
|
+
console.error(
|
|
457
|
+
`FEZ svelte template runtime error in <${ctx.fezName || componentName}>:`,
|
|
458
|
+
e.message,
|
|
459
|
+
);
|
|
460
|
+
console.error("Template source:", result.substring(0, 500));
|
|
461
|
+
return "";
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
} catch (e) {
|
|
465
|
+
console.error(
|
|
466
|
+
`FEZ svelte template compile error in <${componentName}>:`,
|
|
467
|
+
e.message,
|
|
468
|
+
);
|
|
469
|
+
console.error("Template:", text.substring(0, 200));
|
|
470
|
+
return () => "";
|
|
471
|
+
}
|
|
472
|
+
}
|