@aiot-toolkit/aiotpack 2.1.0-prender.15 → 2.1.0-prender.16

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.
@@ -3,6 +3,48 @@ import ICompileParam from '../interface/ICompileParam';
3
3
  import ICompiler from '../interface/ICompiler';
4
4
  import { ILog } from '@aiot-toolkit/shared-utils';
5
5
  import { IFileLaneContext } from 'file-lane';
6
+ /**
7
+ * Resolve an `<import src>` value to an absolute `.ux` path, accepting the
8
+ * extensionless and directory forms the loader also accepts.
9
+ */
10
+ declare function resolveImportSrc(fromUxFile: string, src: string): string | null;
11
+ /**
12
+ * `.ux` files reachable from the routed pages, following `<import src>` chains.
13
+ *
14
+ * aiot-toolkit 1.x only compiled routed pages (components were bundled into the
15
+ * host page), so a `.ux` that no route reaches never produced output. 2.x walks
16
+ * every `.ux` on disk, which ships dead files and surfaces their defects as
17
+ * build failures — prerender-apps/idiom has an unrouted `pages/index` whose
18
+ * leftover `import page from './index.js'` failed the build for a page no route
19
+ * can open.
20
+ */
21
+ declare function collectReachableUx(entryUxFiles: string[]): Set<string>;
22
+ /**
23
+ * Relative ESM imports a sub-component declares that nothing downstream can
24
+ * resolve: `.ux` imports are loader-generated and dropped later, and
25
+ * `@system.*`/`@app-module` imports become `$app_require$` calls.
26
+ */
27
+ declare function findUnbundledRelativeImports(code: string): string[];
28
+ /**
29
+ * Inline a sub-component's relative `.js`/`.ts` dependencies.
30
+ *
31
+ * An entry page is bundled by rolldown, so `import page from './index.js'`
32
+ * is inlined. A sub-component was instead compiled file-by-file by
33
+ * `UxLoaderUtils.compileUxToJavascript` with no bundler, leaving the statement
34
+ * in place — fatal, because the IIFE wrapper nests it in a function body. Run
35
+ * the same rolldown pipeline over the compiled component so such a dependency
36
+ * is resolved the way a page's is.
37
+ *
38
+ * The entry is a virtual module placed in the component's own directory, so
39
+ * relative specifiers resolve against the component without writing to disk.
40
+ * Components with no such import return unchanged, keeping the common case free.
41
+ */
42
+ declare function bundleComponentCode(compCode: string, uxFile: string, srcPath: string, param: IJavascriptCompileOption, userVite: {
43
+ define?: Record<string, string>;
44
+ plugins?: any[];
45
+ resolve?: any;
46
+ build?: any;
47
+ }): Promise<string>;
6
48
  declare class ViteCompiler implements ICompiler {
7
49
  private readonly context;
8
50
  private readonly onLog?;
@@ -22,11 +64,30 @@ declare function stripDeadDefProp(code: string): string;
22
64
  * leaves `vibrator` as a free identifier, which is why the 咪咕/百度地图 keyboard
23
65
  * could no longer vibrate.
24
66
  */
67
+ /**
68
+ * `<import name="comp-score" src="../Score/index.ux">` is compiled by the ux
69
+ * loader into `import comp_score_comp from '../Score/index.ux'`, but a
70
+ * sub-component is resolved purely through data: template.json carries
71
+ * `{type, import}` and the component's own `.js` registers
72
+ * `$app_define$("@app-component/<name>")`. The binding is never read, and
73
+ * leaving the statement in place is a hard error — the IIFE wrapper nests it
74
+ * inside a function body, where `import` is a syntax error.
75
+ *
76
+ * Only drop it when the binding is provably unused; a referenced binding means
77
+ * the loader contract changed and silently dropping it would reintroduce the
78
+ * free-identifier class of bug, so leave it for the caller's guard to report.
79
+ */
80
+ declare function dropUnusedUxImports(code: string): string;
25
81
  declare function buildComponentJs(compCode: string, compName: string, compSid: number, relPath: string): string;
26
82
  declare function mergeStyleSheets(app: any, page: any): any;
27
83
  export declare const __test: {
28
84
  stripDeadDefProp: typeof stripDeadDefProp;
29
85
  mergeStyleSheets: typeof mergeStyleSheets;
30
86
  buildComponentJs: typeof buildComponentJs;
87
+ dropUnusedUxImports: typeof dropUnusedUxImports;
88
+ collectReachableUx: typeof collectReachableUx;
89
+ resolveImportSrc: typeof resolveImportSrc;
90
+ findUnbundledRelativeImports: typeof findUnbundledRelativeImports;
91
+ bundleComponentCode: typeof bundleComponentCode;
31
92
  };
32
93
  export default ViteCompiler;
@@ -216,6 +216,57 @@ function extractLegacyDefinePluginEntries(projectPath, configPath, mode) {
216
216
  } catch {/* swallow config load errors */}
217
217
  return captured;
218
218
  }
219
+
220
+ /**
221
+ * Resolve an `<import src>` value to an absolute `.ux` path, accepting the
222
+ * extensionless and directory forms the loader also accepts.
223
+ */
224
+ function resolveImportSrc(fromUxFile, src) {
225
+ const base = _path.default.resolve(_path.default.dirname(fromUxFile), src);
226
+ for (const cand of [base, `${base}.ux`, _path.default.join(base, 'index.ux')]) {
227
+ if (cand.endsWith('.ux') && _fsExtra.default.existsSync(cand)) return cand;
228
+ }
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * `.ux` files reachable from the routed pages, following `<import src>` chains.
234
+ *
235
+ * aiot-toolkit 1.x only compiled routed pages (components were bundled into the
236
+ * host page), so a `.ux` that no route reaches never produced output. 2.x walks
237
+ * every `.ux` on disk, which ships dead files and surfaces their defects as
238
+ * build failures — prerender-apps/idiom has an unrouted `pages/index` whose
239
+ * leftover `import page from './index.js'` failed the build for a page no route
240
+ * can open.
241
+ */
242
+ function collectReachableUx(entryUxFiles) {
243
+ const parse5 = require('aiot-parse5');
244
+ const reachable = new Set();
245
+ const worklist = [...entryUxFiles];
246
+ while (worklist.length) {
247
+ const uxFile = worklist.pop();
248
+ const key = _path.default.resolve(uxFile);
249
+ if (reachable.has(key)) continue;
250
+ if (!_fsExtra.default.existsSync(key)) continue;
251
+ reachable.add(key);
252
+ let doc;
253
+ try {
254
+ doc = parse5.parseFragment(_fsExtra.default.readFileSync(key, 'utf-8'), {
255
+ scriptingEnabled: false
256
+ });
257
+ } catch {
258
+ continue;
259
+ }
260
+ for (const node of doc.childNodes ?? []) {
261
+ if (node.nodeName !== 'import') continue;
262
+ const srcAttr = node.attrs?.find(a => a.name === 'src');
263
+ if (!srcAttr) continue;
264
+ const resolved = resolveImportSrc(key, srcAttr.value);
265
+ if (resolved) worklist.push(resolved);
266
+ }
267
+ }
268
+ return reachable;
269
+ }
219
270
  function extractAppStyleLiteral(code) {
220
271
  const m = code.match(/var\s+\$app_style\$\s*=\s*\[/);
221
272
  if (!m) return null;
@@ -243,6 +294,161 @@ function extractAppStyleLiteral(code) {
243
294
  }
244
295
  return null;
245
296
  }
297
+ /** Single dynamic-import site for vite, whose types this tsconfig cannot see. */
298
+ async function loadViteBuild() {
299
+ const {
300
+ build
301
+ } = await import('vite');
302
+ return build;
303
+ }
304
+
305
+ /**
306
+ * Plugins shared by the page-entry build and the sub-component bundler, so a
307
+ * component's dependencies are transformed exactly like a page's.
308
+ */
309
+ function makeSharedPlugins(srcPath) {
310
+ return [{
311
+ name: 'image-asset-string',
312
+ enforce: 'pre',
313
+ load(id) {
314
+ // require("./x.png") in .ux <script> must resolve to a path string,
315
+ // matching webpack file-loader / vivo blueos-pack semantics. Without
316
+ // this hook rolldown treats each image as an ES module and emits
317
+ // `(init_x(), __toCommonJS(x_exports))` — the site assigns
318
+ // `{ default: fn }` to src and runtime cannot render.
319
+ const match = id.match(/\.(png|jpe?g|gif|svg|webp|bmp|ico)(\?.*)?$/i);
320
+ if (!match) return null;
321
+ const filePath = id.replace(/\?.*$/, '');
322
+ if (!_fsExtra.default.existsSync(filePath)) return null;
323
+ const relFromSrc = _path.default.relative(srcPath, filePath).split(_path.default.sep).join('/');
324
+ // Assets outside src/ (typically node_modules) fall through to the
325
+ // default asset handler — the caller is responsible for their path
326
+ // resolution. Vivo blueos-pack + webpack file-loader use rpk-root
327
+ // absolute paths (`/common/...`, `/dynamicAssets/name-hash.png`);
328
+ // runtime <image src> resolves against the rpk root, so relative
329
+ // `./common/...` from a nested page directory would misdirect.
330
+ if (relFromSrc.startsWith('..')) return null;
331
+ const runtimePath = '/' + relFromSrc;
332
+ return `module.exports = ${JSON.stringify(runtimePath)};`;
333
+ }
334
+ }, {
335
+ name: "cjs-shim",
336
+ enforce: "pre",
337
+ load(id) {
338
+ if (id.includes('node_modules') && !id.endsWith('.ux')) {
339
+ const content = _fsExtra.default.readFileSync(id, 'utf-8');
340
+ if (content.includes('module.exports') || content.includes('exports.')) {
341
+ return `var module = {exports:{}}; var exports = module.exports;\n${content}\nexport default module.exports;`;
342
+ }
343
+ }
344
+ return null;
345
+ }
346
+ }, {
347
+ name: 'system-to-app-require',
348
+ enforce: 'pre',
349
+ transform(code, id) {
350
+ const out = rewriteSystemImports(code);
351
+ return out !== code ? {
352
+ code: out,
353
+ map: null
354
+ } : null;
355
+ }
356
+ }, {
357
+ name: 'namespace-preserve',
358
+ enforce: 'pre',
359
+ transform(code, id) {
360
+ if (!/import\s*\*\s*as\s+\w+\s+from/.test(code)) return null;
361
+ // Rolldown resolves `import * as L from './x'; L.foo()` at compile time —
362
+ // if `foo` isn't exported by `./x` the call collapses to `(void 0)()` and
363
+ // the source identifier is lost. Rewriting the alias to a runtime variable
364
+ // (with webpack-style default-interop) preserves the identifier so runtime
365
+ // errors point at the actual source name.
366
+ const out = code.replace(/import\s*\*\s*as\s+(\w+)\s+from\s+(['"][^'"]+['"])/g, (_m, local, src) => {
367
+ const ns = `__${local}_ns`,
368
+ key = `__${local}_key`;
369
+ return `import * as ${ns} from ${src}; const ${key} = 'default'; const ${local} = ${ns}[${key}] != null ? ${ns}[${key}] : ${ns};`;
370
+ });
371
+ return out !== code ? {
372
+ code: out,
373
+ map: null
374
+ } : null;
375
+ }
376
+ }];
377
+ }
378
+
379
+ /**
380
+ * Relative ESM imports a sub-component declares that nothing downstream can
381
+ * resolve: `.ux` imports are loader-generated and dropped later, and
382
+ * `@system.*`/`@app-module` imports become `$app_require$` calls.
383
+ */
384
+ function findUnbundledRelativeImports(code) {
385
+ const out = [];
386
+ const re = /^[ \t]*import\s[^\n]*?\sfrom\s+(['"])(\.[^'"]*)\1/gm;
387
+ for (const m of code.matchAll(re)) {
388
+ if (!m[2].endsWith('.ux')) out.push(m[2]);
389
+ }
390
+ return out;
391
+ }
392
+
393
+ /**
394
+ * Inline a sub-component's relative `.js`/`.ts` dependencies.
395
+ *
396
+ * An entry page is bundled by rolldown, so `import page from './index.js'`
397
+ * is inlined. A sub-component was instead compiled file-by-file by
398
+ * `UxLoaderUtils.compileUxToJavascript` with no bundler, leaving the statement
399
+ * in place — fatal, because the IIFE wrapper nests it in a function body. Run
400
+ * the same rolldown pipeline over the compiled component so such a dependency
401
+ * is resolved the way a page's is.
402
+ *
403
+ * The entry is a virtual module placed in the component's own directory, so
404
+ * relative specifiers resolve against the component without writing to disk.
405
+ * Components with no such import return unchanged, keeping the common case free.
406
+ */
407
+ async function bundleComponentCode(compCode, uxFile, srcPath, param, userVite) {
408
+ if (findUnbundledRelativeImports(compCode).length === 0) return compCode;
409
+ const virtualId = _path.default.join(_path.default.dirname(uxFile), `__component_entry__${_path.default.basename(uxFile)}.js`);
410
+ const build = await loadViteBuild();
411
+ const result = await build({
412
+ root: _path.default.dirname(uxFile),
413
+ logLevel: 'silent',
414
+ define: userVite.define ?? {},
415
+ resolve: {
416
+ extensions: ['.js', '.ts', '.ux', '.json'],
417
+ ...(userVite.resolve ?? {})
418
+ },
419
+ build: {
420
+ write: false,
421
+ lib: {
422
+ entry: virtualId,
423
+ formats: ['es'],
424
+ fileName: () => 'component.js'
425
+ },
426
+ rollupOptions: {
427
+ external: id => id.startsWith('@app-module/') || /^@(system|service|android|hap)\./.test(id)
428
+ },
429
+ commonjsOptions: {
430
+ transformMixedEsModules: true
431
+ },
432
+ minify: false
433
+ },
434
+ plugins: [{
435
+ name: 'component-virtual-entry',
436
+ enforce: 'pre',
437
+ resolveId(source) {
438
+ return source === virtualId ? virtualId : null;
439
+ },
440
+ load(id) {
441
+ return id === virtualId ? compCode : null;
442
+ }
443
+ }, ...makeSharedPlugins(srcPath), ...(userVite.plugins ?? [])]
444
+ });
445
+ const outputs = Array.isArray(result) ? result[0]?.output : result?.output;
446
+ const code = outputs?.[0]?.code;
447
+ if (!code) {
448
+ throw new Error(`[component] ${_path.default.relative(srcPath, uxFile)} bundling produced no output; ` + `its relative import(s) ${findUnbundledRelativeImports(compCode).join(', ')} could not be inlined.`);
449
+ }
450
+ return code;
451
+ }
246
452
  class ViteCompiler {
247
453
  constructor(context, onLog) {
248
454
  this.context = context;
@@ -290,9 +496,7 @@ class ViteCompiler {
290
496
  entries: allEntryPaths
291
497
  };
292
498
  try {
293
- const {
294
- build
295
- } = await import('vite');
499
+ const build = await loadViteBuild();
296
500
  const result = await build({
297
501
  root: originalProjectPath,
298
502
  logLevel: 'silent',
@@ -341,73 +545,7 @@ class ViteCompiler {
341
545
  }
342
546
  }
343
547
  },
344
- plugins: [{
345
- name: 'image-asset-string',
346
- enforce: 'pre',
347
- load(id) {
348
- // require("./x.png") in .ux <script> must resolve to a path string,
349
- // matching webpack file-loader / vivo blueos-pack semantics. Without
350
- // this hook rolldown treats each image as an ES module and emits
351
- // `(init_x(), __toCommonJS(x_exports))` — the site assigns
352
- // `{ default: fn }` to src and runtime cannot render.
353
- const match = id.match(/\.(png|jpe?g|gif|svg|webp|bmp|ico)(\?.*)?$/i);
354
- if (!match) return null;
355
- const filePath = id.replace(/\?.*$/, '');
356
- if (!_fsExtra.default.existsSync(filePath)) return null;
357
- const relFromSrc = _path.default.relative(srcPath, filePath).split(_path.default.sep).join('/');
358
- // Assets outside src/ (typically node_modules) fall through to the
359
- // default asset handler — the caller is responsible for their path
360
- // resolution. Vivo blueos-pack + webpack file-loader use rpk-root
361
- // absolute paths (`/common/...`, `/dynamicAssets/name-hash.png`);
362
- // runtime <image src> resolves against the rpk root, so relative
363
- // `./common/...` from a nested page directory would misdirect.
364
- if (relFromSrc.startsWith('..')) return null;
365
- const runtimePath = '/' + relFromSrc;
366
- return `module.exports = ${JSON.stringify(runtimePath)};`;
367
- }
368
- }, {
369
- name: "cjs-shim",
370
- enforce: "pre",
371
- load(id) {
372
- if (id.includes('node_modules') && !id.endsWith('.ux')) {
373
- const content = _fsExtra.default.readFileSync(id, 'utf-8');
374
- if (content.includes('module.exports') || content.includes('exports.')) {
375
- return `var module = {exports:{}}; var exports = module.exports;\n${content}\nexport default module.exports;`;
376
- }
377
- }
378
- return null;
379
- }
380
- }, {
381
- name: 'system-to-app-require',
382
- enforce: 'pre',
383
- transform(code, id) {
384
- const out = rewriteSystemImports(code);
385
- return out !== code ? {
386
- code: out,
387
- map: null
388
- } : null;
389
- }
390
- }, {
391
- name: 'namespace-preserve',
392
- enforce: 'pre',
393
- transform(code, id) {
394
- if (!/import\s*\*\s*as\s+\w+\s+from/.test(code)) return null;
395
- // Rolldown resolves `import * as L from './x'; L.foo()` at compile time —
396
- // if `foo` isn't exported by `./x` the call collapses to `(void 0)()` and
397
- // the source identifier is lost. Rewriting the alias to a runtime variable
398
- // (with webpack-style default-interop) preserves the identifier so runtime
399
- // errors point at the actual source name.
400
- const out = code.replace(/import\s*\*\s*as\s+(\w+)\s+from\s+(['"][^'"]+['"])/g, (_m, local, src) => {
401
- const ns = `__${local}_ns`,
402
- key = `__${local}_key`;
403
- return `import * as ${ns} from ${src}; const ${key} = 'default'; const ${local} = ${ns}[${key}] != null ? ${ns}[${key}] : ${ns};`;
404
- });
405
- return out !== code ? {
406
- code: out,
407
- map: null
408
- } : null;
409
- }
410
- }, {
548
+ plugins: [...makeSharedPlugins(srcPath), {
411
549
  name: 'ux-loader',
412
550
  resolveId(source, importer) {
413
551
  if (source.endsWith('.ux')) {
@@ -554,7 +692,9 @@ class ViteCompiler {
554
692
  spaces: 2
555
693
  });
556
694
  const compJsPath = _path.default.join(buildPath, `${compPath}.js`);
557
- _fsExtra.default.writeFileSync(compJsPath, buildComponentJs(compCode, compName, compSid, compPath));
695
+ // Style data was already read off the unbundled code above.
696
+ const compBundled = await bundleComponentCode(compCode, compUxPath, srcPath, param, userVite);
697
+ _fsExtra.default.writeFileSync(compJsPath, buildComponentJs(compBundled, compName, compSid, compPath));
558
698
  }
559
699
  }
560
700
  }
@@ -615,9 +755,15 @@ class ViteCompiler {
615
755
  };
616
756
  findUx(srcPath);
617
757
  const compiledEntries = new Set(Object.values(entries).map(v => _path.default.resolve(originalProjectPath, v.split('?')[0])));
758
+ const reachableUx = collectReachableUx([...compiledEntries]);
759
+ const skippedUx = [];
618
760
  for (const uxFile of allUxFiles) {
619
761
  if (compiledEntries.has(uxFile)) continue; // already compiled as entry
620
762
  if (uxFile.replace(/\\/g, '/').endsWith('/app.ux')) continue; // app.ux handled separately
763
+ if (!reachableUx.has(_path.default.resolve(uxFile))) {
764
+ skippedUx.push(_path.default.relative(srcPath, uxFile).replace(/\\/g, '/'));
765
+ continue;
766
+ }
621
767
  const relPath = _path.default.relative(srcPath, uxFile).replace(/\.ux$/, '');
622
768
  const outputJs = _path.default.join(buildPath, `${relPath}.js`);
623
769
  const outputTpl = _path.default.join(buildPath, `${relPath}.template.json`);
@@ -683,7 +829,8 @@ class ViteCompiler {
683
829
  // Generate js
684
830
  const compName = _path.default.basename(relPath).toLowerCase();
685
831
  _fsExtra.default.ensureDirSync(_path.default.dirname(outputJs));
686
- _fsExtra.default.writeFileSync(outputJs, buildComponentJs(compCode, compName, sid, relPath));
832
+ const bundled = await bundleComponentCode(compCode, uxFile, srcPath, param, userVite);
833
+ _fsExtra.default.writeFileSync(outputJs, buildComponentJs(bundled, compName, sid, relPath));
687
834
  } catch (e) {
688
835
  // A component that fails to emit renders but has no behaviour, so don't
689
836
  // swallow the guard errors that report exactly why.
@@ -691,6 +838,12 @@ class ViteCompiler {
691
838
  console.warn(`vite: failed to emit component ${relPath}:`, e?.message ?? e);
692
839
  }
693
840
  }
841
+ if (skippedUx.length) {
842
+ this.onLog?.([{
843
+ level: 3,
844
+ message: [`Skipped ${skippedUx.length} .ux file(s) not reachable from any route or <import> chain ` + `(matches aiot-toolkit 1.x, which only compiled routed pages): ${skippedUx.join(', ')}. ` + `If one of these is opened by a dynamic router call, add it to manifest.json router.pages.`]
845
+ }]);
846
+ }
694
847
  }
695
848
  async clean(param) {
696
849
  const {
@@ -717,6 +870,27 @@ function stripDeadDefProp(code) {
717
870
  * leaves `vibrator` as a free identifier, which is why the 咪咕/百度地图 keyboard
718
871
  * could no longer vibrate.
719
872
  */
873
+ /**
874
+ * `<import name="comp-score" src="../Score/index.ux">` is compiled by the ux
875
+ * loader into `import comp_score_comp from '../Score/index.ux'`, but a
876
+ * sub-component is resolved purely through data: template.json carries
877
+ * `{type, import}` and the component's own `.js` registers
878
+ * `$app_define$("@app-component/<name>")`. The binding is never read, and
879
+ * leaving the statement in place is a hard error — the IIFE wrapper nests it
880
+ * inside a function body, where `import` is a syntax error.
881
+ *
882
+ * Only drop it when the binding is provably unused; a referenced binding means
883
+ * the loader contract changed and silently dropping it would reintroduce the
884
+ * free-identifier class of bug, so leave it for the caller's guard to report.
885
+ */
886
+ function dropUnusedUxImports(code) {
887
+ const uxImport = /^[ \t]*import\s+(\w+)\s+from\s+(['"])([^'"]*\.ux)\2[ \t]*;?[ \t]*$\n?/gm;
888
+ return code.replace(uxImport, (stmt, binding) => {
889
+ const rest = code.replace(stmt, '');
890
+ const used = new RegExp(`\\b${binding}\\b`).test(rest);
891
+ return used ? stmt : '';
892
+ });
893
+ }
720
894
  function buildComponentJs(compCode, compName, compSid, relPath) {
721
895
  let code = compCode;
722
896
  // The raw `var $app_style$ = [...]` array is superseded by the $app_style$<sid>
@@ -733,7 +907,22 @@ function buildComponentJs(compCode, compName, compSid, relPath) {
733
907
  }
734
908
  // Turn @system.*/@app-module imports into $app_require$ calls, same as entries.
735
909
  code = rewriteSystemImports(code);
736
- let defaultExpr = '{}';
910
+ const hadDefault = /export\s+default\s|\bas\s+(?:default\b|["']default["'])/.test(code);
911
+ // Bundled components come straight from rolldown, which leaves region markers
912
+ // and runtime helpers behind and re-exports the default as
913
+ // `export { X as default }` rather than `export default`.
914
+ code = stripRolldownArtifacts(code);
915
+ // Must run before the `export default` expression is sliced off, otherwise a
916
+ // binding referenced only inside that expression reads as unused.
917
+ code = dropUnusedUxImports(code);
918
+ let defaultExpr = null;
919
+ const asDefault = code.match(/export\s*\{\s*(\w+)\s+as\s+default\s*(?:,[^}]*)?\}\s*;?/);
920
+ if (asDefault) {
921
+ defaultExpr = asDefault[1];
922
+ code = code.replace(asDefault[0], '');
923
+ }
924
+ // Remaining named exports have no consumer in a $app_define$ module.
925
+ code = code.replace(/^[ \t]*export\s*\{[^}]*\}\s*;?[ \t]*$\n?/gm, '');
737
926
  const edIdx = code.search(/export\s+default\s+/);
738
927
  if (edIdx !== -1) {
739
928
  const kw = code.slice(edIdx).match(/^export\s+default\s+/);
@@ -746,37 +935,22 @@ function buildComponentJs(compCode, compName, compSid, relPath) {
746
935
  if (leftoverImport) {
747
936
  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
937
  }
938
+ if (hadDefault && defaultExpr === null) {
939
+ // An unrecognized default-export shape would register an empty component:
940
+ // it renders but has no data, props or handlers.
941
+ throw new Error(`[component] ${relPath} declares a default export but none was extracted, ` + `which would register an empty component.`);
942
+ }
749
943
  const varName = compName.replace(/-/g, '_') + '_default';
750
944
  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');
945
+ const emittedDefault = defaultExpr ?? '{}';
946
+ return [`let $style$${compSid} = {"@info":{"styleObjectId":${compSid}}};`, `const $app_style$${compSid} = $style$${compSid};`, ...(moduleCode ? [moduleCode] : []), `var ${varName} = ${emittedDefault};`, `$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
947
  }
753
- function wrapOutput(code, entryName, isApp) {
754
- const componentName = _path.default.parse(entryName).name;
755
- const defineId = isApp ? '@app-component/app' : `@app-component/${componentName}`;
756
- const bootstrapId = isApp ? '@app-application/app' : `@app-component/${componentName}`;
757
- // Remove IIFE wrapper: var _page_ = (function(...) { ... return xxx; })(...);
758
- // Only remove the FIRST line and the LAST return+closing
759
- if (code.startsWith('var _page_')) {
760
- // Remove first line (the IIFE opening)
761
- const firstNewline = code.indexOf('\n');
762
- code = code.slice(firstNewline + 1);
763
- // Remove the last return statement and closing })();
764
- const lastReturn = code.lastIndexOf('\treturn ');
765
- if (lastReturn !== -1) {
766
- const afterReturn = code.indexOf('\n', lastReturn);
767
- code = code.slice(0, lastReturn) + code.slice(afterReturn + 1);
768
- }
769
- // Remove the closing })(); or })(...)
770
- const lastClose = code.lastIndexOf('})();');
771
- if (lastClose !== -1) {
772
- code = code.slice(0, lastClose);
773
- } else {
774
- const lastClose2 = code.lastIndexOf('})(');
775
- if (lastClose2 !== -1) {
776
- code = code.slice(0, lastClose2);
777
- }
778
- }
779
- }
948
+
949
+ /**
950
+ * Normalize rolldown output: drop its region markers and runtime helpers, and
951
+ * turn surviving `require("@system.*")` calls into `$app_require$`.
952
+ */
953
+ function stripRolldownArtifacts(code) {
780
954
  // Remove rolldown region comments
781
955
  code = code.replace(/\/\/#region[^\n]*\n/g, '');
782
956
  code = code.replace(/\/\/#endregion[^\n]*/g, '');
@@ -817,6 +991,36 @@ function wrapOutput(code, entryName, isApp) {
817
991
  if (!/\b__require\b/.test(code)) {
818
992
  code = code.replace(/var __require\s*=\s*\/\*[^*]*\*\/\s*\(\(x\)\s*=>[\s\S]*?}\);\n?/g, '');
819
993
  }
994
+ return code;
995
+ }
996
+ function wrapOutput(code, entryName, isApp) {
997
+ const componentName = _path.default.parse(entryName).name;
998
+ const defineId = isApp ? '@app-component/app' : `@app-component/${componentName}`;
999
+ const bootstrapId = isApp ? '@app-application/app' : `@app-component/${componentName}`;
1000
+ // Remove IIFE wrapper: var _page_ = (function(...) { ... return xxx; })(...);
1001
+ // Only remove the FIRST line and the LAST return+closing
1002
+ if (code.startsWith('var _page_')) {
1003
+ // Remove first line (the IIFE opening)
1004
+ const firstNewline = code.indexOf('\n');
1005
+ code = code.slice(firstNewline + 1);
1006
+ // Remove the last return statement and closing })();
1007
+ const lastReturn = code.lastIndexOf('\treturn ');
1008
+ if (lastReturn !== -1) {
1009
+ const afterReturn = code.indexOf('\n', lastReturn);
1010
+ code = code.slice(0, lastReturn) + code.slice(afterReturn + 1);
1011
+ }
1012
+ // Remove the closing })(); or })(...)
1013
+ const lastClose = code.lastIndexOf('})();');
1014
+ if (lastClose !== -1) {
1015
+ code = code.slice(0, lastClose);
1016
+ } else {
1017
+ const lastClose2 = code.lastIndexOf('})(');
1018
+ if (lastClose2 !== -1) {
1019
+ code = code.slice(0, lastClose2);
1020
+ }
1021
+ }
1022
+ }
1023
+ code = stripRolldownArtifacts(code);
820
1024
  // Extract the default export variable
821
1025
  let exportVar = '_default';
822
1026
  const m1 = code.match(/export\s*\{\s*(\w+)\s+as\s+default\s*\}/);
@@ -875,6 +1079,11 @@ function mergeStyleSheets(app, page) {
875
1079
  const __test = exports.__test = {
876
1080
  stripDeadDefProp,
877
1081
  mergeStyleSheets,
878
- buildComponentJs
1082
+ buildComponentJs,
1083
+ dropUnusedUxImports,
1084
+ collectReachableUx,
1085
+ resolveImportSrc,
1086
+ findUnbundledRelativeImports,
1087
+ bundleComponentCode
879
1088
  };
880
1089
  var _default = exports.default = ViteCompiler;
@@ -20,6 +20,12 @@ const REQUIRED_JSC_VERSION = '1.0.9';
20
20
  // not `global` — after the rebind `global.console` would look up ferryGlobal.
21
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
22
  const IIFE_WRAP_SUFFIX = '\n});\n';
23
+
24
+ // `-s` strips debug info. `-m` (force module) is deliberately omitted: aiotjsc
25
+ // autodetects module-ness and the IIFE wrapper's `export default` already makes
26
+ // it detect module. Passing an explicit array also suppresses jsc's own
27
+ // `undefined → ["-ms"]` fallback, so this list is the full flag set.
28
+ const JSC_ARGS = ['-s'];
23
29
  class Jsc {
24
30
  constructor(projectPath, buildPath) {
25
31
  this.projectPath = projectPath;
@@ -37,7 +43,7 @@ class Jsc {
37
43
  this.assertJscVersion();
38
44
  Jsc.wrapAllJsWithIife(buildPath);
39
45
  const start = Date.now();
40
- const msg = await module.default(buildPath, []);
46
+ const msg = await module.default(buildPath, JSC_ARGS);
41
47
  const elapsed = Date.now() - start;
42
48
  if (msg) {
43
49
  _sharedUtils.ColorConsole.success({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiot-toolkit/aiotpack",
3
- "version": "2.1.0-prender.15",
3
+ "version": "2.1.0-prender.16",
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.15",
23
- "@aiot-toolkit/parser": "2.1.0-prender.15",
24
- "@aiot-toolkit/shared-utils": "2.1.0-prender.15",
22
+ "@aiot-toolkit/generator": "2.1.0-prender.16",
23
+ "@aiot-toolkit/parser": "2.1.0-prender.16",
24
+ "@aiot-toolkit/shared-utils": "2.1.0-prender.16",
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.15",
31
+ "file-lane": "2.1.0-prender.16",
32
32
  "file-loader": "^6.2.0",
33
33
  "fs-extra": "^11.2.0",
34
34
  "jsrsasign": "^11.1.0",