@sola-air-ui/compiler 1.2.0 → 1.3.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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/index.js +166 -81
- package/types/index.d.ts +1 -1
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
|
18
18
|
--target <esm|iife> Output format (default: esm)
|
|
19
19
|
esm — ES module with import/export (default, for bundlers)
|
|
20
20
|
iife — Self-contained IIFE for no-bundler environments
|
|
21
|
-
(
|
|
21
|
+
(enterprise portal widgets, plain <script> tags)
|
|
22
22
|
--export-name <Name> Global name for IIFE target (default: SolaComponent)
|
|
23
23
|
The compiled component mounts as window['Name'](el, props)
|
|
24
24
|
-v, --version Print compiler version
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,14 @@ const { version: COMPILER_VERSION } = JSON.parse(
|
|
|
6
6
|
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
|
|
7
7
|
);
|
|
8
8
|
|
|
9
|
+
// Names every compiled component imports from core under their own name. A
|
|
10
|
+
// script that imports one of these again is not given a second binding.
|
|
11
|
+
const CORE_PROVIDED = new Set([
|
|
12
|
+
'createSignal', 'createDerived', 'createEffect', 'createIntent', 'createData',
|
|
13
|
+
'createPersistentSignal', 'onMount', 'onDestroy', 'pushContext', 'popContext',
|
|
14
|
+
'__flush_mounts', '__flush_destroys', '__untrack'
|
|
15
|
+
]);
|
|
16
|
+
|
|
9
17
|
const VOID_ELEMENTS = new Set([
|
|
10
18
|
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
11
19
|
'link', 'meta', 'param', 'source', 'track', 'wbr'
|
|
@@ -325,7 +333,7 @@ function preprocessTemplate(template, dynAttrs, handlers) {
|
|
|
325
333
|
} else if (hasDyn) {
|
|
326
334
|
// Build a string-concatenation expression instead of a runtime backtick
|
|
327
335
|
// template literal. Confirmed by driving a real browser against a live
|
|
328
|
-
//
|
|
336
|
+
// enterprise portal instance: its widget-script delivery pipeline
|
|
329
337
|
// silently mangles `${expr}` interpolation inside a template literal down
|
|
330
338
|
// to bare, unevaluated expression text (e.g. `background: ${x}` becomes
|
|
331
339
|
// the literal string "background: x" — the function reference itself,
|
|
@@ -889,6 +897,12 @@ export function compile(source, options = {}) {
|
|
|
889
897
|
const stateVars = new Set();
|
|
890
898
|
const exportedProps = [];
|
|
891
899
|
const componentImports = [];
|
|
900
|
+
// Every other import in the script. They used to be deleted, so a package
|
|
901
|
+
// import (`import DataCard from '@sola-air-ui/ui/data-card'`, or anything from
|
|
902
|
+
// '@sola-air-ui/core' that the compiler does not already provide) was gone from
|
|
903
|
+
// the output and left an undefined name behind.
|
|
904
|
+
const otherImports = [];
|
|
905
|
+
const coreImportExtras = [];
|
|
892
906
|
|
|
893
907
|
let reactiveVars = NO_REACTIVE_VARS;
|
|
894
908
|
if (strippedScript.trim()) {
|
|
@@ -914,6 +928,21 @@ export function compile(source, options = {}) {
|
|
|
914
928
|
node.specifiers.forEach(spec => {
|
|
915
929
|
componentImports.push({ localName: spec.local.name, path: src });
|
|
916
930
|
});
|
|
931
|
+
} else if (src === '@sola-air-ui/core' || src === '@sola/core') {
|
|
932
|
+
// The compiler already imports these names under their own names.
|
|
933
|
+
// Importing one again would be a duplicate binding, so keep only the
|
|
934
|
+
// rest: names it does not provide, and aliased ones.
|
|
935
|
+
node.specifiers.forEach(spec => {
|
|
936
|
+
if (spec.type !== 'ImportSpecifier') return;
|
|
937
|
+
const imported = spec.imported.name ?? spec.imported.value;
|
|
938
|
+
if (spec.local.name === imported && CORE_PROVIDED.has(imported)) return;
|
|
939
|
+
coreImportExtras.push({ imported, local: spec.local.name });
|
|
940
|
+
});
|
|
941
|
+
} else {
|
|
942
|
+
otherImports.push({
|
|
943
|
+
code: strippedScript.slice(node.start, node.end).trim().replace(/;?$/, ';'),
|
|
944
|
+
locals: node.specifiers.map(spec => spec.local.name)
|
|
945
|
+
});
|
|
917
946
|
}
|
|
918
947
|
edits.push({
|
|
919
948
|
start: node.start,
|
|
@@ -947,6 +976,14 @@ export function compile(source, options = {}) {
|
|
|
947
976
|
edits.push({ start: call.end, end: call.end, replacement: ')' });
|
|
948
977
|
}
|
|
949
978
|
|
|
979
|
+
// $effect(fn) as a bare statement: the form the docs show. It compiles to
|
|
980
|
+
// createEffect(fn); the declaration form (const stop = $effect(fn)) is below.
|
|
981
|
+
if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' &&
|
|
982
|
+
node.expression.callee.type === 'Identifier' && node.expression.callee.name === '$effect') {
|
|
983
|
+
const callee = node.expression.callee;
|
|
984
|
+
edits.push({ start: callee.start, end: callee.end, replacement: 'createEffect' });
|
|
985
|
+
}
|
|
986
|
+
|
|
950
987
|
// $state()/$derived()/$effect()/$intent()/$data() macro declarations.
|
|
951
988
|
// Bracket edits (head + tail) leave the argument expression in place so
|
|
952
989
|
// the reactive rewrite pass below can still auto-call signals inside it.
|
|
@@ -1050,6 +1087,11 @@ export function compile(source, options = {}) {
|
|
|
1050
1087
|
let uid = 0;
|
|
1051
1088
|
let domCode = '';
|
|
1052
1089
|
const importedComponentSet = new Set(componentImports.map(i => i.localName));
|
|
1090
|
+
// A capitalised tag that matches a name imported from a package is a component
|
|
1091
|
+
// too (`import DataCard from '@sola-air-ui/ui/data-card'`), not only a `.sola` path.
|
|
1092
|
+
for (const imp of otherImports) {
|
|
1093
|
+
for (const name of imp.locals) if (/^[A-Z]/.test(name)) importedComponentSet.add(name);
|
|
1094
|
+
}
|
|
1053
1095
|
|
|
1054
1096
|
const sourceFile = options.filename || '<component>';
|
|
1055
1097
|
|
|
@@ -1130,25 +1172,43 @@ export function compile(source, options = {}) {
|
|
|
1130
1172
|
domCode += ` ${parentVar}.appendChild(${cid}_target);\n`;
|
|
1131
1173
|
|
|
1132
1174
|
// Dynamically-bound props (rows={items}) arrive here as __soladyn_N__ placeholders —
|
|
1133
|
-
// resolve them back to the real expression from dynAttrs
|
|
1134
|
-
// (unquoted) property so the child receives live data, not the placeholder text.
|
|
1175
|
+
// resolve them back to the real expression from dynAttrs.
|
|
1135
1176
|
//
|
|
1136
1177
|
// `onXxx`-named props (e.g. onChange={handler}) arrive as a
|
|
1137
1178
|
// __solahandler_N__ marker, because preprocessTemplate cannot tell a
|
|
1138
1179
|
// component apart from a native element and lifts both the same way.
|
|
1139
1180
|
// Either way the expression is passed through as a live function.
|
|
1181
|
+
//
|
|
1182
|
+
// Three kinds of dynamic prop:
|
|
1183
|
+
// - a bare name (rows={items}) is passed as-is. For a $state that is the
|
|
1184
|
+
// signal accessor, which is how a child receives live data (the shipped
|
|
1185
|
+
// components accept either an accessor or a plain value);
|
|
1186
|
+
// - an expression that reads a signal (title={ctx.title}) is evaluated
|
|
1187
|
+
// with the signal reads rewritten, and the child is re-mounted when
|
|
1188
|
+
// the value changes;
|
|
1189
|
+
// - an expression that reads no signal (text={msg.text} in an {#each})
|
|
1190
|
+
// is evaluated once, when the child is mounted.
|
|
1140
1191
|
const propEntries = [];
|
|
1192
|
+
let reactiveProps = false;
|
|
1141
1193
|
for (const [key, val] of Object.entries(node.attribs || {})) {
|
|
1142
1194
|
const dynMatch = /^__soladyn_(\d+)__$/.exec(val);
|
|
1143
1195
|
const handlerMatch = /^__solahandler_(\d+)__$/.exec(val);
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1196
|
+
let propValue;
|
|
1197
|
+
if (dynMatch) {
|
|
1198
|
+
const raw = dynAttrs[parseInt(dynMatch[1], 10)];
|
|
1199
|
+
if (/^\s*[A-Za-z_$][\w$]*\s*$/.test(raw)) {
|
|
1200
|
+
propValue = `(${raw})`;
|
|
1201
|
+
} else {
|
|
1202
|
+
checkExpression(raw, 'component prop');
|
|
1203
|
+
const rewritten = rewrite(raw);
|
|
1204
|
+
if (rewritten !== raw) reactiveProps = true;
|
|
1205
|
+
propValue = `(${rewritten})`;
|
|
1206
|
+
}
|
|
1207
|
+
} else if (handlerMatch) {
|
|
1208
|
+
propValue = `(${rewrite(handlers[parseInt(handlerMatch[1], 10)])})`;
|
|
1209
|
+
} else {
|
|
1210
|
+
propValue = JSON.stringify(val);
|
|
1211
|
+
}
|
|
1152
1212
|
propEntries.push(`${JSON.stringify(key)}: ${propValue}`);
|
|
1153
1213
|
}
|
|
1154
1214
|
|
|
@@ -1162,7 +1222,21 @@ export function compile(source, options = {}) {
|
|
|
1162
1222
|
propEntries.push(`children: ${slotVar}`);
|
|
1163
1223
|
}
|
|
1164
1224
|
|
|
1165
|
-
|
|
1225
|
+
// The child mounts untracked, so nothing it reads while building itself
|
|
1226
|
+
// can re-run the parent's effect. Its teardown is kept and registered with
|
|
1227
|
+
// whatever scope is being built, so it runs when that scope is destroyed.
|
|
1228
|
+
domCode += ` let ${cid}_unmount = null;\n`;
|
|
1229
|
+
domCode += ` onDestroy(() => { if (typeof ${cid}_unmount === 'function') ${cid}_unmount(); ${cid}_unmount = null; });\n`;
|
|
1230
|
+
if (reactiveProps) {
|
|
1231
|
+
domCode += ` createEffect(() => {\n`;
|
|
1232
|
+
domCode += ` const ${cid}_props = { ${propEntries.join(', ')} };\n`;
|
|
1233
|
+
domCode += ` if (typeof ${cid}_unmount === 'function') __untrack(${cid}_unmount);\n`;
|
|
1234
|
+
domCode += ` ${cid}_target.textContent = '';\n`;
|
|
1235
|
+
domCode += ` ${cid}_unmount = __untrack(() => ${node.name}(${cid}_target, ${cid}_props));\n`;
|
|
1236
|
+
domCode += ` });\n`;
|
|
1237
|
+
} else {
|
|
1238
|
+
domCode += ` ${cid}_unmount = __untrack(() => ${node.name}(${cid}_target, { ${propEntries.join(', ')} }));\n`;
|
|
1239
|
+
}
|
|
1166
1240
|
return;
|
|
1167
1241
|
}
|
|
1168
1242
|
|
|
@@ -1173,37 +1247,50 @@ export function compile(source, options = {}) {
|
|
|
1173
1247
|
}
|
|
1174
1248
|
|
|
1175
1249
|
// {#if ...} {:else} ... {/if}
|
|
1250
|
+
//
|
|
1251
|
+
// Each time a branch is built it gets its own scope (pushContext). Effects,
|
|
1252
|
+
// onDestroy callbacks and child components created inside register with that
|
|
1253
|
+
// scope, and switching or removing the branch destroys it, so nothing built
|
|
1254
|
+
// for a branch that is no longer on the page keeps running.
|
|
1176
1255
|
if (node.name === 'sola-if') {
|
|
1177
1256
|
const cid = `c${uid++}`;
|
|
1178
1257
|
domCode += ` const ${cid}_a = document.createComment('if');\n`;
|
|
1179
1258
|
domCode += ` ${parentVar}.appendChild(${cid}_a);\n`;
|
|
1180
1259
|
domCode += ` let ${cid}_els = [];\n`;
|
|
1181
|
-
|
|
1260
|
+
domCode += ` let ${cid}_ctx = null;\n`;
|
|
1261
|
+
domCode += ` onDestroy(() => { if (${cid}_ctx) { __flush_destroys(${cid}_ctx); ${cid}_ctx = null; } });\n`;
|
|
1262
|
+
|
|
1182
1263
|
const thenChild = node.children.find(c => c.name === 'sola-then');
|
|
1183
1264
|
const elseChild = node.children.find(c => c.name === 'sola-else');
|
|
1184
1265
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1266
|
+
const emitBranch = (branch) => {
|
|
1267
|
+
if (!branch) return;
|
|
1268
|
+
domCode += ` ${cid}_ctx = pushContext();\n`;
|
|
1269
|
+
domCode += ` try {\n`;
|
|
1189
1270
|
domCode += ` const f = document.createDocumentFragment();\n`;
|
|
1190
|
-
|
|
1271
|
+
branch.children.forEach(child => emitNode(child, 'f', locals));
|
|
1191
1272
|
domCode += ` Array.from(f.childNodes).forEach(n => ${cid}_els.push(n));\n`;
|
|
1192
1273
|
domCode += ` ${cid}_a.parentNode.insertBefore(f, ${cid}_a.nextSibling);\n`;
|
|
1193
|
-
}
|
|
1274
|
+
domCode += ` } finally { popContext(${cid}_ctx); }\n`;
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
domCode += ` createEffect(() => {\n`;
|
|
1278
|
+
domCode += ` if (${cid}_ctx) { __flush_destroys(${cid}_ctx); ${cid}_ctx = null; }\n`;
|
|
1279
|
+
domCode += ` ${cid}_els.forEach(e => e.remove()); ${cid}_els = [];\n`;
|
|
1280
|
+
domCode += ` if (${rewrite(node.attribs.condition)}) {\n`;
|
|
1281
|
+
emitBranch(thenChild);
|
|
1194
1282
|
domCode += ` } else {\n`;
|
|
1195
|
-
|
|
1196
|
-
domCode += ` const f = document.createDocumentFragment();\n`;
|
|
1197
|
-
elseChild.children.forEach(child => emitNode(child, 'f', locals));
|
|
1198
|
-
domCode += ` Array.from(f.childNodes).forEach(n => ${cid}_els.push(n));\n`;
|
|
1199
|
-
domCode += ` ${cid}_a.parentNode.insertBefore(f, ${cid}_a.nextSibling);\n`;
|
|
1200
|
-
}
|
|
1283
|
+
emitBranch(elseChild);
|
|
1201
1284
|
domCode += ` }\n`;
|
|
1202
1285
|
domCode += ` });\n`;
|
|
1203
1286
|
return;
|
|
1204
1287
|
}
|
|
1205
1288
|
|
|
1206
1289
|
// {#each} with optional keyed diffing: {#each items as item (item.id)}
|
|
1290
|
+
//
|
|
1291
|
+
// Like {#if}, what is built for the list gets its own scope so it can be
|
|
1292
|
+
// destroyed: the whole list for an unkeyed each, one scope per key for a
|
|
1293
|
+
// keyed one (so removing an item stops that item's effects and children).
|
|
1207
1294
|
if (node.name === 'sola-each') {
|
|
1208
1295
|
const eid = `e${uid++}`;
|
|
1209
1296
|
// Item/index names shadow signals inside the loop body and key expression
|
|
@@ -1217,70 +1304,53 @@ export function compile(source, options = {}) {
|
|
|
1217
1304
|
domCode += ` const ${eid}_a = document.createComment('each');\n`;
|
|
1218
1305
|
domCode += ` ${parentVar}.appendChild(${eid}_a);\n`;
|
|
1219
1306
|
if (keyExpr) {
|
|
1220
|
-
// Keyed: maintain a Map<key,
|
|
1307
|
+
// Keyed: maintain a Map<key, { nodes, ctx }> and reconcile on each run
|
|
1221
1308
|
// Reconcile by walking the new order and moving each key's nodes into
|
|
1222
1309
|
// place against a cursor. The previous version only ever inserted, and
|
|
1223
1310
|
// against an anchor it never advanced — so reordering a keyed list (a
|
|
1224
1311
|
// sort, a drag, a reverse) left the DOM in its original order, silently.
|
|
1225
|
-
domCode += ` let ${eid}_keyMap = new Map()
|
|
1226
|
-
`;
|
|
1227
|
-
domCode += ` createEffect(() => {
|
|
1228
|
-
`;
|
|
1229
|
-
domCode += ` const
|
|
1230
|
-
`;
|
|
1231
|
-
domCode += `
|
|
1232
|
-
`;
|
|
1233
|
-
domCode += `
|
|
1234
|
-
`;
|
|
1235
|
-
domCode += `
|
|
1236
|
-
`;
|
|
1237
|
-
domCode += `
|
|
1238
|
-
`;
|
|
1239
|
-
domCode += `
|
|
1240
|
-
`;
|
|
1241
|
-
|
|
1242
|
-
`;
|
|
1243
|
-
domCode += ` const _key = String(${keyExpr});
|
|
1244
|
-
`;
|
|
1245
|
-
domCode += ` _seen.add(_key);
|
|
1246
|
-
`;
|
|
1247
|
-
domCode += ` let _nodes = ${eid}_keyMap.get(_key);
|
|
1248
|
-
`;
|
|
1249
|
-
domCode += ` if (!_nodes) {
|
|
1250
|
-
`;
|
|
1251
|
-
domCode += ` const f = document.createDocumentFragment();
|
|
1252
|
-
`;
|
|
1312
|
+
domCode += ` let ${eid}_keyMap = new Map();\n`;
|
|
1313
|
+
domCode += ` onDestroy(() => { for (const _e of ${eid}_keyMap.values()) __flush_destroys(_e.ctx); ${eid}_keyMap.clear(); });\n`;
|
|
1314
|
+
domCode += ` createEffect(() => {\n`;
|
|
1315
|
+
domCode += ` const _items = ${arrayExpr} || [];\n`;
|
|
1316
|
+
domCode += ` const _parent = ${eid}_a.parentNode;\n`;
|
|
1317
|
+
domCode += ` const _seen = new Set();\n`;
|
|
1318
|
+
domCode += ` let _cursor = ${eid}_a.nextSibling;\n`;
|
|
1319
|
+
domCode += ` for (let _i = 0; _i < _items.length; _i++) {\n`;
|
|
1320
|
+
domCode += ` const ${node.attribs.item} = _items[_i];\n`;
|
|
1321
|
+
if (node.attribs.index) domCode += ` const ${node.attribs.index} = _i;\n`;
|
|
1322
|
+
domCode += ` const _key = String(${keyExpr});\n`;
|
|
1323
|
+
domCode += ` _seen.add(_key);\n`;
|
|
1324
|
+
domCode += ` let _entry = ${eid}_keyMap.get(_key);\n`;
|
|
1325
|
+
domCode += ` if (!_entry) {\n`;
|
|
1326
|
+
domCode += ` const _kctx = pushContext();\n`;
|
|
1327
|
+
domCode += ` try {\n`;
|
|
1328
|
+
domCode += ` const f = document.createDocumentFragment();\n`;
|
|
1253
1329
|
node.children.forEach(child => emitNode(child, 'f', eachLocals));
|
|
1254
|
-
domCode += `
|
|
1255
|
-
`;
|
|
1256
|
-
domCode += ` ${eid}_keyMap.set(_key,
|
|
1257
|
-
`;
|
|
1258
|
-
domCode += `
|
|
1259
|
-
`;
|
|
1260
|
-
domCode += `
|
|
1261
|
-
`;
|
|
1262
|
-
domCode += `
|
|
1263
|
-
`;
|
|
1264
|
-
domCode += `
|
|
1265
|
-
`;
|
|
1266
|
-
domCode += `
|
|
1267
|
-
`;
|
|
1268
|
-
domCode += ` }
|
|
1269
|
-
`;
|
|
1270
|
-
domCode += ` for (const [_k, _stale] of ${eid}_keyMap) {
|
|
1271
|
-
`;
|
|
1272
|
-
domCode += ` if (!_seen.has(_k)) { _stale.forEach(n => n.remove()); ${eid}_keyMap.delete(_k); }
|
|
1273
|
-
`;
|
|
1274
|
-
domCode += ` }
|
|
1275
|
-
`;
|
|
1276
|
-
domCode += ` });
|
|
1277
|
-
`;
|
|
1330
|
+
domCode += ` _entry = { nodes: Array.from(f.childNodes), ctx: _kctx };\n`;
|
|
1331
|
+
domCode += ` } finally { popContext(_kctx); }\n`;
|
|
1332
|
+
domCode += ` ${eid}_keyMap.set(_key, _entry);\n`;
|
|
1333
|
+
domCode += ` }\n`;
|
|
1334
|
+
domCode += ` for (const _n of _entry.nodes) {\n`;
|
|
1335
|
+
domCode += ` if (_n === _cursor) { _cursor = _cursor.nextSibling; }\n`;
|
|
1336
|
+
domCode += ` else { _parent.insertBefore(_n, _cursor); }\n`;
|
|
1337
|
+
domCode += ` }\n`;
|
|
1338
|
+
domCode += ` }\n`;
|
|
1339
|
+
domCode += ` for (const [_k, _stale] of ${eid}_keyMap) {\n`;
|
|
1340
|
+
domCode += ` if (!_seen.has(_k)) { __flush_destroys(_stale.ctx); _stale.nodes.forEach(n => n.remove()); ${eid}_keyMap.delete(_k); }\n`;
|
|
1341
|
+
domCode += ` }\n`;
|
|
1342
|
+
domCode += ` });\n`;
|
|
1278
1343
|
} else {
|
|
1279
1344
|
// Unkeyed: full teardown + rebuild
|
|
1280
1345
|
domCode += ` let ${eid}_els = [];\n`;
|
|
1346
|
+
domCode += ` let ${eid}_ctx = null;\n`;
|
|
1347
|
+
domCode += ` onDestroy(() => { if (${eid}_ctx) { __flush_destroys(${eid}_ctx); ${eid}_ctx = null; } });\n`;
|
|
1281
1348
|
domCode += ` createEffect(() => {\n`;
|
|
1349
|
+
domCode += ` if (${eid}_ctx) { __flush_destroys(${eid}_ctx); ${eid}_ctx = null; }\n`;
|
|
1282
1350
|
domCode += ` ${eid}_els.forEach(e => e.remove()); ${eid}_els = [];\n`;
|
|
1283
1351
|
domCode += ` const _items = ${arrayExpr} || [];\n`;
|
|
1352
|
+
domCode += ` ${eid}_ctx = pushContext();\n`;
|
|
1353
|
+
domCode += ` try {\n`;
|
|
1284
1354
|
domCode += ` const f = document.createDocumentFragment();\n`;
|
|
1285
1355
|
domCode += ` for (let _i = 0; _i < _items.length; _i++) {\n`;
|
|
1286
1356
|
domCode += ` const ${node.attribs.item} = _items[_i];\n`;
|
|
@@ -1289,6 +1359,7 @@ export function compile(source, options = {}) {
|
|
|
1289
1359
|
domCode += ` }\n`;
|
|
1290
1360
|
domCode += ` Array.from(f.childNodes).forEach(n => ${eid}_els.push(n));\n`;
|
|
1291
1361
|
domCode += ` ${eid}_a.parentNode.insertBefore(f, ${eid}_a.nextSibling);\n`;
|
|
1362
|
+
domCode += ` } finally { popContext(${eid}_ctx); }\n`;
|
|
1292
1363
|
domCode += ` });\n`;
|
|
1293
1364
|
}
|
|
1294
1365
|
return;
|
|
@@ -1356,7 +1427,7 @@ export function compile(source, options = {}) {
|
|
|
1356
1427
|
root.children.forEach(child => emitNode(child, '__target'));
|
|
1357
1428
|
|
|
1358
1429
|
// Assemble final output module
|
|
1359
|
-
const CORE_EXPORTS = '
|
|
1430
|
+
const CORE_EXPORTS = [...CORE_PROVIDED].join(', ');
|
|
1360
1431
|
// The default bus is imported under an alias so a user variable named `bus`
|
|
1361
1432
|
// cannot shadow what $on / $lastEvent compile to.
|
|
1362
1433
|
const ESM_CORE_IMPORTS = CORE_EXPORTS + ', bus as __solaBus';
|
|
@@ -1365,19 +1436,30 @@ export function compile(source, options = {}) {
|
|
|
1365
1436
|
output += `// Compiled by @sola-air-ui/compiler v${COMPILER_VERSION}\n`;
|
|
1366
1437
|
|
|
1367
1438
|
if (target === 'iife') {
|
|
1368
|
-
//
|
|
1439
|
+
// No-bundler mode: pull from window.SolaCore global
|
|
1369
1440
|
output += `(function() {\n`;
|
|
1370
|
-
|
|
1441
|
+
const iifeExtras = coreImportExtras.map(e => (e.local === e.imported ? e.imported : `${e.imported}: ${e.local}`));
|
|
1442
|
+
output += `const { ${[IIFE_CORE_PULL, ...iifeExtras].join(', ')} } = window.SolaCore;\n`;
|
|
1371
1443
|
for (const imp of componentImports) {
|
|
1372
1444
|
output += `// Note: sub-component ${imp.localName} must be pre-loaded before this script\n`;
|
|
1373
1445
|
}
|
|
1446
|
+
for (const imp of otherImports) {
|
|
1447
|
+
output += `// Note: ${imp.locals.join(', ')} must be loaded before this script (no-bundler output cannot import)\n`;
|
|
1448
|
+
}
|
|
1374
1449
|
output += `\n`;
|
|
1375
1450
|
output += `window['${options.exportName || 'SolaComponent'}'] = function mount(__target, props = {}) {\n`;
|
|
1376
1451
|
} else {
|
|
1377
1452
|
output += `import { ${ESM_CORE_IMPORTS} } from '@sola-air-ui/core';\n`;
|
|
1453
|
+
if (coreImportExtras.length) {
|
|
1454
|
+
const names = coreImportExtras.map(e => (e.local === e.imported ? e.imported : `${e.imported} as ${e.local}`));
|
|
1455
|
+
output += `import { ${names.join(', ')} } from '@sola-air-ui/core';\n`;
|
|
1456
|
+
}
|
|
1378
1457
|
for (const imp of componentImports) {
|
|
1379
1458
|
output += `import ${imp.localName} from '${imp.path}';\n`;
|
|
1380
1459
|
}
|
|
1460
|
+
for (const imp of otherImports) {
|
|
1461
|
+
output += `${imp.code}\n`;
|
|
1462
|
+
}
|
|
1381
1463
|
output += `\n`;
|
|
1382
1464
|
output += `export default function mount(__target, props = {}) {\n`;
|
|
1383
1465
|
}
|
|
@@ -1462,6 +1544,9 @@ function inferPropType(defaultSrc) {
|
|
|
1462
1544
|
if (/^(['"`])[\s\S]*\1$/.test(d)) return 'string';
|
|
1463
1545
|
if (/^-?\d[\d_]*(\.\d+)?([eE][+-]?\d+)?$/.test(d)) return 'number';
|
|
1464
1546
|
if (d === 'true' || d === 'false') return 'boolean';
|
|
1547
|
+
// A null or undefined default says nothing about the type. `unknown` would stop a
|
|
1548
|
+
// TypeScript host from reading or calling the prop; `any` lets it.
|
|
1549
|
+
if (d === 'null' || d === 'undefined') return 'any';
|
|
1465
1550
|
if (d.startsWith('[')) return 'unknown[]';
|
|
1466
1551
|
if (d.startsWith('{')) return 'Record<string, unknown>';
|
|
1467
1552
|
if (/^(\(.*\)|\w+)\s*=>|^function\b/.test(d)) return '(...args: any[]) => any';
|
package/types/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface CompileOptions {
|
|
|
4
4
|
/**
|
|
5
5
|
* 'esm' (default) emits a module importing from @sola-air-ui/core.
|
|
6
6
|
* 'iife' emits a self-contained bundle reading from window.SolaCore, for
|
|
7
|
-
*
|
|
7
|
+
* enterprise portals and other no-bundler hosts.
|
|
8
8
|
*/
|
|
9
9
|
target?: 'esm' | 'iife';
|
|
10
10
|
/** Global name the IIFE build registers itself under. Default 'SolaComponent'. */
|