@sola-air-ui/compiler 1.0.3 → 1.0.4
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 +24 -7
- package/src/index.js +40 -11
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -12,30 +12,42 @@ if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
|
12
12
|
sola compile <file.sola> [options]
|
|
13
13
|
|
|
14
14
|
Options:
|
|
15
|
-
-o, --out <file>
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
-o, --out <file> Write compiled output to a file (default: stdout)
|
|
16
|
+
--target <esm|iife> Output format (default: esm)
|
|
17
|
+
esm — ES module with import/export (default, for bundlers)
|
|
18
|
+
iife — Self-contained IIFE for no-bundler environments
|
|
19
|
+
(ServiceNow Service Portal, plain <script> tags)
|
|
20
|
+
--export-name <Name> Global name for IIFE target (default: SolaComponent)
|
|
21
|
+
The compiled component mounts as window['Name'](el, props)
|
|
22
|
+
-v, --version Print compiler version
|
|
23
|
+
-h, --help Show this help message
|
|
18
24
|
|
|
19
25
|
Examples:
|
|
20
26
|
sola compile App.sola -o dist/App.js
|
|
21
|
-
sola compile
|
|
27
|
+
sola compile Widget.sola --target iife --export-name MyWidget -o dist/my-widget.js
|
|
22
28
|
`);
|
|
23
29
|
process.exit(0);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
if (args.includes('-v') || args.includes('--version')) {
|
|
27
|
-
console.log('Sola Compiler v1.0.
|
|
33
|
+
console.log('Sola Compiler v1.0.3');
|
|
28
34
|
process.exit(0);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
let inputFile = null;
|
|
32
38
|
let outputFile = null;
|
|
39
|
+
let target = 'esm';
|
|
40
|
+
let exportName = 'SolaComponent';
|
|
33
41
|
|
|
34
42
|
for (let i = 0; i < args.length; i++) {
|
|
35
43
|
const arg = args[i];
|
|
36
44
|
if (arg === 'compile') continue;
|
|
37
45
|
if (arg === '-o' || arg === '--out') {
|
|
38
46
|
outputFile = args[++i];
|
|
47
|
+
} else if (arg === '--target') {
|
|
48
|
+
target = args[++i];
|
|
49
|
+
} else if (arg === '--export-name') {
|
|
50
|
+
exportName = args[++i];
|
|
39
51
|
} else if (!inputFile && !arg.startsWith('-')) {
|
|
40
52
|
inputFile = arg;
|
|
41
53
|
}
|
|
@@ -46,6 +58,11 @@ if (!inputFile) {
|
|
|
46
58
|
process.exit(1);
|
|
47
59
|
}
|
|
48
60
|
|
|
61
|
+
if (target !== 'esm' && target !== 'iife') {
|
|
62
|
+
console.error(`Error: --target must be "esm" or "iife", got "${target}"`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
const inputPath = path.resolve(process.cwd(), inputFile);
|
|
50
67
|
if (!fs.existsSync(inputPath)) {
|
|
51
68
|
console.error(`Error: File not found: ${inputFile}`);
|
|
@@ -54,14 +71,14 @@ if (!fs.existsSync(inputPath)) {
|
|
|
54
71
|
|
|
55
72
|
try {
|
|
56
73
|
const source = fs.readFileSync(inputPath, 'utf8');
|
|
57
|
-
const result = compile(source, inputFile);
|
|
74
|
+
const result = compile(source, { filename: inputFile, target, exportName });
|
|
58
75
|
const code = result.code || result;
|
|
59
76
|
|
|
60
77
|
if (outputFile) {
|
|
61
78
|
const outPath = path.resolve(process.cwd(), outputFile);
|
|
62
79
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
63
80
|
fs.writeFileSync(outPath, code, 'utf8');
|
|
64
|
-
console.log(`✓ Compiled ${inputFile} -> ${outputFile} (${code.length} bytes)`);
|
|
81
|
+
console.log(`✓ Compiled ${inputFile} -> ${outputFile} [${target}] (${code.length} bytes)`);
|
|
65
82
|
} else {
|
|
66
83
|
console.log(code);
|
|
67
84
|
}
|
package/src/index.js
CHANGED
|
@@ -42,9 +42,11 @@ function extractScriptAndStyle(source) {
|
|
|
42
42
|
let style = '';
|
|
43
43
|
let template = source;
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
let isTypeScript = false;
|
|
46
|
+
const scriptMatch = template.match(/<script(?:\s+lang=["'](ts|js)["'])?>([\s\S]*?)<\/script>/);
|
|
46
47
|
if (scriptMatch) {
|
|
47
|
-
|
|
48
|
+
isTypeScript = scriptMatch[1] === 'ts';
|
|
49
|
+
script = scriptMatch[2];
|
|
48
50
|
template = template.replace(scriptMatch[0], '');
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -54,7 +56,7 @@ function extractScriptAndStyle(source) {
|
|
|
54
56
|
template = template.replace(styleMatch[0], '');
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
return { script, style, template };
|
|
59
|
+
return { script, style, template, isTypeScript };
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// Returns the index of the closing } that matches the { at openPos,
|
|
@@ -104,7 +106,7 @@ function preprocessTemplate(template, dynAttrs) {
|
|
|
104
106
|
if (closeIdx !== -1) {
|
|
105
107
|
const expr = template.slice(i + 1, closeIdx).trim();
|
|
106
108
|
if (!expr.startsWith('#') && !expr.startsWith('/') && !expr.startsWith(':')) {
|
|
107
|
-
if (/^on
|
|
109
|
+
if (/^on:?[\w]+$/.test(attrName) || /^bind:[\w]+$/.test(attrName)) {
|
|
108
110
|
result += `${attrName}="${expr}"`;
|
|
109
111
|
} else {
|
|
110
112
|
dynAttrs.push(expr);
|
|
@@ -193,7 +195,7 @@ function stripTypeScript(code) {
|
|
|
193
195
|
|
|
194
196
|
export function compile(source, options = {}) {
|
|
195
197
|
const target = options.target || 'esm'; // 'esm' | 'iife'
|
|
196
|
-
const { script: scriptContent, style: styleContent, template: rawTemplateSource } = extractScriptAndStyle(source);
|
|
198
|
+
const { script: scriptContent, style: styleContent, template: rawTemplateSource, isTypeScript } = extractScriptAndStyle(source);
|
|
197
199
|
const scopeHash = hashString(styleContent || rawTemplateSource);
|
|
198
200
|
const hasStyles = styleContent.trim().length > 0;
|
|
199
201
|
const scopedCss = hasStyles ? scopeStyles(styleContent, scopeHash) : '';
|
|
@@ -238,14 +240,18 @@ export function compile(source, options = {}) {
|
|
|
238
240
|
templateNodes.pop();
|
|
239
241
|
}
|
|
240
242
|
}
|
|
241
|
-
}, { recognizeSelfClosing: true });
|
|
243
|
+
}, { recognizeSelfClosing: true, lowerCaseTags: false });
|
|
242
244
|
|
|
243
245
|
parser.write(rawTemplate.trim() || '<div></div>');
|
|
244
246
|
parser.end();
|
|
245
247
|
|
|
246
248
|
// Parse JS AST with Acorn
|
|
247
|
-
// Strip TS first — AST positions and all slice() calls operate on the stripped source
|
|
248
|
-
|
|
249
|
+
// Strip TS first — AST positions and all slice() calls operate on the stripped source.
|
|
250
|
+
// Only run on <script lang="ts"> — the strip regex can't reliably distinguish a type
|
|
251
|
+
// annotation's `:` from a ternary's `: falsyBranch` or an object literal's `key: value`,
|
|
252
|
+
// so applying it to plain JS risks corrupting valid code. Scripts that don't opt into TS
|
|
253
|
+
// are left untouched.
|
|
254
|
+
const strippedScript = scriptContent.trim() && isTypeScript ? stripTypeScript(scriptContent) : scriptContent;
|
|
249
255
|
let jsOutput = strippedScript;
|
|
250
256
|
const stateVars = new Set();
|
|
251
257
|
const exportedProps = [];
|
|
@@ -439,11 +445,34 @@ export function compile(source, options = {}) {
|
|
|
439
445
|
domCode += ` const ${cid}_target = document.createElement('div');\n`;
|
|
440
446
|
domCode += ` ${cid}_target.className = 'sola-component-root';\n`;
|
|
441
447
|
domCode += ` ${parentVar}.appendChild(${cid}_target);\n`;
|
|
442
|
-
|
|
448
|
+
|
|
449
|
+
// Dynamically-bound props (rows={items}) arrive here as __soladyn_N__ placeholders —
|
|
450
|
+
// resolve them back to the real expression from dynAttrs and pass it as a raw
|
|
451
|
+
// (unquoted) property so the child receives live data, not the placeholder text.
|
|
452
|
+
const propEntries = [];
|
|
443
453
|
for (const [key, val] of Object.entries(node.attribs || {})) {
|
|
444
|
-
|
|
454
|
+
const dynMatch = /^__soladyn_(\d+)__$/.exec(val);
|
|
455
|
+
const propValue = dynMatch ? `(${dynAttrs[parseInt(dynMatch[1], 10)]})` : JSON.stringify(val);
|
|
456
|
+
propEntries.push(`${JSON.stringify(key)}: ${propValue}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Slot content: compile the tag's children into a function the child component can
|
|
460
|
+
// call to project them wherever it renders a <slot> in its own template.
|
|
461
|
+
if (node.children && node.children.length > 0) {
|
|
462
|
+
const slotVar = `${cid}_slot`;
|
|
463
|
+
domCode += ` const ${slotVar} = (__slot_target) => {\n`;
|
|
464
|
+
node.children.forEach(child => emitNode(child, '__slot_target'));
|
|
465
|
+
domCode += ` };\n`;
|
|
466
|
+
propEntries.push(`children: ${slotVar}`);
|
|
445
467
|
}
|
|
446
|
-
|
|
468
|
+
|
|
469
|
+
domCode += ` ${node.name}(${cid}_target, { ${propEntries.join(', ')} });\n`;
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Slot placeholder: projects the children passed by a parent component, if any.
|
|
474
|
+
if (node.name === 'slot') {
|
|
475
|
+
domCode += ` if (typeof props.children === 'function') { props.children(${parentVar}); }\n`;
|
|
447
476
|
return;
|
|
448
477
|
}
|
|
449
478
|
|