@aiot-toolkit/aiotpack 2.1.0-prender.13 → 2.1.0-prender.14
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/lib/compiler/javascript/TemplateCompiler.js +191 -15
- package/lib/compiler/javascript/ViteCompiler.d.ts +13 -0
- package/lib/compiler/javascript/ViteCompiler.js +55 -11
- package/lib/compiler/javascript/vela/utils/Jsc.d.ts +1 -0
- package/lib/compiler/javascript/vela/utils/Jsc.js +28 -2
- package/package.json +5 -5
|
@@ -236,6 +236,8 @@ function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pa
|
|
|
236
236
|
const rootEl = children.find(n => n.tagName && n.nodeName !== '#text');
|
|
237
237
|
if (!rootEl) return {};
|
|
238
238
|
const result = elementToJson(rootEl, styleSheet, importMap, undefined, pageDir);
|
|
239
|
+
assertNoRelativeAssetPaths(result, pageDir);
|
|
240
|
+
assertNoDelimitersInEvents(result, pageDir);
|
|
239
241
|
const componentsForRoot = componentsMap && Object.keys(componentsMap).length ? componentsMap : importMap;
|
|
240
242
|
if (componentsForRoot && Object.keys(componentsForRoot).length) {
|
|
241
243
|
result.components = {
|
|
@@ -259,6 +261,175 @@ function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pa
|
|
|
259
261
|
/** Add this. prefix to variable references in an expression */
|
|
260
262
|
// blueos-pack's pathTestRE: matches simple paths like a.b.c, a['key'], a[0], a[$idx]
|
|
261
263
|
const pathTestRE = /^[A-Za-z_$][\w$]*(?:\.[\w$]+|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/;
|
|
264
|
+
|
|
265
|
+
// Attributes whose value is a resource path and therefore must be rewritten from
|
|
266
|
+
// `./x` / `../x` (relative to the owning .ux file) to an rpk-root absolute path.
|
|
267
|
+
// Runtime resolves <image src> against the rpk root, so a relative path emitted
|
|
268
|
+
// from a nested component directory never resolves and falls back to a placeholder.
|
|
269
|
+
const PATH_ATTRS = new Set(['src', 'href', 'alt']);
|
|
270
|
+
const ARROW_RE = /=>/;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Canonical event key for an `@x` / `onx` attribute.
|
|
274
|
+
*
|
|
275
|
+
* Every event name the platform defines is a lowercase single word
|
|
276
|
+
* (`click`, `longpress`, `touchstart`, `animationiteration`, ... — 72 of them
|
|
277
|
+
* across vela and android, none camelCase or hyphenated), and the parser's own
|
|
278
|
+
* `translateEventName` just drops the prefix. Camel-casing the remainder turned
|
|
279
|
+
* `on-long-press` into `LongPress`, which matches no event and silently never
|
|
280
|
+
* fires, so separators are removed and the name lowercased instead.
|
|
281
|
+
*/
|
|
282
|
+
function normalizeEventName(name) {
|
|
283
|
+
const stripped = name.startsWith('@') ? name.slice(1) : name.replace(/^on/, '');
|
|
284
|
+
return stripped.replace(/[-_]/g, '').toLowerCase();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Strip one wrapping `{{ ... }}` from an attribute value.
|
|
289
|
+
*
|
|
290
|
+
* Only the outer pair is removed, and only when it actually wraps the whole
|
|
291
|
+
* value — `{{()=>{ x() }}}` must yield `()=>{ x() }`, so a global
|
|
292
|
+
* `replace(/\{\{|\}\}/g, '')` is wrong: it would also eat the arrow body's
|
|
293
|
+
* braces. A value that merely contains an interpolation (`a {{b}} c`) is left
|
|
294
|
+
* alone; callers that need that do their own splitting.
|
|
295
|
+
*/
|
|
296
|
+
function stripTemplateDelimiters(value) {
|
|
297
|
+
let v = (value ?? '').trim();
|
|
298
|
+
if (!v.startsWith('{{') || !v.endsWith('}}')) return v;
|
|
299
|
+
const inner = v.slice(2, -2);
|
|
300
|
+
// Reject `{{a}} + {{b}}`, where the opening pair closes before the end.
|
|
301
|
+
let depth = 0;
|
|
302
|
+
for (let i = 0; i < inner.length; i++) {
|
|
303
|
+
if (inner[i] === '{') depth++;else if (inner[i] === '}') {
|
|
304
|
+
depth--;
|
|
305
|
+
if (depth < 0) return v;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return depth === 0 ? inner.trim() : v;
|
|
309
|
+
}
|
|
310
|
+
function isRelAssetPath(v) {
|
|
311
|
+
return v.startsWith('./') || v.startsWith('../');
|
|
312
|
+
}
|
|
313
|
+
function resolveRelFromPageDir(rel, pageDir) {
|
|
314
|
+
// A trailing slash is significant when the path is a prefix fragment of a
|
|
315
|
+
// concatenated expression ('./assets/full/' + lang + '.png') — dropping it
|
|
316
|
+
// would yield '/.../fullcn.png'.
|
|
317
|
+
const trailing = rel.endsWith('/') ? '/' : '';
|
|
318
|
+
const segments = pageDir.split('/').filter(Boolean).concat(rel.split('/'));
|
|
319
|
+
const stack = [];
|
|
320
|
+
for (const seg of segments) {
|
|
321
|
+
if (seg === '..') stack.pop();else if (seg && seg !== '.') stack.push(seg);
|
|
322
|
+
}
|
|
323
|
+
return '/' + stack.join('/') + trailing;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Rewrite relative resource-path string literals inside a generated expression.
|
|
328
|
+
* Dynamic `src="{{'./assets/' + lang + '.png'}}"` compiles to a `$src` function
|
|
329
|
+
* body, so the path lives in a literal rather than a plain attribute value — the
|
|
330
|
+
* static-attribute rewrite never sees it. Only literals starting with ./ or ../
|
|
331
|
+
* are touched, so suffix fragments like '.png' pass through untouched.
|
|
332
|
+
*/
|
|
333
|
+
function rewriteRelPathLiterals(exprSrc, pageDir) {
|
|
334
|
+
let out = '';
|
|
335
|
+
let i = 0;
|
|
336
|
+
while (i < exprSrc.length) {
|
|
337
|
+
const ch = exprSrc[i];
|
|
338
|
+
if (ch !== "'" && ch !== '"') {
|
|
339
|
+
out += ch;
|
|
340
|
+
i++;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const quote = ch;
|
|
344
|
+
let j = i + 1;
|
|
345
|
+
let raw = '';
|
|
346
|
+
let closed = false;
|
|
347
|
+
while (j < exprSrc.length) {
|
|
348
|
+
const c = exprSrc[j];
|
|
349
|
+
if (c === '\\') {
|
|
350
|
+
raw += c + (exprSrc[j + 1] ?? '');
|
|
351
|
+
j += 2;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (c === quote) {
|
|
355
|
+
closed = true;
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
raw += c;
|
|
359
|
+
j++;
|
|
360
|
+
}
|
|
361
|
+
if (!closed) {
|
|
362
|
+
out += exprSrc.slice(i);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
const unescaped = raw.replace(/\\(['"\\])/g, '$1');
|
|
366
|
+
if (isRelAssetPath(unescaped)) {
|
|
367
|
+
const abs = resolveRelFromPageDir(unescaped, pageDir);
|
|
368
|
+
out += quote + abs.replace(/\\/g, '\\\\').replace(new RegExp(quote, 'g'), '\\' + quote) + quote;
|
|
369
|
+
} else {
|
|
370
|
+
out += quote + raw + quote;
|
|
371
|
+
}
|
|
372
|
+
i = j + 1;
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
const REL_LITERAL_IN_EXPR_RE = /(['"])\.\.?\//;
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Fail the build when a template still carries a relative resource path, in a
|
|
380
|
+
* static attr or inside a `$`-bound expression's literals. Runtime resolves
|
|
381
|
+
* these against the rpk root, silently falling back to a placeholder image —
|
|
382
|
+
* far harder to diagnose on-device than a pack-time error.
|
|
383
|
+
*/
|
|
384
|
+
function assertNoRelativeAssetPaths(root, pageDir) {
|
|
385
|
+
const offenders = [];
|
|
386
|
+
const walk = node => {
|
|
387
|
+
if (!node || typeof node !== 'object') return;
|
|
388
|
+
const attr = node.attr;
|
|
389
|
+
if (attr && typeof attr === 'object') {
|
|
390
|
+
for (const [key, val] of Object.entries(attr)) {
|
|
391
|
+
if (typeof val !== 'string') continue;
|
|
392
|
+
const bare = key.startsWith('$') ? key.slice(1) : key;
|
|
393
|
+
if (!PATH_ATTRS.has(bare)) continue;
|
|
394
|
+
const bad = key.startsWith('$') ? REL_LITERAL_IN_EXPR_RE.test(val) : isRelAssetPath(val);
|
|
395
|
+
if (bad) offenders.push(`<${node.type ?? '?'} ${key}="${val}">`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (Array.isArray(node.children)) node.children.forEach(walk);
|
|
399
|
+
};
|
|
400
|
+
walk(root);
|
|
401
|
+
if (offenders.length) {
|
|
402
|
+
throw new Error(`[template] ${offenders.length} relative resource path(s) survived rewriting` + `${pageDir ? ` in ${pageDir}` : ' (no pageDir supplied)'}. Runtime resolves src/href ` + `against the rpk root, so these render a placeholder instead of the asset:\n` + offenders.map(o => ` ${o}`).join('\n'));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Fail the build when an event value still carries template delimiters. Runtime
|
|
408
|
+
* cannot recover: the `$`-prefixed form fails to compile (logged, event skipped)
|
|
409
|
+
* and the bare form is looked up as a method literally named `{{fn}}` (silent).
|
|
410
|
+
* Either way the handler is dead, so catching it at pack time is the only way
|
|
411
|
+
* the breakage is visible before the device.
|
|
412
|
+
*/
|
|
413
|
+
function assertNoDelimitersInEvents(root, pageDir) {
|
|
414
|
+
const offenders = [];
|
|
415
|
+
const walk = node => {
|
|
416
|
+
if (!node || typeof node !== 'object') return;
|
|
417
|
+
const events = node.events;
|
|
418
|
+
if (events && typeof events === 'object') {
|
|
419
|
+
for (const [key, val] of Object.entries(events)) {
|
|
420
|
+
if (typeof val !== 'string') continue;
|
|
421
|
+
if (val.includes('{{') || val.includes('}}')) {
|
|
422
|
+
offenders.push(`<${node.type ?? '?'} ${key}="${val}">`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (Array.isArray(node.children)) node.children.forEach(walk);
|
|
427
|
+
};
|
|
428
|
+
walk(root);
|
|
429
|
+
if (offenders.length) {
|
|
430
|
+
throw new Error(`[template] ${offenders.length} event handler(s) still contain template delimiters` + `${pageDir ? ` in ${pageDir}` : ''}. Runtime cannot compile or resolve these, so the ` + `events are silently dead:\n` + offenders.map(o => ` ${o}`).join('\n'));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
262
433
|
function tryEvalStaticShowLiteral(expr) {
|
|
263
434
|
if (/^'[^']*'$/.test(expr) || /^"[^"]*"$/.test(expr)) {
|
|
264
435
|
const inner = expr.slice(1, -1);
|
|
@@ -588,11 +759,18 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
588
759
|
if (name === 'class') {
|
|
589
760
|
className = value.replace(/\s+/g, ' ').trim();
|
|
590
761
|
} else if (name.startsWith('@') || name.startsWith('on') && name.length > 2) {
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
//
|
|
594
|
-
|
|
595
|
-
|
|
762
|
+
const evtName = normalizeEventName(name);
|
|
763
|
+
// Event values may be written bare (@click="fn") or wrapped in template
|
|
764
|
+
// delimiters (@click="{{fn}}"); both mean the same thing, so strip the
|
|
765
|
+
// delimiters before classifying. Leaving them in emits `{{fn}}`, which
|
|
766
|
+
// runtime either fails to compile ($-form) or looks up as a method name
|
|
767
|
+
// literally called "{{fn}}" (bare form) — a silent dead event either way.
|
|
768
|
+
const evtVal = stripTemplateDelimiters(value);
|
|
769
|
+
// An arrow function is already a handler; wrapping it in `function (evt)`
|
|
770
|
+
// and appending evt to its call would both mis-bind and drop the event arg.
|
|
771
|
+
if (ARROW_RE.test(evtVal)) {
|
|
772
|
+
events[`$${evtName}`] = `function (evt) { return (${addThisPrefix(evtVal)})(evt); }`;
|
|
773
|
+
} else if (evtVal.includes('(')) {
|
|
596
774
|
// Add evt as last parameter
|
|
597
775
|
const withEvt = evtVal.replace(/\)$/, evtVal.includes('()') ? 'evt)' : ', evt)');
|
|
598
776
|
events[`$${evtName}`] = `function (evt) { return ${addThisPrefix(withEvt)}; }`;
|
|
@@ -750,11 +928,16 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
750
928
|
} else if (/\{\{.*\}\}/.test(value)) {
|
|
751
929
|
// Dynamic binding - build expression from mixed static/dynamic parts
|
|
752
930
|
const parts = value.split(/(\{\{[\s\S]*?\}\})/);
|
|
753
|
-
|
|
931
|
+
let exprs = parts.filter(Boolean).map(p => {
|
|
754
932
|
const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
|
|
755
933
|
if (m) return addThisPrefix(m[1].trim());
|
|
756
934
|
return `'${p.replace(/'/g, "\\'")}'`;
|
|
757
935
|
});
|
|
936
|
+
// Resource-path attrs carry their path inside the expression's string
|
|
937
|
+
// literals; rewrite those too so dynamic src matches static src form.
|
|
938
|
+
if (PATH_ATTRS.has(name) && pageDir) {
|
|
939
|
+
exprs = exprs.map(e => rewriteRelPathLiterals(e, pageDir));
|
|
940
|
+
}
|
|
758
941
|
const camelName = '$' + name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
759
942
|
const plainName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
760
943
|
// Handle literal values (null, true, false, numbers)
|
|
@@ -793,15 +976,8 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
793
976
|
// Normalize relative path attrs (src, href, alt) to absolute paths from src root.
|
|
794
977
|
// alt can also carry prose (e.g. "blank") — only rewrite when the value starts with
|
|
795
978
|
// ./ or ../ so image alt-text prose passes through untouched.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
// Resolve relative to pageDir, then make absolute from src root
|
|
799
|
-
const segments = pageDir.split('/').filter(Boolean).concat(staticVal.split('/'));
|
|
800
|
-
const stack = [];
|
|
801
|
-
for (const seg of segments) {
|
|
802
|
-
if (seg === '..') stack.pop();else if (seg && seg !== '.') stack.push(seg);
|
|
803
|
-
}
|
|
804
|
-
staticVal = '/' + stack.join('/');
|
|
979
|
+
if (staticVal && isRelAssetPath(staticVal) && PATH_ATTRS.has(name) && pageDir) {
|
|
980
|
+
staticVal = resolveRelFromPageDir(staticVal, pageDir);
|
|
805
981
|
}
|
|
806
982
|
attrObj[name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = staticVal;
|
|
807
983
|
if (name === 'id') hasBind = true;
|
|
@@ -11,9 +11,22 @@ declare class ViteCompiler implements ICompiler {
|
|
|
11
11
|
clean(param: ICompileParam & IJavascriptCompileOption): Promise<void>;
|
|
12
12
|
}
|
|
13
13
|
declare function stripDeadDefProp(code: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Build a sub-component's standalone `.js`.
|
|
16
|
+
*
|
|
17
|
+
* A component is registered as its own `$app_define$` module and loaded
|
|
18
|
+
* independently at runtime — the host page bundle does not carry its code. So
|
|
19
|
+
* everything the component's `<script>` declares at module level has to survive:
|
|
20
|
+
* feature imports (`import vibrator from "@system.vibrator"`), consts, helpers.
|
|
21
|
+
* Keeping only the `export default` object literal strips the feature binding and
|
|
22
|
+
* leaves `vibrator` as a free identifier, which is why the 咪咕/百度地图 keyboard
|
|
23
|
+
* could no longer vibrate.
|
|
24
|
+
*/
|
|
25
|
+
declare function buildComponentJs(compCode: string, compName: string, compSid: number, relPath: string): string;
|
|
14
26
|
declare function mergeStyleSheets(app: any, page: any): any;
|
|
15
27
|
export declare const __test: {
|
|
16
28
|
stripDeadDefProp: typeof stripDeadDefProp;
|
|
17
29
|
mergeStyleSheets: typeof mergeStyleSheets;
|
|
30
|
+
buildComponentJs: typeof buildComponentJs;
|
|
18
31
|
};
|
|
19
32
|
export default ViteCompiler;
|
|
@@ -554,11 +554,7 @@ class ViteCompiler {
|
|
|
554
554
|
spaces: 2
|
|
555
555
|
});
|
|
556
556
|
const compJsPath = _path.default.join(buildPath, `${compPath}.js`);
|
|
557
|
-
|
|
558
|
-
const compObj = exportMatch ? exportMatch[1].trim() : '{}';
|
|
559
|
-
const compVarName = compName.replace(/-/g, '_') + '_default';
|
|
560
|
-
const compJsContent = `let $style$${compSid} = {"@info":{"styleObjectId":${compSid}}};\nconst $app_style$${compSid} = $style$${compSid};\nvar ${compVarName} = ${compObj};\n$app_define$("@app-component/${compName}", [], function($app_require$, $app_exports$, $app_module$) {\n $app_module$.exports = ${compVarName}.default || ${compVarName};\n $app_module$.exports.style = $app_style$${compSid};\n});\n`;
|
|
561
|
-
_fsExtra.default.writeFileSync(compJsPath, compJsContent);
|
|
557
|
+
_fsExtra.default.writeFileSync(compJsPath, buildComponentJs(compCode, compName, compSid, compPath));
|
|
562
558
|
}
|
|
563
559
|
}
|
|
564
560
|
}
|
|
@@ -685,13 +681,15 @@ class ViteCompiler {
|
|
|
685
681
|
spaces: 2
|
|
686
682
|
});
|
|
687
683
|
// Generate js
|
|
688
|
-
const exportMatch = compCode.match(/export\s+default\s+([\s\S]*?);?\s*$/);
|
|
689
|
-
const compObj = exportMatch ? exportMatch[1].trim() : '{}';
|
|
690
684
|
const compName = _path.default.basename(relPath).toLowerCase();
|
|
691
|
-
const varName = compName.replace(/-/g, '_') + '_default';
|
|
692
685
|
_fsExtra.default.ensureDirSync(_path.default.dirname(outputJs));
|
|
693
|
-
_fsExtra.default.writeFileSync(outputJs,
|
|
694
|
-
} catch {
|
|
686
|
+
_fsExtra.default.writeFileSync(outputJs, buildComponentJs(compCode, compName, sid, relPath));
|
|
687
|
+
} catch (e) {
|
|
688
|
+
// A component that fails to emit renders but has no behaviour, so don't
|
|
689
|
+
// swallow the guard errors that report exactly why.
|
|
690
|
+
if (e?.message?.startsWith('[component]')) throw e;
|
|
691
|
+
console.warn(`vite: failed to emit component ${relPath}:`, e?.message ?? e);
|
|
692
|
+
}
|
|
695
693
|
}
|
|
696
694
|
}
|
|
697
695
|
async clean(param) {
|
|
@@ -707,6 +705,51 @@ function stripDeadDefProp(code) {
|
|
|
707
705
|
const withoutDefine = code.replace(/var __defProp = Object\.defineProperty;\n?/g, '');
|
|
708
706
|
return /\b__defProp\s*\(/.test(withoutDefine) ? code : withoutDefine;
|
|
709
707
|
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Build a sub-component's standalone `.js`.
|
|
711
|
+
*
|
|
712
|
+
* A component is registered as its own `$app_define$` module and loaded
|
|
713
|
+
* independently at runtime — the host page bundle does not carry its code. So
|
|
714
|
+
* everything the component's `<script>` declares at module level has to survive:
|
|
715
|
+
* feature imports (`import vibrator from "@system.vibrator"`), consts, helpers.
|
|
716
|
+
* Keeping only the `export default` object literal strips the feature binding and
|
|
717
|
+
* leaves `vibrator` as a free identifier, which is why the 咪咕/百度地图 keyboard
|
|
718
|
+
* could no longer vibrate.
|
|
719
|
+
*/
|
|
720
|
+
function buildComponentJs(compCode, compName, compSid, relPath) {
|
|
721
|
+
let code = compCode;
|
|
722
|
+
// The raw `var $app_style$ = [...]` array is superseded by the $app_style$<sid>
|
|
723
|
+
// @info reference emitted below; css.json already carries the real style data.
|
|
724
|
+
const styleLit = extractAppStyleLiteral(code);
|
|
725
|
+
if (styleLit) {
|
|
726
|
+
const decl = code.match(/var\s+\$app_style\$\s*=\s*/);
|
|
727
|
+
if (decl) {
|
|
728
|
+
const start = decl.index;
|
|
729
|
+
let end = code.indexOf(styleLit, start) + styleLit.length;
|
|
730
|
+
if (code[end] === ';') end++;
|
|
731
|
+
code = code.slice(0, start) + code.slice(end);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// Turn @system.*/@app-module imports into $app_require$ calls, same as entries.
|
|
735
|
+
code = rewriteSystemImports(code);
|
|
736
|
+
let defaultExpr = '{}';
|
|
737
|
+
const edIdx = code.search(/export\s+default\s+/);
|
|
738
|
+
if (edIdx !== -1) {
|
|
739
|
+
const kw = code.slice(edIdx).match(/^export\s+default\s+/);
|
|
740
|
+
defaultExpr = code.slice(edIdx + kw[0].length).trim().replace(/;\s*$/, '') || '{}';
|
|
741
|
+
code = code.slice(0, edIdx);
|
|
742
|
+
}
|
|
743
|
+
// Named exports have no consumer in a $app_define$ module; keep the declaration.
|
|
744
|
+
code = code.replace(/\bexport\s+(?=(?:const|let|var|function|class|async)\b)/g, '');
|
|
745
|
+
const leftoverImport = code.match(/^\s*import\s[^\n]*/m);
|
|
746
|
+
if (leftoverImport) {
|
|
747
|
+
throw new Error(`[component] ${relPath} has an unresolved ESM import that cannot be emitted into a ` + `standalone $app_define$ module: ${leftoverImport[0].trim()}\n` + `Only @system.*/@service.*/@android.*/@hap.*/@app-module imports are rewritten to ` + `$app_require$. Inline the dependency into the component or move it behind a feature module.`);
|
|
748
|
+
}
|
|
749
|
+
const varName = compName.replace(/-/g, '_') + '_default';
|
|
750
|
+
const moduleCode = code.trim();
|
|
751
|
+
return [`let $style$${compSid} = {"@info":{"styleObjectId":${compSid}}};`, `const $app_style$${compSid} = $style$${compSid};`, ...(moduleCode ? [moduleCode] : []), `var ${varName} = ${defaultExpr};`, `$app_define$("@app-component/${compName}", [], function($app_require$, $app_exports$, $app_module$) {`, ` $app_module$.exports = ${varName}.default || ${varName};`, ` $app_module$.exports.style = $app_style$${compSid};`, `});`, ''].join('\n');
|
|
752
|
+
}
|
|
710
753
|
function wrapOutput(code, entryName, isApp) {
|
|
711
754
|
const componentName = _path.default.parse(entryName).name;
|
|
712
755
|
const defineId = isApp ? '@app-component/app' : `@app-component/${componentName}`;
|
|
@@ -831,6 +874,7 @@ function mergeStyleSheets(app, page) {
|
|
|
831
874
|
}
|
|
832
875
|
const __test = exports.__test = {
|
|
833
876
|
stripDeadDefProp,
|
|
834
|
-
mergeStyleSheets
|
|
877
|
+
mergeStyleSheets,
|
|
878
|
+
buildComponentJs
|
|
835
879
|
};
|
|
836
880
|
var _default = exports.default = ViteCompiler;
|
|
@@ -10,7 +10,15 @@ var _sharedUtils = require("@aiot-toolkit/shared-utils");
|
|
|
10
10
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
11
11
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
12
12
|
const REQUIRED_JSC_VERSION = '1.0.9';
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// `__ctx` is the page scope whose prototype is `ferryGlobal` (where the runtime
|
|
15
|
+
// registers `runGC` and friends). A bare `global` in legacy page source resolves
|
|
16
|
+
// to QuickJS's real global object, which does NOT carry those APIs — hence
|
|
17
|
+
// `global.runGC()` throws `TypeError: not a function`. Bind `global`/`window`/`self`
|
|
18
|
+
// to `__ctx` so `global.xxx` walks `__ctx → ferryGlobal`. `globalThis` is left
|
|
19
|
+
// untouched (still the real global), so `console` must default from `globalThis`,
|
|
20
|
+
// not `global` — after the rebind `global.console` would look up ferryGlobal.
|
|
21
|
+
const IIFE_WRAP_PREFIX = 'export default (function (__ctx) {\n' + ' const global = __ctx, window = __ctx, self = __ctx;\n' + ' let { requireModule, unRequireModule, $app_define$, $app_bootstrap$, $app_require$,\n' + ' console = globalThis.console } = __ctx;\n' + ' let { setTimeout, clearTimeout, setInterval, clearInterval } = __ctx;\n\n';
|
|
14
22
|
const IIFE_WRAP_SUFFIX = '\n});\n';
|
|
15
23
|
class Jsc {
|
|
16
24
|
constructor(projectPath, buildPath) {
|
|
@@ -150,7 +158,25 @@ class Jsc {
|
|
|
150
158
|
const abs = path.join(buildPath, rel);
|
|
151
159
|
const body = fs.readFileSync(abs, 'utf-8');
|
|
152
160
|
if (body.startsWith('export default (function (__ctx)')) continue;
|
|
153
|
-
|
|
161
|
+
const wrapped = IIFE_WRAP_PREFIX + body + IIFE_WRAP_SUFFIX;
|
|
162
|
+
Jsc.assertLegacyGlobalBinding(rel, wrapped);
|
|
163
|
+
fs.writeFileSync(abs, wrapped, 'utf-8');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fail the build if a Component-JS page keeps a bare `global`/`window`/`self`
|
|
168
|
+
// member access but the wrapper lost its compat alias — otherwise the page
|
|
169
|
+
// ships and only blows up at runtime (`global.runGC is not a function` →
|
|
170
|
+
// unhandled promise rejection stuck on the loading page). The alias is emitted
|
|
171
|
+
// unconditionally by IIFE_WRAP_PREFIX, so this trips only if that prefix
|
|
172
|
+
// regresses, surfacing the break at pack time instead of on-device.
|
|
173
|
+
static assertLegacyGlobalBinding(rel, wrapped) {
|
|
174
|
+
const usesLegacyGlobal = /\b(global|window|self)\s*\./.test(wrapped);
|
|
175
|
+
if (!usesLegacyGlobal) return;
|
|
176
|
+
const hasAlias = /\bconst global = __ctx, window = __ctx, self = __ctx;/.test(wrapped);
|
|
177
|
+
if (!hasAlias) {
|
|
178
|
+
_sharedUtils.ColorConsole.throw(`[jsc] ${rel} references bare global/window/self but the IIFE wrapper is ` + `missing the \`const global = __ctx, window = __ctx, self = __ctx\` compat ` + `alias. Such a page would throw at runtime (e.g. global.runGC is not a ` + `function) and hang on the loading page.`);
|
|
179
|
+
throw new Error(`[jsc] ${rel}: missing legacy global compat alias in wrapper`);
|
|
154
180
|
}
|
|
155
181
|
}
|
|
156
182
|
static compareSemver(a, b) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiot-toolkit/aiotpack",
|
|
3
|
-
"version": "2.1.0-prender.
|
|
3
|
+
"version": "2.1.0-prender.14",
|
|
4
4
|
"description": "The process tool for packaging aiot projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aiotpack"
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
"test": "node ./__tests__/aiotpack.test.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@aiot-toolkit/generator": "2.1.0-prender.
|
|
23
|
-
"@aiot-toolkit/parser": "2.1.0-prender.
|
|
24
|
-
"@aiot-toolkit/shared-utils": "2.1.0-prender.
|
|
22
|
+
"@aiot-toolkit/generator": "2.1.0-prender.14",
|
|
23
|
+
"@aiot-toolkit/parser": "2.1.0-prender.14",
|
|
24
|
+
"@aiot-toolkit/shared-utils": "2.1.0-prender.14",
|
|
25
25
|
"@hap-toolkit/aaptjs": "^2.0.0",
|
|
26
26
|
"@rspack/core": "^1.3.9",
|
|
27
27
|
"acorn": "^8.16.0",
|
|
28
28
|
"aiot-parse5": "^1.0.2",
|
|
29
29
|
"astring": "^1.9.0",
|
|
30
30
|
"babel-loader": "^9.1.3",
|
|
31
|
-
"file-lane": "2.1.0-prender.
|
|
31
|
+
"file-lane": "2.1.0-prender.14",
|
|
32
32
|
"file-loader": "^6.2.0",
|
|
33
33
|
"fs-extra": "^11.2.0",
|
|
34
34
|
"jsrsasign": "^11.1.0",
|