@sola-air-ui/compiler 1.0.1 → 1.0.3
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/package.json +38 -31
- package/src/index.js +219 -74
package/package.json
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@sola-air-ui/compiler",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Compiler for Sola single-file components (.sola) — transforms template + script + style to ES modules",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./src/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"sola": "./src/cli.js"
|
|
9
|
-
},
|
|
10
|
-
"exports": {
|
|
11
|
-
".": "./src/index.js"
|
|
12
|
-
},
|
|
13
|
-
"scripts": {
|
|
14
|
-
"test": "node test/compiler.test.js"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@sola-air-ui/compiler",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Compiler for Sola single-file components (.sola) — transforms template + script + style to ES modules",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sola": "./src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node test/compiler.test.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sola",
|
|
18
|
+
"compiler",
|
|
19
|
+
"sfc",
|
|
20
|
+
"single-file-component",
|
|
21
|
+
"zero-vdom",
|
|
22
|
+
"build-tool"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://sola-air.dev",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/rbm3267/sola-air"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"acorn": "^8.10.0",
|
|
32
|
+
"htmlparser2": "^9.0.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"registry": "https://registry.npmjs.org/"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.js
CHANGED
|
@@ -57,17 +57,142 @@ function extractScriptAndStyle(source) {
|
|
|
57
57
|
return { script, style, template };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Returns the index of the closing } that matches the { at openPos,
|
|
61
|
+
// respecting string literals and nested braces. Returns -1 if unbalanced.
|
|
62
|
+
function matchBrace(text, openPos) {
|
|
63
|
+
let depth = 0;
|
|
64
|
+
let inStr = null;
|
|
65
|
+
for (let i = openPos; i < text.length; i++) {
|
|
66
|
+
const ch = text[i];
|
|
67
|
+
if (inStr) {
|
|
68
|
+
if (ch === '\\') { i++; continue; }
|
|
69
|
+
if (ch === inStr) inStr = null;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === '"' || ch === "'" || ch === '`') { inStr = ch; continue; }
|
|
73
|
+
if (ch === '{') depth++;
|
|
74
|
+
else if (ch === '}') { if (--depth === 0) return i; }
|
|
75
|
+
}
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Walk the template and resolve {expr} in attribute values using balanced-brace
|
|
80
|
+
// matching, storing dynamic expressions in dynAttrs. Event/bind handlers are
|
|
81
|
+
// stored as plain strings; other expressions become __soladyn_N__ markers.
|
|
82
|
+
function preprocessTemplate(template, dynAttrs) {
|
|
83
|
+
let result = '';
|
|
84
|
+
let i = 0;
|
|
85
|
+
while (i < template.length) {
|
|
86
|
+
// Opening tag (not closing, not comment)
|
|
87
|
+
if (template[i] === '<' && template[i + 1] !== '/' && template[i + 1] !== '!') {
|
|
88
|
+
result += '<';
|
|
89
|
+
i++;
|
|
90
|
+
// Tag name
|
|
91
|
+
while (i < template.length && /[^\s>\/]/.test(template[i])) result += template[i++];
|
|
92
|
+
// Attributes
|
|
93
|
+
while (i < template.length && template[i] !== '>' && !(template[i] === '/' && template[i + 1] === '>')) {
|
|
94
|
+
if (/\s/.test(template[i])) { result += template[i++]; continue; }
|
|
95
|
+
// Attribute name
|
|
96
|
+
let attrName = '';
|
|
97
|
+
while (i < template.length && /[^\s=\/>]/.test(template[i])) attrName += template[i++];
|
|
98
|
+
if (!attrName) { result += template[i++]; continue; }
|
|
99
|
+
if (template[i] !== '=') { result += attrName; continue; }
|
|
100
|
+
i++; // skip =
|
|
101
|
+
if (template[i] === '{') {
|
|
102
|
+
// Unquoted brace expression: attr={expr}
|
|
103
|
+
const closeIdx = matchBrace(template, i);
|
|
104
|
+
if (closeIdx !== -1) {
|
|
105
|
+
const expr = template.slice(i + 1, closeIdx).trim();
|
|
106
|
+
if (!expr.startsWith('#') && !expr.startsWith('/') && !expr.startsWith(':')) {
|
|
107
|
+
if (/^on:[\w]+$/.test(attrName) || /^bind:[\w]+$/.test(attrName)) {
|
|
108
|
+
result += `${attrName}="${expr}"`;
|
|
109
|
+
} else {
|
|
110
|
+
dynAttrs.push(expr);
|
|
111
|
+
result += `${attrName}="__soladyn_${dynAttrs.length - 1}__"`;
|
|
112
|
+
}
|
|
113
|
+
i = closeIdx + 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
result += `${attrName}={`; i++;
|
|
118
|
+
} else if (template[i] === '"' || template[i] === "'") {
|
|
119
|
+
// Quoted attribute value — scan for {expr} interpolations
|
|
120
|
+
const quote = template[i++];
|
|
121
|
+
let inner = ''; let hasDyn = false;
|
|
122
|
+
while (i < template.length && template[i] !== quote) {
|
|
123
|
+
if (template[i] === '{') {
|
|
124
|
+
const closeIdx = matchBrace(template, i);
|
|
125
|
+
if (closeIdx !== -1) {
|
|
126
|
+
const expr = template.slice(i + 1, closeIdx).trim();
|
|
127
|
+
if (!expr.startsWith('#') && !expr.startsWith('/') && !expr.startsWith(':')) {
|
|
128
|
+
hasDyn = true;
|
|
129
|
+
inner += `\${${expr}}`; i = closeIdx + 1; continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
inner += template[i++];
|
|
134
|
+
}
|
|
135
|
+
if (i < template.length) i++; // skip closing quote
|
|
136
|
+
if (hasDyn) {
|
|
137
|
+
dynAttrs.push('`' + inner + '`');
|
|
138
|
+
result += `${attrName}="__soladyn_${dynAttrs.length - 1}__"`;
|
|
139
|
+
} else {
|
|
140
|
+
result += `${attrName}="${inner}"`;
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
result += `${attrName}=`;
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (i < template.length && template[i] === '/') { result += '/'; i++; }
|
|
148
|
+
if (i < template.length && template[i] === '>') { result += '>'; i++; }
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
result += template[i++];
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Replace {expr} in text content with <sola-expr> tags, using balanced-brace
|
|
157
|
+
// matching so expressions like {fn({key: val})} work correctly.
|
|
60
158
|
function extractExpressions(text) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
159
|
+
let result = '';
|
|
160
|
+
let i = 0;
|
|
161
|
+
while (i < text.length) {
|
|
162
|
+
if (text[i] === '{') {
|
|
163
|
+
const closeIdx = matchBrace(text, i);
|
|
164
|
+
if (closeIdx === -1) { result += text[i++]; continue; }
|
|
165
|
+
const inner = text.slice(i + 1, closeIdx).trim();
|
|
166
|
+
if (inner.startsWith('#') || inner.startsWith('/') || inner.startsWith(':')) {
|
|
167
|
+
result += text.slice(i, closeIdx + 1); i = closeIdx + 1;
|
|
168
|
+
} else {
|
|
169
|
+
result += `<sola-expr expr="${inner.replace(/"/g, '"')}"></sola-expr>`;
|
|
170
|
+
i = closeIdx + 1;
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
result += text[i++];
|
|
65
174
|
}
|
|
66
|
-
|
|
67
|
-
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Strip TypeScript type annotations so Acorn (plain JS parser) can handle
|
|
180
|
+
// script blocks written in TS. Covers the common patterns: type annotations,
|
|
181
|
+
// interface/type declarations, angle-bracket generics, and `as` casts.
|
|
182
|
+
function stripTypeScript(code) {
|
|
183
|
+
// Remove interface and type alias declarations
|
|
184
|
+
code = code.replace(/^\s*(export\s+)?(interface|type)\s+\w[\s\S]*?(?=\n(?:export|const|let|var|function|class|\/\/|$))/gm, '');
|
|
185
|
+
// Remove inline type annotations: `: Type` before = , ) , ; , , or end of line
|
|
186
|
+
code = code.replace(/:\s*[\w<>\[\]|&{},\s.?]+(?=[=,);{}\n])/g, '');
|
|
187
|
+
// Remove `as Type` casts
|
|
188
|
+
code = code.replace(/\s+as\s+[\w<>\[\]|&{}.?]+/g, '');
|
|
189
|
+
// Remove generic type parameters from function/class declarations
|
|
190
|
+
code = code.replace(/<[^>()=]+>(?=\s*\()/g, '');
|
|
191
|
+
return code;
|
|
68
192
|
}
|
|
69
193
|
|
|
70
194
|
export function compile(source, options = {}) {
|
|
195
|
+
const target = options.target || 'esm'; // 'esm' | 'iife'
|
|
71
196
|
const { script: scriptContent, style: styleContent, template: rawTemplateSource } = extractScriptAndStyle(source);
|
|
72
197
|
const scopeHash = hashString(styleContent || rawTemplateSource);
|
|
73
198
|
const hasStyles = styleContent.trim().length > 0;
|
|
@@ -75,34 +200,10 @@ export function compile(source, options = {}) {
|
|
|
75
200
|
|
|
76
201
|
let rawTemplate = rawTemplateSource;
|
|
77
202
|
|
|
78
|
-
// Pre-process attribute-bound expressions BEFORE extractExpressions
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Event handlers: on:event={expr} → on:event="expr"
|
|
84
|
-
rawTemplate = rawTemplate.replace(/\bon:([\w]+)=\{([^}]+)\}/g, 'on:$1="$2"');
|
|
85
|
-
// Bind: bind:prop={expr} → bind:prop="expr"
|
|
86
|
-
rawTemplate = rawTemplate.replace(/\bbind:([\w]+)=\{([^}]+)\}/g, 'bind:$1="$2"');
|
|
87
|
-
// Quoted string interpolation: attr="text {expr} text" → attr="__soladyn_N__"
|
|
88
|
-
// Run iteratively to handle multiple {expr} per value.
|
|
89
|
-
let _prev;
|
|
90
|
-
do {
|
|
91
|
-
_prev = rawTemplate;
|
|
92
|
-
rawTemplate = rawTemplate.replace(/([\w:-]+)="([^"]*\{[^{}]+\}[^"]*)"/g, (_, attr, val) => {
|
|
93
|
-
const jsExpr = '`' + val.replace(/\{([^{}]+)\}/g, (__, e) => `\${${e.trim()}}`) + '`';
|
|
94
|
-
const i = dynAttrs.length;
|
|
95
|
-
dynAttrs.push(jsExpr);
|
|
96
|
-
return `${attr}="__soladyn_${i}__"`;
|
|
97
|
-
});
|
|
98
|
-
} while (rawTemplate !== _prev);
|
|
99
|
-
// Unquoted attr expressions: attr={expr} → attr="__soladyn_N__"
|
|
100
|
-
rawTemplate = rawTemplate.replace(/([\w:-]+)=\{([^{}]+)\}/g, (_, attr, expr) => {
|
|
101
|
-
const i = dynAttrs.length;
|
|
102
|
-
dynAttrs.push(expr.trim());
|
|
103
|
-
return `${attr}="__soladyn_${i}__"`;
|
|
104
|
-
});
|
|
105
|
-
|
|
203
|
+
// Pre-process attribute-bound expressions BEFORE extractExpressions using
|
|
204
|
+
// balanced-brace matching (handles nested object literals, ternaries, etc.)
|
|
205
|
+
const dynAttrs = [];
|
|
206
|
+
rawTemplate = preprocessTemplate(rawTemplate, dynAttrs);
|
|
106
207
|
rawTemplate = extractExpressions(rawTemplate);
|
|
107
208
|
|
|
108
209
|
// Pre-process logic blocks: structured if-then-else
|
|
@@ -110,8 +211,8 @@ export function compile(source, options = {}) {
|
|
|
110
211
|
rawTemplate = rawTemplate
|
|
111
212
|
.replace(/{#if\s+([\s\S]*?)}([\s\S]*?){:else}([\s\S]*?){\/if}/g, '<sola-if condition="$1"><sola-then>$2</sola-then><sola-else>$3</sola-else></sola-if>')
|
|
112
213
|
.replace(/{#if\s+([\s\S]*?)}([\s\S]*?){\/if}/g, '<sola-if condition="$1"><sola-then>$2</sola-then></sola-if>')
|
|
113
|
-
.replace(/{#each\s+([\s\S]*?)\s+as\s+([\w]+)(?:\s*,\s*([\w]+))?}/g,
|
|
114
|
-
(_, arr, item, index) => `<sola-each array="${arr}" item="${item}"${index ? ` index="${index}"` : ''}>`)
|
|
214
|
+
.replace(/{#each\s+([\s\S]*?)\s+as\s+([\w]+)(?:\s*,\s*([\w]+))?(?:\s*\(([^)]+)\))?}/g,
|
|
215
|
+
(_, arr, item, index, key) => `<sola-each array="${arr}" item="${item}"${index ? ` index="${index}"` : ''}${key ? ` key="${key.trim()}"` : ''}>`)
|
|
115
216
|
.replace(/{\/each}/g, '</sola-each>');
|
|
116
217
|
|
|
117
218
|
// Parse HTML AST
|
|
@@ -143,14 +244,16 @@ export function compile(source, options = {}) {
|
|
|
143
244
|
parser.end();
|
|
144
245
|
|
|
145
246
|
// Parse JS AST with Acorn
|
|
146
|
-
|
|
247
|
+
// Strip TS first — AST positions and all slice() calls operate on the stripped source
|
|
248
|
+
const strippedScript = scriptContent.trim() ? stripTypeScript(scriptContent) : scriptContent;
|
|
249
|
+
let jsOutput = strippedScript;
|
|
147
250
|
const stateVars = new Set();
|
|
148
251
|
const exportedProps = [];
|
|
149
252
|
const componentImports = [];
|
|
150
253
|
|
|
151
|
-
if (
|
|
254
|
+
if (strippedScript.trim()) {
|
|
152
255
|
try {
|
|
153
|
-
const ast = acorn.parse(
|
|
256
|
+
const ast = acorn.parse(strippedScript, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
154
257
|
const edits = [];
|
|
155
258
|
|
|
156
259
|
function walkAST(node, parent) {
|
|
@@ -177,7 +280,7 @@ export function compile(source, options = {}) {
|
|
|
177
280
|
node.declaration.declarations.forEach(decl => {
|
|
178
281
|
const name = decl.id.name;
|
|
179
282
|
const defaultVal = decl.init
|
|
180
|
-
?
|
|
283
|
+
? strippedScript.slice(decl.init.start, decl.init.end)
|
|
181
284
|
: 'undefined';
|
|
182
285
|
exportedProps.push({ name, default: defaultVal });
|
|
183
286
|
edits.push({
|
|
@@ -197,7 +300,7 @@ export function compile(source, options = {}) {
|
|
|
197
300
|
const varName = decl.id.name;
|
|
198
301
|
stateVars.add(varName);
|
|
199
302
|
const initArg = decl.init.arguments[0];
|
|
200
|
-
const initVal = initArg ?
|
|
303
|
+
const initVal = initArg ? strippedScript.slice(initArg.start, initArg.end) : 'undefined';
|
|
201
304
|
edits.push({
|
|
202
305
|
start: node.start,
|
|
203
306
|
end: node.end,
|
|
@@ -206,7 +309,7 @@ export function compile(source, options = {}) {
|
|
|
206
309
|
} else if (callee.type === 'Identifier' && callee.name === '$derived') {
|
|
207
310
|
const varName = decl.id.name;
|
|
208
311
|
const expr = decl.init.arguments[0];
|
|
209
|
-
const exprSrc = expr ?
|
|
312
|
+
const exprSrc = expr ? strippedScript.slice(expr.start, expr.end) : 'undefined';
|
|
210
313
|
const isFn = expr && (expr.type === 'ArrowFunctionExpression' || expr.type === 'FunctionExpression');
|
|
211
314
|
edits.push({
|
|
212
315
|
start: node.start,
|
|
@@ -217,7 +320,7 @@ export function compile(source, options = {}) {
|
|
|
217
320
|
});
|
|
218
321
|
} else if (callee.type === 'Identifier' && callee.name === '$effect') {
|
|
219
322
|
const body = decl.init.arguments[0];
|
|
220
|
-
const bodySrc = body ?
|
|
323
|
+
const bodySrc = body ? strippedScript.slice(body.start, body.end) : '() => {}';
|
|
221
324
|
edits.push({
|
|
222
325
|
start: node.start,
|
|
223
326
|
end: node.end,
|
|
@@ -225,10 +328,10 @@ export function compile(source, options = {}) {
|
|
|
225
328
|
});
|
|
226
329
|
} else if (callee.type === 'Identifier' && callee.name === '$intent') {
|
|
227
330
|
const promptArg = decl.init.arguments[0]
|
|
228
|
-
?
|
|
331
|
+
? strippedScript.slice(decl.init.arguments[0].start, decl.init.arguments[0].end)
|
|
229
332
|
: "''";
|
|
230
333
|
const fallbackArg = decl.init.arguments[1]
|
|
231
|
-
?
|
|
334
|
+
? strippedScript.slice(decl.init.arguments[1].start, decl.init.arguments[1].end)
|
|
232
335
|
: 'null';
|
|
233
336
|
edits.push({
|
|
234
337
|
start: node.start,
|
|
@@ -237,10 +340,10 @@ export function compile(source, options = {}) {
|
|
|
237
340
|
});
|
|
238
341
|
} else if (callee.type === 'Identifier' && callee.name === '$data') {
|
|
239
342
|
const sourceArg = decl.init.arguments[0]
|
|
240
|
-
?
|
|
343
|
+
? strippedScript.slice(decl.init.arguments[0].start, decl.init.arguments[0].end)
|
|
241
344
|
: "''";
|
|
242
345
|
const optsArg = decl.init.arguments[1]
|
|
243
|
-
? ', ' +
|
|
346
|
+
? ', ' + strippedScript.slice(decl.init.arguments[1].start, decl.init.arguments[1].end)
|
|
244
347
|
: '';
|
|
245
348
|
edits.push({
|
|
246
349
|
start: node.start,
|
|
@@ -257,7 +360,7 @@ export function compile(source, options = {}) {
|
|
|
257
360
|
node.left.type === 'Identifier' &&
|
|
258
361
|
stateVars.has(node.left.name)) {
|
|
259
362
|
const name = node.left.name;
|
|
260
|
-
const rightSource =
|
|
363
|
+
const rightSource = strippedScript.slice(node.right.start, node.right.end);
|
|
261
364
|
if (node.operator === '=') {
|
|
262
365
|
edits.push({ start: node.start, end: node.end, replacement: `set_${name}(${rightSource})` });
|
|
263
366
|
} else {
|
|
@@ -297,7 +400,9 @@ export function compile(source, options = {}) {
|
|
|
297
400
|
jsOutput = jsOutput.slice(0, edit.start) + edit.replacement + jsOutput.slice(edit.end);
|
|
298
401
|
}
|
|
299
402
|
} catch (err) {
|
|
300
|
-
|
|
403
|
+
const loc = err.loc ? ` (line ${err.loc.line}, col ${err.loc.column})` : '';
|
|
404
|
+
const filePath = typeof options === 'string' ? options : (options.filename || '<script>');
|
|
405
|
+
throw new Error(`[sola compiler] Parse error in ${filePath}${loc}:\n ${err.message}`);
|
|
301
406
|
}
|
|
302
407
|
}
|
|
303
408
|
|
|
@@ -373,26 +478,53 @@ export function compile(source, options = {}) {
|
|
|
373
478
|
return;
|
|
374
479
|
}
|
|
375
480
|
|
|
376
|
-
// {#each}
|
|
481
|
+
// {#each} with optional keyed diffing: {#each items as item (item.id)}
|
|
377
482
|
if (node.name === 'sola-each') {
|
|
378
483
|
const eid = `e${uid++}`;
|
|
484
|
+
const keyExpr = node.attribs.key || null;
|
|
379
485
|
domCode += ` const ${eid}_a = document.createComment('each');\n`;
|
|
380
486
|
domCode += ` ${parentVar}.appendChild(${eid}_a);\n`;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
domCode += `
|
|
487
|
+
if (keyExpr) {
|
|
488
|
+
// Keyed: maintain a Map<key, Node[]> and reconcile on each run
|
|
489
|
+
domCode += ` let ${eid}_keyMap = new Map();\n`;
|
|
490
|
+
domCode += ` createEffect(() => {\n`;
|
|
491
|
+
domCode += ` const _items = ${node.attribs.array} || [];\n`;
|
|
492
|
+
domCode += ` const _nextKeys = _items.map((${node.attribs.item}, _i) => String(${keyExpr}));\n`;
|
|
493
|
+
domCode += ` // Remove stale keys\n`;
|
|
494
|
+
domCode += ` for (const [_k, _nodes] of ${eid}_keyMap) {\n`;
|
|
495
|
+
domCode += ` if (!_nextKeys.includes(_k)) { _nodes.forEach(n => n.remove()); ${eid}_keyMap.delete(_k); }\n`;
|
|
496
|
+
domCode += ` }\n`;
|
|
497
|
+
domCode += ` // Insert/reorder items\n`;
|
|
498
|
+
domCode += ` let _anchor = ${eid}_a.nextSibling;\n`;
|
|
499
|
+
domCode += ` for (let _i = 0; _i < _items.length; _i++) {\n`;
|
|
500
|
+
domCode += ` const ${node.attribs.item} = _items[_i];\n`;
|
|
501
|
+
if (node.attribs.index) domCode += ` const ${node.attribs.index} = _i;\n`;
|
|
502
|
+
domCode += ` const _key = String(${keyExpr});\n`;
|
|
503
|
+
domCode += ` if (!${eid}_keyMap.has(_key)) {\n`;
|
|
504
|
+
domCode += ` const f = document.createDocumentFragment();\n`;
|
|
505
|
+
node.children.forEach(child => emitNode(child, 'f'));
|
|
506
|
+
domCode += ` const _nodes = Array.from(f.childNodes);\n`;
|
|
507
|
+
domCode += ` ${eid}_keyMap.set(_key, _nodes);\n`;
|
|
508
|
+
domCode += ` ${eid}_a.parentNode.insertBefore(f, _anchor);\n`;
|
|
509
|
+
domCode += ` }\n`;
|
|
510
|
+
domCode += ` }\n`;
|
|
511
|
+
domCode += ` });\n`;
|
|
512
|
+
} else {
|
|
513
|
+
// Unkeyed: full teardown + rebuild
|
|
514
|
+
domCode += ` let ${eid}_els = [];\n`;
|
|
515
|
+
domCode += ` createEffect(() => {\n`;
|
|
516
|
+
domCode += ` ${eid}_els.forEach(e => e.remove()); ${eid}_els = [];\n`;
|
|
517
|
+
domCode += ` const _items = ${node.attribs.array} || [];\n`;
|
|
518
|
+
domCode += ` const f = document.createDocumentFragment();\n`;
|
|
519
|
+
domCode += ` for (let _i = 0; _i < _items.length; _i++) {\n`;
|
|
520
|
+
domCode += ` const ${node.attribs.item} = _items[_i];\n`;
|
|
521
|
+
if (node.attribs.index) domCode += ` const ${node.attribs.index} = _i;\n`;
|
|
522
|
+
node.children.forEach(child => emitNode(child, 'f'));
|
|
523
|
+
domCode += ` }\n`;
|
|
524
|
+
domCode += ` Array.from(f.childNodes).forEach(n => ${eid}_els.push(n));\n`;
|
|
525
|
+
domCode += ` ${eid}_a.parentNode.insertBefore(f, ${eid}_a.nextSibling);\n`;
|
|
526
|
+
domCode += ` });\n`;
|
|
390
527
|
}
|
|
391
|
-
node.children.forEach(child => emitNode(child, 'f'));
|
|
392
|
-
domCode += ` }\n`;
|
|
393
|
-
domCode += ` Array.from(f.childNodes).forEach(n => ${eid}_els.push(n));\n`;
|
|
394
|
-
domCode += ` ${eid}_a.parentNode.insertBefore(f, ${eid}_a.nextSibling);\n`;
|
|
395
|
-
domCode += ` });\n`;
|
|
396
528
|
return;
|
|
397
529
|
}
|
|
398
530
|
|
|
@@ -454,16 +586,27 @@ export function compile(source, options = {}) {
|
|
|
454
586
|
root.children.forEach(child => emitNode(child, '__target'));
|
|
455
587
|
|
|
456
588
|
// Assemble final output module
|
|
589
|
+
const CORE_EXPORTS = 'createSignal, createDerived, createEffect, createIntent, createData, onMount, onDestroy, pushContext, popContext, __flush_mounts, __flush_destroys';
|
|
457
590
|
let output = '';
|
|
458
|
-
output += `// Compiled by @sola-air-ui/compiler v1.0.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
output += `
|
|
591
|
+
output += `// Compiled by @sola-air-ui/compiler v1.0.3\n`;
|
|
592
|
+
|
|
593
|
+
if (target === 'iife') {
|
|
594
|
+
// ServiceNow / no-bundler mode: pull from window.SolaCore global
|
|
595
|
+
output += `(function() {\n`;
|
|
596
|
+
output += `const { ${CORE_EXPORTS} } = window.SolaCore;\n`;
|
|
597
|
+
for (const imp of componentImports) {
|
|
598
|
+
output += `// Note: sub-component ${imp.localName} must be pre-loaded before this script\n`;
|
|
599
|
+
}
|
|
600
|
+
output += `\n`;
|
|
601
|
+
output += `window['${options.exportName || 'SolaComponent'}'] = function mount(__target, props = {}) {\n`;
|
|
602
|
+
} else {
|
|
603
|
+
output += `import { ${CORE_EXPORTS} } from '@sola-air-ui/core';\n`;
|
|
604
|
+
for (const imp of componentImports) {
|
|
605
|
+
output += `import ${imp.localName} from '${imp.path}';\n`;
|
|
606
|
+
}
|
|
607
|
+
output += `\n`;
|
|
608
|
+
output += `export default function mount(__target, props = {}) {\n`;
|
|
463
609
|
}
|
|
464
|
-
|
|
465
|
-
output += `\n`;
|
|
466
|
-
output += `export default function mount(__target, props = {}) {\n`;
|
|
467
610
|
output += ` const __ctx = pushContext();\n\n`;
|
|
468
611
|
|
|
469
612
|
if (hasStyles) {
|
|
@@ -487,12 +630,14 @@ export function compile(source, options = {}) {
|
|
|
487
630
|
|
|
488
631
|
output += `\n __flush_mounts();\n`;
|
|
489
632
|
output += `\n return () => {\n`;
|
|
490
|
-
output += ` // Cleanup\n`;
|
|
491
633
|
output += ` __flush_destroys();\n`;
|
|
492
634
|
output += ` popContext(__ctx);\n`;
|
|
493
635
|
output += ` __target.innerHTML = '';\n`;
|
|
494
636
|
output += ` };\n`;
|
|
495
|
-
output += `}
|
|
637
|
+
output += `};\n`;
|
|
638
|
+
if (target === 'iife') {
|
|
639
|
+
output += `})();\n`;
|
|
640
|
+
}
|
|
496
641
|
|
|
497
642
|
return {
|
|
498
643
|
code: output,
|