@aiot-toolkit/aiotpack 2.1.0-prender.12 → 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/afterCompile/ux/UxAfterCompile.d.ts +10 -0
- package/lib/afterCompile/ux/UxAfterCompile.js +78 -2
- package/lib/compiler/javascript/TemplateCompiler.d.ts +1 -1
- package/lib/compiler/javascript/TemplateCompiler.js +218 -19
- package/lib/compiler/javascript/ViteCompiler.d.ts +13 -0
- package/lib/compiler/javascript/ViteCompiler.js +120 -15
- package/lib/compiler/javascript/vela/utils/Jsc.d.ts +4 -0
- package/lib/compiler/javascript/vela/utils/Jsc.js +69 -1
- package/lib/compiler/javascript/vela/utils/ZipUtil.js +11 -2
- package/lib/config/UxConfig.js +3 -0
- package/package.json +5 -5
|
@@ -22,6 +22,16 @@ declare class UxAfterCompile {
|
|
|
22
22
|
* @param params
|
|
23
23
|
*/
|
|
24
24
|
static compressResource: FollowWork<IJavascriptCompileOption>;
|
|
25
|
+
/**
|
|
26
|
+
* Relocate manifest.icon into build root so debug and release rpks share the
|
|
27
|
+
* same asset layout. Rewrites `manifest.icon` + every string reference to the
|
|
28
|
+
* old icon path inside template.json / css.json / *.js. Files are text-scanned
|
|
29
|
+
* and quoted-substring matched only (no cross-string boundary rewriting).
|
|
30
|
+
*
|
|
31
|
+
* No-op when manifest.icon is already at root, the physical file is missing,
|
|
32
|
+
* or the destination basename already exists at root.
|
|
33
|
+
*/
|
|
34
|
+
static relocateManifestIcon: FollowWork<IJavascriptCompileOption>;
|
|
25
35
|
/**
|
|
26
36
|
* 如果是更新触发,则对比临时项目build文件夹 和 项目build文件夹,生成diff.json
|
|
27
37
|
* @param context
|
|
@@ -99,8 +99,12 @@ class UxAfterCompile {
|
|
|
99
99
|
context,
|
|
100
100
|
compilerOption
|
|
101
101
|
} = params;
|
|
102
|
-
if (compilerOption
|
|
103
|
-
|
|
102
|
+
if (compilerOption) {
|
|
103
|
+
const buildPath = _path.default.join(context.projectPath, context.output, compilerOption.outputPath);
|
|
104
|
+
_Jsc.default.wrapAllJsWithIife(buildPath);
|
|
105
|
+
if (compilerOption.enableJsc === true) {
|
|
106
|
+
return new _Jsc.default(context.projectPath, buildPath).jsc();
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
109
|
};
|
|
106
110
|
static toRpk = async params => {
|
|
@@ -334,6 +338,78 @@ class UxAfterCompile {
|
|
|
334
338
|
}
|
|
335
339
|
};
|
|
336
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Relocate manifest.icon into build root so debug and release rpks share the
|
|
343
|
+
* same asset layout. Rewrites `manifest.icon` + every string reference to the
|
|
344
|
+
* old icon path inside template.json / css.json / *.js. Files are text-scanned
|
|
345
|
+
* and quoted-substring matched only (no cross-string boundary rewriting).
|
|
346
|
+
*
|
|
347
|
+
* No-op when manifest.icon is already at root, the physical file is missing,
|
|
348
|
+
* or the destination basename already exists at root.
|
|
349
|
+
*/
|
|
350
|
+
static relocateManifestIcon = async params => {
|
|
351
|
+
const {
|
|
352
|
+
compilerOption,
|
|
353
|
+
onLog
|
|
354
|
+
} = params;
|
|
355
|
+
if (!compilerOption) return;
|
|
356
|
+
const {
|
|
357
|
+
projectPath,
|
|
358
|
+
outputPath
|
|
359
|
+
} = compilerOption;
|
|
360
|
+
const buildDir = _path.default.join(projectPath, outputPath);
|
|
361
|
+
const manifestPath = _path.default.join(buildDir, 'manifest.json');
|
|
362
|
+
if (!_fsExtra.default.existsSync(manifestPath)) return;
|
|
363
|
+
const manifest = _fsExtra.default.readJSONSync(manifestPath);
|
|
364
|
+
const oldIcon = manifest.icon;
|
|
365
|
+
if (!oldIcon || typeof oldIcon !== 'string') return;
|
|
366
|
+
const basename = _path.default.basename(oldIcon);
|
|
367
|
+
const newIcon = '/' + basename;
|
|
368
|
+
if (oldIcon === newIcon) return;
|
|
369
|
+
const oldFile = _path.default.join(buildDir, oldIcon.replace(/^\//, ''));
|
|
370
|
+
const newFile = _path.default.join(buildDir, basename);
|
|
371
|
+
if (!_fsExtra.default.existsSync(oldFile)) return;
|
|
372
|
+
if (_fsExtra.default.existsSync(newFile)) {
|
|
373
|
+
onLog?.([{
|
|
374
|
+
level: _sharedUtils.Loglevel.WARN,
|
|
375
|
+
message: [`relocateManifestIcon: skipped — build root already contains ${basename}`]
|
|
376
|
+
}]);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
_fsExtra.default.moveSync(oldFile, newFile);
|
|
380
|
+
manifest.icon = newIcon;
|
|
381
|
+
_fsExtra.default.writeJSONSync(manifestPath, manifest, {
|
|
382
|
+
spaces: 2
|
|
383
|
+
});
|
|
384
|
+
const variants = [oldIcon, oldIcon.replace(/^\//, '')];
|
|
385
|
+
const replacements = [];
|
|
386
|
+
for (const v of variants) {
|
|
387
|
+
const target = v === oldIcon ? newIcon : basename;
|
|
388
|
+
replacements.push([JSON.stringify(v), JSON.stringify(target)]);
|
|
389
|
+
replacements.push([`'${v}'`, `'${target}'`]);
|
|
390
|
+
}
|
|
391
|
+
const rewriteFile = fp => {
|
|
392
|
+
let text;
|
|
393
|
+
try {
|
|
394
|
+
text = _fsExtra.default.readFileSync(fp, 'utf-8');
|
|
395
|
+
} catch {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
let changed = false;
|
|
399
|
+
for (const [from, to] of replacements) {
|
|
400
|
+
if (text.includes(from)) {
|
|
401
|
+
text = text.split(from).join(to);
|
|
402
|
+
changed = true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (changed) _fsExtra.default.writeFileSync(fp, text, 'utf-8');
|
|
406
|
+
};
|
|
407
|
+
for (const fp of _sharedUtils.FileUtil.readAlldirSync(buildDir)) {
|
|
408
|
+
if (fp === manifestPath) continue;
|
|
409
|
+
if (/\.(template\.json|css\.json|js|jsx?)$/.test(fp)) rewriteFile(fp);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
337
413
|
/**
|
|
338
414
|
* 如果是更新触发,则对比临时项目build文件夹 和 项目build文件夹,生成diff.json
|
|
339
415
|
* @param context
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare function uxToTemplateJson(templateNode: any, styleObjectId?: number, styleSheet?: any, importMap?: Record<string, string>, pageDir?: string): any;
|
|
1
|
+
declare function uxToTemplateJson(templateNode: any, styleObjectId?: number, styleSheet?: any, importMap?: Record<string, string>, pageDir?: string, componentsMap?: Record<string, string>): any;
|
|
2
2
|
/**
|
|
3
3
|
* Normalize a media query condition text from CSS Level 4 syntax to Level 3.
|
|
4
4
|
*
|
|
@@ -231,11 +231,19 @@ function normalizeCssTransform(input) {
|
|
|
231
231
|
}
|
|
232
232
|
return matched ? JSON.stringify(out) : null;
|
|
233
233
|
}
|
|
234
|
-
function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pageDir) {
|
|
234
|
+
function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pageDir, componentsMap) {
|
|
235
235
|
const children = templateNode.content?.childNodes || templateNode.childNodes || [];
|
|
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);
|
|
241
|
+
const componentsForRoot = componentsMap && Object.keys(componentsMap).length ? componentsMap : importMap;
|
|
242
|
+
if (componentsForRoot && Object.keys(componentsForRoot).length) {
|
|
243
|
+
result.components = {
|
|
244
|
+
...componentsForRoot
|
|
245
|
+
};
|
|
246
|
+
}
|
|
239
247
|
// Insert styleObjectId before children (vivo format)
|
|
240
248
|
if (styleObjectId !== undefined) {
|
|
241
249
|
const ordered = {};
|
|
@@ -253,6 +261,185 @@ function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pa
|
|
|
253
261
|
/** Add this. prefix to variable references in an expression */
|
|
254
262
|
// blueos-pack's pathTestRE: matches simple paths like a.b.c, a['key'], a[0], a[$idx]
|
|
255
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
|
+
}
|
|
433
|
+
function tryEvalStaticShowLiteral(expr) {
|
|
434
|
+
if (/^'[^']*'$/.test(expr) || /^"[^"]*"$/.test(expr)) {
|
|
435
|
+
const inner = expr.slice(1, -1);
|
|
436
|
+
return inner.length > 0;
|
|
437
|
+
}
|
|
438
|
+
if (expr === 'undefined' || expr === 'null' || expr === 'NaN') return false;
|
|
439
|
+
if (expr === 'Infinity') return true;
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
256
443
|
/**
|
|
257
444
|
* Normalize a media query condition text from CSS Level 4 syntax to Level 3.
|
|
258
445
|
*
|
|
@@ -572,11 +759,18 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
572
759
|
if (name === 'class') {
|
|
573
760
|
className = value.replace(/\s+/g, ' ').trim();
|
|
574
761
|
} else if (name.startsWith('@') || name.startsWith('on') && name.length > 2) {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
|
|
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('(')) {
|
|
580
774
|
// Add evt as last parameter
|
|
581
775
|
const withEvt = evtVal.replace(/\)$/, evtVal.includes('()') ? 'evt)' : ', evt)');
|
|
582
776
|
events[`$${evtName}`] = `function (evt) { return ${addThisPrefix(withEvt)}; }`;
|
|
@@ -612,13 +806,20 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
612
806
|
} else if (name === 'show') {
|
|
613
807
|
const showVal = value.replace(/\{\{|\}\}/g, '').trim();
|
|
614
808
|
if (showVal === 'true') attrObj['show'] = true;else if (showVal === 'false') attrObj['show'] = false;else {
|
|
615
|
-
// Try to evaluate as literal
|
|
616
809
|
try {
|
|
617
810
|
const v = JSON.parse(showVal);
|
|
618
811
|
attrObj["show"] = v;
|
|
619
812
|
} catch {
|
|
620
|
-
|
|
621
|
-
|
|
813
|
+
const staticVal = tryEvalStaticShowLiteral(showVal);
|
|
814
|
+
if (staticVal !== undefined) {
|
|
815
|
+
attrObj['show'] = staticVal;
|
|
816
|
+
} else if (pathTestRE.test(showVal)) {
|
|
817
|
+
attrObj['$show'] = showVal;
|
|
818
|
+
hasBind = true;
|
|
819
|
+
} else {
|
|
820
|
+
attrObj['$show'] = `function () { return ${addThisPrefix(showVal)}; }`;
|
|
821
|
+
hasBind = true;
|
|
822
|
+
}
|
|
622
823
|
}
|
|
623
824
|
}
|
|
624
825
|
} else if (name === 'for') {
|
|
@@ -727,11 +928,16 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
727
928
|
} else if (/\{\{.*\}\}/.test(value)) {
|
|
728
929
|
// Dynamic binding - build expression from mixed static/dynamic parts
|
|
729
930
|
const parts = value.split(/(\{\{[\s\S]*?\}\})/);
|
|
730
|
-
|
|
931
|
+
let exprs = parts.filter(Boolean).map(p => {
|
|
731
932
|
const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
|
|
732
933
|
if (m) return addThisPrefix(m[1].trim());
|
|
733
934
|
return `'${p.replace(/'/g, "\\'")}'`;
|
|
734
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
|
+
}
|
|
735
941
|
const camelName = '$' + name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
736
942
|
const plainName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
737
943
|
// Handle literal values (null, true, false, numbers)
|
|
@@ -770,15 +976,8 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
770
976
|
// Normalize relative path attrs (src, href, alt) to absolute paths from src root.
|
|
771
977
|
// alt can also carry prose (e.g. "blank") — only rewrite when the value starts with
|
|
772
978
|
// ./ or ../ so image alt-text prose passes through untouched.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
// Resolve relative to pageDir, then make absolute from src root
|
|
776
|
-
const segments = pageDir.split('/').filter(Boolean).concat(staticVal.split('/'));
|
|
777
|
-
const stack = [];
|
|
778
|
-
for (const seg of segments) {
|
|
779
|
-
if (seg === '..') stack.pop();else if (seg && seg !== '.') stack.push(seg);
|
|
780
|
-
}
|
|
781
|
-
staticVal = '/' + stack.join('/');
|
|
979
|
+
if (staticVal && isRelAssetPath(staticVal) && PATH_ATTRS.has(name) && pageDir) {
|
|
980
|
+
staticVal = resolveRelFromPageDir(staticVal, pageDir);
|
|
782
981
|
}
|
|
783
982
|
attrObj[name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = staticVal;
|
|
784
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;
|
|
@@ -90,12 +90,43 @@ function rewriteSystemImports(code) {
|
|
|
90
90
|
replacement: parts.join(' ')
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
+
// Rewrite CJS require() calls for system modules: require('@system.xxx') → $app_require$("@app-module/system.xxx")
|
|
94
|
+
collectRequireEdits(ast, edits);
|
|
93
95
|
if (edits.length === 0) return code;
|
|
94
96
|
edits.sort((a, b) => b.start - a.start);
|
|
95
97
|
let out = code;
|
|
96
98
|
for (const e of edits) out = out.slice(0, e.start) + e.replacement + out.slice(e.end);
|
|
97
99
|
return out;
|
|
98
100
|
}
|
|
101
|
+
function collectRequireEdits(node, edits) {
|
|
102
|
+
if (!node || typeof node !== 'object') return;
|
|
103
|
+
if (node.type === 'CallExpression' && node.callee?.type === 'Identifier' && node.callee.name === 'require' && node.arguments?.length === 1 && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') {
|
|
104
|
+
const src = node.arguments[0].value;
|
|
105
|
+
let moduleName = null;
|
|
106
|
+
if (NS_PREFIX.test(src)) {
|
|
107
|
+
moduleName = src.replace(NS_PREFIX, '$1.');
|
|
108
|
+
} else if (src.startsWith(APP_MODULE_PREFIX)) {
|
|
109
|
+
moduleName = src.slice(APP_MODULE_PREFIX.length);
|
|
110
|
+
}
|
|
111
|
+
if (moduleName !== null) {
|
|
112
|
+
edits.push({
|
|
113
|
+
start: node.start,
|
|
114
|
+
end: node.end,
|
|
115
|
+
replacement: `$app_require$("${APP_MODULE_PREFIX}${moduleName}")`
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const key of Object.keys(node)) {
|
|
121
|
+
if (key === 'start' || key === 'end' || key === 'type') continue;
|
|
122
|
+
const val = node[key];
|
|
123
|
+
if (Array.isArray(val)) {
|
|
124
|
+
for (const child of val) collectRequireEdits(child, edits);
|
|
125
|
+
} else if (val && typeof val === 'object' && val.type) {
|
|
126
|
+
collectRequireEdits(val, edits);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
99
130
|
function loadUserViteConfig(projectPath, mode) {
|
|
100
131
|
const configPath = _path.default.join(projectPath, 'quickapp.config.js');
|
|
101
132
|
if (!_fsExtra.default.existsSync(configPath)) return {};
|
|
@@ -397,6 +428,7 @@ class ViteCompiler {
|
|
|
397
428
|
});
|
|
398
429
|
let templateNode = null;
|
|
399
430
|
const importMap = {}; // name -> path
|
|
431
|
+
const componentsMap = {}; // originalName -> path
|
|
400
432
|
for (const node of doc.childNodes) {
|
|
401
433
|
if (node.nodeName === 'template') templateNode = node;else if (node.nodeName === 'import') {
|
|
402
434
|
const nameAttr = node.attrs?.find(a => a.name === 'name');
|
|
@@ -405,6 +437,7 @@ class ViteCompiler {
|
|
|
405
437
|
const compAbsPath = _path.default.resolve(_path.default.dirname(resolvedEntry), srcAttr.value).replace(/\.ux$/, '');
|
|
406
438
|
const compPath = _path.default.relative(srcPath, compAbsPath).replace(/\\/g, '/');
|
|
407
439
|
importMap[nameAttr.value.toLowerCase()] = compPath;
|
|
440
|
+
componentsMap[nameAttr.value] = compPath;
|
|
408
441
|
}
|
|
409
442
|
}
|
|
410
443
|
}
|
|
@@ -444,7 +477,7 @@ class ViteCompiler {
|
|
|
444
477
|
const tplPath = _path.default.join(buildPath, `${pageName}.template.json`);
|
|
445
478
|
_fsExtra.default.ensureDirSync(_path.default.dirname(tplPath));
|
|
446
479
|
const mergedSheet = mergeStyleSheets(appStyleSheet, styleSheet);
|
|
447
|
-
_fsExtra.default.writeJSONSync(tplPath, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, mergedSheet, importMap, _path.default.dirname(pageName)), param.mode === 'production' ? undefined : {
|
|
480
|
+
_fsExtra.default.writeJSONSync(tplPath, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, mergedSheet, importMap, _path.default.dirname(pageName), componentsMap), param.mode === 'production' ? undefined : {
|
|
448
481
|
spaces: 2
|
|
449
482
|
});
|
|
450
483
|
// Worklist BFS over nested <import> chain so transitively imported
|
|
@@ -474,6 +507,7 @@ class ViteCompiler {
|
|
|
474
507
|
});
|
|
475
508
|
let compTpl = null;
|
|
476
509
|
const compImportMap = {};
|
|
510
|
+
const compComponentsMap = {};
|
|
477
511
|
for (const node of compDoc.childNodes) {
|
|
478
512
|
if (node.nodeName === 'template') compTpl = node;else if (node.nodeName === 'import') {
|
|
479
513
|
const n = node.attrs?.find(a => a.name === 'name');
|
|
@@ -482,6 +516,7 @@ class ViteCompiler {
|
|
|
482
516
|
const nestedAbsPath = _path.default.resolve(_path.default.dirname(compUxPath), s.value).replace(/\.ux$/, '');
|
|
483
517
|
const nestedPath = _path.default.relative(srcPath, nestedAbsPath).replace(/\\/g, '/');
|
|
484
518
|
compImportMap[n.value.toLowerCase()] = nestedPath;
|
|
519
|
+
compComponentsMap[n.value] = nestedPath;
|
|
485
520
|
if (!visited.has(nestedPath)) {
|
|
486
521
|
worklist.push({
|
|
487
522
|
compName: n.value.toLowerCase(),
|
|
@@ -509,7 +544,7 @@ class ViteCompiler {
|
|
|
509
544
|
compStyleSheet = (0, _TemplateCompiler.parseStyleArray)(csm);
|
|
510
545
|
}
|
|
511
546
|
const compSid = (0, _TemplateCompiler.getStyleObjectId)(compPath);
|
|
512
|
-
_fsExtra.default.writeJSONSync(compTplPath, (0, _TemplateCompiler.uxToTemplateJson)(compTpl, compSid, mergeStyleSheets(appStyleSheet, compStyleSheet), compImportMap, _path.default.dirname(compPath)), param.mode === 'production' ? undefined : {
|
|
547
|
+
_fsExtra.default.writeJSONSync(compTplPath, (0, _TemplateCompiler.uxToTemplateJson)(compTpl, compSid, mergeStyleSheets(appStyleSheet, compStyleSheet), compImportMap, _path.default.dirname(compPath), compComponentsMap), param.mode === 'production' ? undefined : {
|
|
513
548
|
spaces: 2
|
|
514
549
|
});
|
|
515
550
|
const compCssPath = _path.default.join(buildPath, `${compPath}.css.json`);
|
|
@@ -519,11 +554,7 @@ class ViteCompiler {
|
|
|
519
554
|
spaces: 2
|
|
520
555
|
});
|
|
521
556
|
const compJsPath = _path.default.join(buildPath, `${compPath}.js`);
|
|
522
|
-
|
|
523
|
-
const compObj = exportMatch ? exportMatch[1].trim() : '{}';
|
|
524
|
-
const compVarName = compName.replace(/-/g, '_') + '_default';
|
|
525
|
-
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`;
|
|
526
|
-
_fsExtra.default.writeFileSync(compJsPath, compJsContent);
|
|
557
|
+
_fsExtra.default.writeFileSync(compJsPath, buildComponentJs(compCode, compName, compSid, compPath));
|
|
527
558
|
}
|
|
528
559
|
}
|
|
529
560
|
}
|
|
@@ -599,13 +630,16 @@ class ViteCompiler {
|
|
|
599
630
|
});
|
|
600
631
|
let templateNode = null;
|
|
601
632
|
const importMap = {};
|
|
633
|
+
const componentsMap = {};
|
|
602
634
|
for (const node of doc.childNodes) {
|
|
603
635
|
if (node.nodeName === 'template') templateNode = node;else if (node.nodeName === 'import') {
|
|
604
636
|
const nameAttr = node.attrs?.find(a => a.name === 'name');
|
|
605
637
|
const srcAttr = node.attrs?.find(a => a.name === 'src');
|
|
606
638
|
if (nameAttr && srcAttr) {
|
|
607
639
|
const compPath = _path.default.resolve(_path.default.dirname(uxFile), srcAttr.value).replace(/\.ux$/, '');
|
|
608
|
-
|
|
640
|
+
const rel = _path.default.relative(srcPath, compPath).replace(/\\/g, '/');
|
|
641
|
+
importMap[nameAttr.value.toLowerCase()] = rel;
|
|
642
|
+
componentsMap[nameAttr.value] = rel;
|
|
609
643
|
}
|
|
610
644
|
}
|
|
611
645
|
}
|
|
@@ -635,7 +669,7 @@ class ViteCompiler {
|
|
|
635
669
|
// Generate template.json
|
|
636
670
|
if (templateNode) {
|
|
637
671
|
_fsExtra.default.ensureDirSync(_path.default.dirname(outputTpl));
|
|
638
|
-
_fsExtra.default.writeJSONSync(outputTpl, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, mergeStyleSheets(appStyleSheet, styleSheet), Object.keys(importMap).length ? importMap : undefined, _path.default.dirname(relPath)), param.mode === 'production' ? undefined : {
|
|
672
|
+
_fsExtra.default.writeJSONSync(outputTpl, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, mergeStyleSheets(appStyleSheet, styleSheet), Object.keys(importMap).length ? importMap : undefined, _path.default.dirname(relPath), Object.keys(componentsMap).length ? componentsMap : undefined), param.mode === 'production' ? undefined : {
|
|
639
673
|
spaces: 2
|
|
640
674
|
});
|
|
641
675
|
}
|
|
@@ -647,13 +681,15 @@ class ViteCompiler {
|
|
|
647
681
|
spaces: 2
|
|
648
682
|
});
|
|
649
683
|
// Generate js
|
|
650
|
-
const exportMatch = compCode.match(/export\s+default\s+([\s\S]*?);?\s*$/);
|
|
651
|
-
const compObj = exportMatch ? exportMatch[1].trim() : '{}';
|
|
652
684
|
const compName = _path.default.basename(relPath).toLowerCase();
|
|
653
|
-
const varName = compName.replace(/-/g, '_') + '_default';
|
|
654
685
|
_fsExtra.default.ensureDirSync(_path.default.dirname(outputJs));
|
|
655
|
-
_fsExtra.default.writeFileSync(outputJs,
|
|
656
|
-
} 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
|
+
}
|
|
657
693
|
}
|
|
658
694
|
}
|
|
659
695
|
async clean(param) {
|
|
@@ -669,6 +705,51 @@ function stripDeadDefProp(code) {
|
|
|
669
705
|
const withoutDefine = code.replace(/var __defProp = Object\.defineProperty;\n?/g, '');
|
|
670
706
|
return /\b__defProp\s*\(/.test(withoutDefine) ? code : withoutDefine;
|
|
671
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
|
+
}
|
|
672
753
|
function wrapOutput(code, entryName, isApp) {
|
|
673
754
|
const componentName = _path.default.parse(entryName).name;
|
|
674
755
|
const defineId = isApp ? '@app-component/app' : `@app-component/${componentName}`;
|
|
@@ -713,6 +794,29 @@ function wrapOutput(code, entryName, isApp) {
|
|
|
713
794
|
});
|
|
714
795
|
// Remove Object.defineProperty for Symbol.toStringTag
|
|
715
796
|
code = code.replace(/\s*Object\.defineProperty\(\w+,\s*Symbol\.toStringTag[^;]*;\s*/g, '');
|
|
797
|
+
// Rewrite __require("@system.*") → $app_require$("@app-module/system.*") in rolldown output.
|
|
798
|
+
// Rolldown emits __require when CJS require() survives bundling (aliased require, dynamic
|
|
799
|
+
// concat, or patterns the pre-transform couldn't catch). Also catch _require() which appears
|
|
800
|
+
// when the original code does `var _require = require; _require('@system.xxx')`.
|
|
801
|
+
const sysRequirePattern = /\b_?_?require\(\s*"(@(?:system|service|android|hap)\.[^"]+)"\s*\)/g;
|
|
802
|
+
code = code.replace(sysRequirePattern, (_, mod) => {
|
|
803
|
+
const moduleName = mod.replace(/^@(system|service|android|hap)\./, '$1.');
|
|
804
|
+
return `$app_require$("@app-module/${moduleName}")`;
|
|
805
|
+
});
|
|
806
|
+
// Same for @app-module/ prefix
|
|
807
|
+
code = code.replace(/\b_?_?require\(\s*"(@app-module\/[^"]+)"\s*\)/g, (_, mod) => {
|
|
808
|
+
const moduleName = mod.slice('@app-module/'.length);
|
|
809
|
+
return `$app_require$("@app-module/${moduleName}")`;
|
|
810
|
+
});
|
|
811
|
+
// Also handle single-quoted variants
|
|
812
|
+
code = code.replace(/\b_?_?require\(\s*'(@(?:system|service|android|hap)\.[^']+)'\s*\)/g, (_, mod) => {
|
|
813
|
+
const moduleName = mod.replace(/^@(system|service|android|hap)\./, '$1.');
|
|
814
|
+
return `$app_require$("@app-module/${moduleName}")`;
|
|
815
|
+
});
|
|
816
|
+
// Remove the __require polyfill definition if it's no longer referenced anywhere
|
|
817
|
+
if (!/\b__require\b/.test(code)) {
|
|
818
|
+
code = code.replace(/var __require\s*=\s*\/\*[^*]*\*\/\s*\(\(x\)\s*=>[\s\S]*?}\);\n?/g, '');
|
|
819
|
+
}
|
|
716
820
|
// Extract the default export variable
|
|
717
821
|
let exportVar = '_default';
|
|
718
822
|
const m1 = code.match(/export\s*\{\s*(\w+)\s+as\s+default\s*\}/);
|
|
@@ -770,6 +874,7 @@ function mergeStyleSheets(app, page) {
|
|
|
770
874
|
}
|
|
771
875
|
const __test = exports.__test = {
|
|
772
876
|
stripDeadDefProp,
|
|
773
|
-
mergeStyleSheets
|
|
877
|
+
mergeStyleSheets,
|
|
878
|
+
buildComponentJs
|
|
774
879
|
};
|
|
775
880
|
var _default = exports.default = ViteCompiler;
|
|
@@ -6,5 +6,9 @@ declare class Jsc {
|
|
|
6
6
|
private logBeforeInvoke;
|
|
7
7
|
private logAfterInvoke;
|
|
8
8
|
private static listFiles;
|
|
9
|
+
private assertJscVersion;
|
|
10
|
+
static wrapAllJsWithIife(buildPath: string): void;
|
|
11
|
+
private static assertLegacyGlobalBinding;
|
|
12
|
+
private static compareSemver;
|
|
9
13
|
}
|
|
10
14
|
export default Jsc;
|
|
@@ -9,6 +9,17 @@ var path = _interopRequireWildcard(require("path"));
|
|
|
9
9
|
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
|
+
const REQUIRED_JSC_VERSION = '1.0.9';
|
|
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';
|
|
22
|
+
const IIFE_WRAP_SUFFIX = '\n});\n';
|
|
12
23
|
class Jsc {
|
|
13
24
|
constructor(projectPath, buildPath) {
|
|
14
25
|
this.projectPath = projectPath;
|
|
@@ -23,8 +34,10 @@ class Jsc {
|
|
|
23
34
|
const beforeJsCount = Jsc.listFiles(buildPath, '.js').length;
|
|
24
35
|
return _sharedUtils.CommonUtil.requireNodeModule(projectPath, `@aiot-toolkit/jsc`).then(async module => {
|
|
25
36
|
this.logBeforeInvoke();
|
|
37
|
+
this.assertJscVersion();
|
|
38
|
+
Jsc.wrapAllJsWithIife(buildPath);
|
|
26
39
|
const start = Date.now();
|
|
27
|
-
const msg = await module.default(buildPath);
|
|
40
|
+
const msg = await module.default(buildPath, []);
|
|
28
41
|
const elapsed = Date.now() - start;
|
|
29
42
|
if (msg) {
|
|
30
43
|
_sharedUtils.ColorConsole.success({
|
|
@@ -119,5 +132,60 @@ class Jsc {
|
|
|
119
132
|
walk(dir);
|
|
120
133
|
return out;
|
|
121
134
|
}
|
|
135
|
+
assertJscVersion() {
|
|
136
|
+
const {
|
|
137
|
+
projectPath
|
|
138
|
+
} = this;
|
|
139
|
+
const jscName = '@aiot-toolkit/jsc';
|
|
140
|
+
try {
|
|
141
|
+
const pkgJsonPath = require.resolve(`${jscName}/package.json`, {
|
|
142
|
+
paths: [projectPath]
|
|
143
|
+
});
|
|
144
|
+
const pkg = require(pkgJsonPath);
|
|
145
|
+
const version = pkg.version || '0.0.0';
|
|
146
|
+
if (Jsc.compareSemver(version, REQUIRED_JSC_VERSION) < 0) {
|
|
147
|
+
_sharedUtils.ColorConsole.throw(`[jsc] ${jscName}@${version} is too old — need >= ${REQUIRED_JSC_VERSION}. ` + `Run: npm i ${jscName}@^${REQUIRED_JSC_VERSION}`);
|
|
148
|
+
throw new Error(`[jsc] ${jscName} must be >= ${REQUIRED_JSC_VERSION}, got ${version}`);
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (e?.message?.includes(`must be >= ${REQUIRED_JSC_VERSION}`)) throw e;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
static wrapAllJsWithIife(buildPath) {
|
|
155
|
+
for (const {
|
|
156
|
+
rel
|
|
157
|
+
} of Jsc.listFiles(buildPath, '.js')) {
|
|
158
|
+
const abs = path.join(buildPath, rel);
|
|
159
|
+
const body = fs.readFileSync(abs, 'utf-8');
|
|
160
|
+
if (body.startsWith('export default (function (__ctx)')) continue;
|
|
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`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
static compareSemver(a, b) {
|
|
183
|
+
const parse = v => v.replace(/^[\^~=v]/, '').split(/[.-]/).slice(0, 3).map(p => parseInt(p, 10) || 0);
|
|
184
|
+
const [a1, a2, a3] = parse(a);
|
|
185
|
+
const [b1, b2, b3] = parse(b);
|
|
186
|
+
if (a1 !== b1) return a1 - b1;
|
|
187
|
+
if (a2 !== b2) return a2 - b2;
|
|
188
|
+
return a3 - b3;
|
|
189
|
+
}
|
|
122
190
|
}
|
|
123
191
|
var _default = exports.default = Jsc;
|
|
@@ -153,8 +153,17 @@ class ZipUtil {
|
|
|
153
153
|
// basename from manifest.icon (e.g. `/resources/base/media/icon_small.png`
|
|
154
154
|
// → `icon_small.vug` at rpk root) so runtime + IDE previews can look it up
|
|
155
155
|
// by the manifest-declared name.
|
|
156
|
-
|
|
157
|
-
const
|
|
156
|
+
const runtimeIconPath = (() => {
|
|
157
|
+
const buildManifest = _path.default.join(buildDir, 'manifest.json');
|
|
158
|
+
if (_fsExtra.default.existsSync(buildManifest)) {
|
|
159
|
+
try {
|
|
160
|
+
return _fsExtra.default.readJSONSync(buildManifest).icon;
|
|
161
|
+
} catch {/* fall through */}
|
|
162
|
+
}
|
|
163
|
+
return config.icon;
|
|
164
|
+
})();
|
|
165
|
+
if (runtimeIconPath) {
|
|
166
|
+
const iconPath = _path.default.join(buildDir, runtimeIconPath.replace(/^\//, ''));
|
|
158
167
|
if (_fsExtra.default.existsSync(iconPath)) {
|
|
159
168
|
const iconBuf = _fsExtra.default.readFileSync(iconPath);
|
|
160
169
|
outerPackage.addResource({
|
package/lib/config/UxConfig.js
CHANGED
|
@@ -86,6 +86,9 @@ class UxConfig {
|
|
|
86
86
|
}, {
|
|
87
87
|
worker: _UxAfterCompile.default.copyResource,
|
|
88
88
|
workerDescribe: 'Copy resource files'
|
|
89
|
+
}, {
|
|
90
|
+
worker: _UxAfterCompile.default.relocateManifestIcon,
|
|
91
|
+
workerDescribe: 'Move manifest.icon to build root + rewrite refs'
|
|
89
92
|
}, {
|
|
90
93
|
worker: _UxAfterCompile.default.compressResource,
|
|
91
94
|
workerDescribe: 'Compress resource files'
|
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",
|