@dimina/compiler 1.0.16 → 1.1.0

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.
@@ -2,6 +2,9 @@ import path from "node:path";
2
2
  import process from "node:process";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
+ import { parseSync } from "oxc-parser";
6
+ import { walk } from "oxc-walker";
7
+ import crypto from "node:crypto";
5
8
  //#region src/common/path-utils.js
6
9
  var WINDOWS_FS_PATH_RE = /^(?:[a-zA-Z]:[\\/]|\\\\)/;
7
10
  function isWindowsFsPath(targetPath) {
@@ -29,19 +32,6 @@ function getRelativePosixPath(targetPath, rootPath) {
29
32
  return normalizeToPosixPath(targetPath).replace(normalizeToPosixPath(rootPath), "").replace(/^\//, "");
30
33
  }
31
34
  //#endregion
32
- //#region src/common/art.js
33
- var artCode = `
34
- ██████╗ ██╗███╗ ███╗██╗███╗ ██╗ █████╗
35
- ██╔══██╗██║████╗ ████║██║████╗ ██║██╔══██╗
36
- ██║ ██║██║██╔████╔██║██║██╔██╗ ██║███████║
37
- ██║ ██║██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║
38
- ██████╔╝██║██║ ╚═╝ ██║██║██║ ╚████║██║ ██║
39
- ╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝
40
- `;
41
- function art_default() {
42
- console.log(artCode);
43
- }
44
- //#endregion
45
35
  //#region src/common/utils.js
46
36
  function hasCompileInfo(modulePath, list, preList) {
47
37
  const mergeList = Array.isArray(preList) ? [...preList, ...list] : list;
@@ -58,19 +48,31 @@ function getAbsolutePath(workPath, pagePath, src) {
58
48
  return path.resolve(workPath, relativePath, src);
59
49
  }
60
50
  var assetsMap = {};
51
+ function isPathInside(rootPath, targetPath) {
52
+ const relativePath = path.relative(rootPath, targetPath);
53
+ return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
54
+ }
55
+ function resolveAssetSourcePath(workPath, pagePath, src) {
56
+ const projectRoot = path.resolve(workPath);
57
+ if (src.startsWith("/")) return path.resolve(projectRoot, `.${src}`);
58
+ const normalizedPagePath = path.resolve(pagePath);
59
+ const pageDirectory = path.isAbsolute(pagePath) && isPathInside(projectRoot, normalizedPagePath) ? path.dirname(normalizedPagePath) : path.resolve(projectRoot, path.dirname(pagePath.replace(/^[/\\]+/, "")));
60
+ const resolvedPath = path.resolve(pageDirectory, src);
61
+ if (isPathInside(projectRoot, resolvedPath)) return resolvedPath;
62
+ return path.resolve(projectRoot, src.replace(/^(?:\.\.[/\\])+/, ""));
63
+ }
61
64
  /**
62
65
  * 将静态资源存储到 static 文件夹
63
66
  */
64
67
  function collectAssets(workPath, pagePath, src, targetPath, appId) {
65
68
  if (src.startsWith("http") || src.startsWith("//")) return src;
66
69
  if (!/\.(?:png|jpe?g|gif|svg)(?:\?.*)?$/.test(src)) return src;
67
- const relativePath = pagePath.split("/").slice(0, -1).join("/");
68
- const absolutePath = src.startsWith("/") ? workPath + src : path.resolve(workPath, relativePath, src);
70
+ const absolutePath = resolveAssetSourcePath(workPath, pagePath, src);
69
71
  if (assetsMap[absolutePath]) return assetsMap[absolutePath];
70
72
  try {
71
73
  const ext = `.${src.split(".").pop()}`;
72
74
  const dirPath = absolutePath.split(path.sep).slice(0, -1).join("/");
73
- const prefix = uuid();
75
+ const prefix = uuid(dirPath);
74
76
  const targetStatic = `${targetPath}/main/static`;
75
77
  if (!fs.existsSync(targetStatic)) fs.mkdirSync(targetStatic, { recursive: true });
76
78
  getFilesWithExtension(dirPath, ext).forEach((file) => {
@@ -96,15 +98,16 @@ function isString(o) {
96
98
  function transformRpx(styleText) {
97
99
  if (!isString(styleText)) return styleText;
98
100
  return styleText.replace(/([+-]?\d+(?:\.\d+)?)rpx/g, (_, pixel) => {
99
- return `${Number(pixel)}rem`;
101
+ const viewportWidth = Number((Number(pixel) / 7.5).toFixed(6));
102
+ return `${Object.is(viewportWidth, -0) ? 0 : viewportWidth}vw`;
100
103
  });
101
104
  }
102
- function uuid() {
103
- return Math.random().toString(36).slice(2, 7);
105
+ function uuid(str) {
106
+ return crypto.createHash("sha256").update(str).digest().readBigUInt64BE(0).toString(36);
104
107
  }
105
108
  var tagWhiteList = [
106
109
  "page",
107
- "wrapper",
110
+ "component-host",
108
111
  "block",
109
112
  "button",
110
113
  "camera",
@@ -145,6 +148,24 @@ var tagWhiteList = [
145
148
  "view",
146
149
  "web-view"
147
150
  ];
151
+ var miniProgramBuiltinTags = /* @__PURE__ */ new Set([
152
+ ...tagWhiteList,
153
+ "canvas",
154
+ "match-media",
155
+ "page-container",
156
+ "share-element",
157
+ "editor",
158
+ "audio",
159
+ "channel-live",
160
+ "channel-video",
161
+ "live-player",
162
+ "live-pusher",
163
+ "voip-room",
164
+ "ad",
165
+ "ad-custom",
166
+ "official-account",
167
+ "xr-frame"
168
+ ]);
148
169
  //#endregion
149
170
  //#region src/common/npm-resolver.js
150
171
  /**
@@ -317,24 +338,119 @@ var NpmResolver = class {
317
338
  var pathInfo = {};
318
339
  var configInfo = {};
319
340
  var npmResolver = null;
341
+ var DEFAULT_TEMPLATE_EXTS = [".wxml", ".ddml"];
342
+ var DEFAULT_STYLE_EXTS = [
343
+ ".wxss",
344
+ ".ddss",
345
+ ".less",
346
+ ".scss",
347
+ ".sass"
348
+ ];
349
+ var DEFAULT_VIEW_SCRIPT_EXTS = [".wxs"];
350
+ var DEFAULT_VIEW_SCRIPT_TAGS = ["wxs", "dds"];
351
+ var CUSTOM_TAB_BAR_COMPONENT_PATH = "/custom-tab-bar/index";
352
+ var STYLE_ISOLATION_VALUES = /* @__PURE__ */ new Set([
353
+ "isolated",
354
+ "apply-shared",
355
+ "shared"
356
+ ]);
357
+ var RESERVED_EXTS = /* @__PURE__ */ new Set([
358
+ ...DEFAULT_TEMPLATE_EXTS,
359
+ ...DEFAULT_STYLE_EXTS,
360
+ ...DEFAULT_VIEW_SCRIPT_EXTS,
361
+ ".js",
362
+ ".ts",
363
+ ".json"
364
+ ]);
365
+ var compilerOptions = normalizeFileTypes();
366
+ /**
367
+ * 将单项规范化为扩展名:去除首尾空白、转小写并补一个前导点。
368
+ * 仅接受字母、数字、连字符和下划线;空字符串、路径分隔符或其他元字符
369
+ * 均返回 null,由调用方丢弃。扩展名会用于生成尾部匹配正则和查找文件,
370
+ * 放行元字符可能导致误匹配。
371
+ */
372
+ function normalizeExt(raw) {
373
+ if (typeof raw !== "string") return null;
374
+ const v = raw.trim().toLowerCase().replace(/^\.+/, "");
375
+ if (!/^[a-z0-9_-]+$/.test(v)) return null;
376
+ return `.${v}`;
377
+ }
378
+ /**
379
+ * 将单项规范化为内联标签名:去除首尾空白、转小写并移除前导点。
380
+ * 标签名会用于拼接 Cheerio 选择器(如 transTagWxs),因此必须以字母开头,
381
+ * 且只能包含字母、数字、连字符和下划线。拒绝选择器元字符,避免 'qds,view'
382
+ * 误选并删除 <view>,破坏编译产物。
383
+ */
384
+ function normalizeTag(raw) {
385
+ if (typeof raw !== "string") return null;
386
+ const v = raw.trim().toLowerCase().replace(/^\.+/, "");
387
+ if (!/^[a-z][a-z0-9_-]*$/.test(v)) return null;
388
+ return v;
389
+ }
390
+ /**
391
+ * 合并并去重内置项和自定义项;内置项在前,顺序即同名文件的查找优先级。
392
+ * 传入 reserved 时,落在其中的自定义项被丢弃(防止占用其他角色/逻辑/配置的扩展名)。
393
+ */
394
+ function mergeUnique(builtins, custom, normalizer, reserved) {
395
+ const out = [...builtins];
396
+ const seen = new Set(builtins);
397
+ if (Array.isArray(custom)) for (const raw of custom) {
398
+ const n = normalizer(raw);
399
+ if (n && !seen.has(n) && !reserved?.has(n)) {
400
+ seen.add(n);
401
+ out.push(n);
402
+ }
403
+ }
404
+ return out;
405
+ }
406
+ /**
407
+ * 根据 options.fileTypes 生成本次构建使用的自定义扩展名和标签。
408
+ * viewScript 同时用于生成文件扩展名和内联标签。
409
+ */
410
+ function normalizeFileTypes(fileTypes = {}) {
411
+ const ft = fileTypes || {};
412
+ return {
413
+ templateExts: mergeUnique(DEFAULT_TEMPLATE_EXTS, ft.template, normalizeExt, RESERVED_EXTS),
414
+ styleExts: mergeUnique(DEFAULT_STYLE_EXTS, ft.style, normalizeExt, RESERVED_EXTS),
415
+ viewScriptExts: mergeUnique(DEFAULT_VIEW_SCRIPT_EXTS, ft.viewScript, normalizeExt, RESERVED_EXTS),
416
+ viewScriptTags: mergeUnique(DEFAULT_VIEW_SCRIPT_TAGS, ft.viewScript, normalizeTag)
417
+ };
418
+ }
320
419
  /**
321
420
  * 持久化编译过程的上下文
421
+ * @param {string} workPath 编译工作目录
422
+ * @param {{ fileTypes?: { template?: string[], style?: string[], viewScript?: string[] } }} [options] 构建选项
322
423
  */
323
- function storeInfo(workPath) {
424
+ function storeInfo(workPath, options = {}) {
324
425
  storePathInfo(workPath);
325
426
  storeProjectConfig();
326
427
  storeAppConfig();
327
428
  storePageConfig();
429
+ compilerOptions = normalizeFileTypes(options.fileTypes);
328
430
  return {
329
431
  pathInfo,
330
- configInfo
432
+ configInfo,
433
+ compilerOptions
331
434
  };
332
435
  }
333
436
  function resetStoreInfo(opts) {
334
437
  pathInfo = opts.pathInfo;
335
438
  configInfo = opts.configInfo;
439
+ compilerOptions = opts.compilerOptions || normalizeFileTypes();
336
440
  if (pathInfo.workPath) npmResolver = new NpmResolver(pathInfo.workPath);
337
441
  }
442
+ function getTemplateExts() {
443
+ return compilerOptions.templateExts;
444
+ }
445
+ function getStyleExts() {
446
+ return compilerOptions.styleExts;
447
+ }
448
+ function getViewScriptExts() {
449
+ return compilerOptions.viewScriptExts;
450
+ }
451
+ function getViewScriptTags() {
452
+ return compilerOptions.viewScriptTags;
453
+ }
338
454
  function storePathInfo(workPath) {
339
455
  pathInfo.workPath = workPath;
340
456
  if (process.env.TARGET_PATH) pathInfo.targetPath = process.env.TARGET_PATH;
@@ -394,6 +510,43 @@ function storePageConfig() {
394
510
  if (subPackages) subPackages.forEach((subPkg) => {
395
511
  collectionPageJson(subPkg.pages, subPkg.root);
396
512
  });
513
+ storeCustomTabBarConfig();
514
+ }
515
+ /**
516
+ * 微信会把 custom-tab-bar/index 作为每个 tab 页的直属组件创建。业务页面
517
+ * 不需要在 usingComponents 中显式声明它,因此编译阶段补一个内部组件引用,
518
+ * 让逻辑、视图和样式三个编译器都能沿现有依赖图收集该组件。
519
+ */
520
+ function storeCustomTabBarConfig() {
521
+ const tabBar = configInfo.appInfo?.tabBar;
522
+ if (tabBar?.custom !== true || !Array.isArray(tabBar.list)) return;
523
+ const componentJsonPath = path.join(pathInfo.workPath, "custom-tab-bar/index.json");
524
+ if (!fs.existsSync(componentJsonPath)) {
525
+ console.warn("[env] tabBar.custom 已启用,但找不到 custom-tab-bar/index.json");
526
+ return;
527
+ }
528
+ const dependencyName = `dimina-${uuid(CUSTOM_TAB_BAR_COMPONENT_PATH)}`;
529
+ storeComponentConfig({ usingComponents: { [dependencyName]: CUSTOM_TAB_BAR_COMPONENT_PATH } }, path.join(pathInfo.workPath, "app.json"));
530
+ const componentConfig = configInfo.componentInfo[CUSTOM_TAB_BAR_COMPONENT_PATH];
531
+ if (componentConfig) componentConfig.customTabBar = true;
532
+ for (const item of tabBar.list) {
533
+ const pagePath = typeof item?.pagePath === "string" ? item.pagePath.replace(/^\/+/, "") : "";
534
+ if (!pagePath || !configInfo.appInfo.pages?.includes(pagePath)) continue;
535
+ const pageConfig = configInfo.pageInfo[pagePath] ||= {};
536
+ pageConfig.usingComponents ||= {};
537
+ const declaredComponents = {
538
+ ...configInfo.appInfo.usingComponents || {},
539
+ ...pageConfig.usingComponents
540
+ };
541
+ let componentName = Object.entries(declaredComponents).find(([, componentPath]) => componentPath === CUSTOM_TAB_BAR_COMPONENT_PATH)?.[0] || dependencyName;
542
+ let suffix = 0;
543
+ while (declaredComponents[componentName] && declaredComponents[componentName] !== CUSTOM_TAB_BAR_COMPONENT_PATH) {
544
+ suffix++;
545
+ componentName = `${dependencyName}-${suffix}`;
546
+ }
547
+ pageConfig.usingComponents[componentName] = CUSTOM_TAB_BAR_COMPONENT_PATH;
548
+ pageConfig.customTabBar = { componentName };
549
+ }
397
550
  }
398
551
  /**
399
552
  * 匹配页面和对应的配置信息
@@ -447,19 +600,65 @@ function storeComponentConfig(pageJsonContent, pageFilePath) {
447
600
  }
448
601
  const cUsing = cContent.usingComponents || {};
449
602
  const isComponent = cContent.component || false;
603
+ const styleIsolation = resolveComponentStyleIsolation(cContent, componentFilePath);
450
604
  const cComponents = Object.keys(cUsing).reduce((acc, key) => {
451
605
  acc[key] = getModuleId(cUsing[key], componentFilePath);
452
606
  return acc;
453
607
  }, {});
454
608
  configInfo.componentInfo[moduleId] = {
455
- id: uuid(),
609
+ id: uuid(moduleId),
456
610
  path: moduleId,
457
611
  component: isComponent,
612
+ styleIsolation,
458
613
  usingComponents: cComponents
459
614
  };
460
615
  if (cContent.usingComponents && Object.keys(cContent.usingComponents).length > 0) storeComponentConfig(configInfo.componentInfo[moduleId], componentFilePath);
461
616
  }
462
617
  }
618
+ function getStaticProperty(objectExpression, propertyName) {
619
+ if (objectExpression?.type !== "ObjectExpression") return;
620
+ return objectExpression.properties?.find((property) => {
621
+ if (property.type !== "Property" || property.computed) return false;
622
+ return property.key?.name === propertyName || property.key?.value === propertyName;
623
+ })?.value;
624
+ }
625
+ function normalizeStyleIsolation(value) {
626
+ return STYLE_ISOLATION_VALUES.has(value) ? value : void 0;
627
+ }
628
+ /**
629
+ * styleIsolation can be declared either in component.json or in
630
+ * Component({ options }). The style compiler must know it before service
631
+ * runtime starts, so only statically-declared literal options participate.
632
+ * addGlobalClass is the legacy equivalent of apply-shared.
633
+ */
634
+ function resolveComponentStyleIsolation(componentConfig, componentJsonPath) {
635
+ const jsonValue = normalizeStyleIsolation(componentConfig?.styleIsolation);
636
+ if (jsonValue) return jsonValue;
637
+ const basePath = componentJsonPath.replace(/\.json$/i, "");
638
+ const scriptPath = [".js", ".ts"].map((ext) => `${basePath}${ext}`).find((candidate) => fs.existsSync(candidate));
639
+ if (!scriptPath) return "isolated";
640
+ try {
641
+ const { program } = parseSync(scriptPath, getContentByPath(scriptPath), { sourceType: "unambiguous" });
642
+ let extractedValue;
643
+ walk(program, { enter(expression) {
644
+ if (extractedValue) return;
645
+ if (expression?.type !== "CallExpression" || expression.callee?.type !== "Identifier" || expression.callee.name !== "Component") return;
646
+ const definition = expression.arguments?.[0];
647
+ const options = getStaticProperty(definition, "options");
648
+ const styleIsolation = getStaticProperty(options, "styleIsolation")?.value;
649
+ const normalized = normalizeStyleIsolation(styleIsolation);
650
+ if (normalized) {
651
+ extractedValue = normalized;
652
+ return;
653
+ }
654
+ if (getStaticProperty(options, "addGlobalClass")?.value === true) extractedValue = "apply-shared";
655
+ } });
656
+ if (extractedValue) return extractedValue;
657
+ } catch (error) {
658
+ console.warn(`[env] 无法解析组件样式隔离配置 ${scriptPath}: ${error.message}`);
659
+ }
660
+ return "isolated";
661
+ }
463
662
  /**
464
663
  * 转化为相对小程序根目录的绝对路径,作为模块唯一性 id
465
664
  * 支持 npm 组件解析
@@ -525,9 +724,12 @@ function getPages() {
525
724
  ...pageComponents
526
725
  };
527
726
  return {
528
- id: uuid(),
727
+ id: uuid(path),
529
728
  path,
530
- usingComponents: mergedComponents
729
+ appStyleScopeId: getAppStyleScopeId(),
730
+ sharedStyleScopeIds: collectSharedStyleScopeIds(mergedComponents),
731
+ usingComponents: mergedComponents,
732
+ customTabBar: pageInfo[path]?.customTabBar
531
733
  };
532
734
  });
533
735
  const subPages = {};
@@ -544,9 +746,12 @@ function getPages() {
544
746
  ...pageComponents
545
747
  };
546
748
  return {
547
- id: uuid(),
749
+ id: uuid(fullPath),
548
750
  path: fullPath,
549
- usingComponents: mergedComponents
751
+ appStyleScopeId: getAppStyleScopeId(),
752
+ sharedStyleScopeIds: collectSharedStyleScopeIds(mergedComponents),
753
+ usingComponents: mergedComponents,
754
+ customTabBar: pageInfo[fullPath]?.customTabBar
550
755
  };
551
756
  })
552
757
  };
@@ -556,5 +761,22 @@ function getPages() {
556
761
  subPages
557
762
  };
558
763
  }
764
+ function collectSharedStyleScopeIds(usingComponents) {
765
+ const result = [];
766
+ const visited = /* @__PURE__ */ new Set();
767
+ const visit = (componentPath) => {
768
+ if (visited.has(componentPath)) return;
769
+ visited.add(componentPath);
770
+ const component = configInfo.componentInfo[componentPath];
771
+ if (!component) return;
772
+ if (component.styleIsolation === "shared") result.push(component.id);
773
+ for (const childPath of Object.values(component.usingComponents || {})) visit(childPath);
774
+ };
775
+ for (const componentPath of Object.values(usingComponents || {})) visit(componentPath);
776
+ return result;
777
+ }
778
+ function getAppStyleScopeId() {
779
+ return uuid("app");
780
+ }
559
781
  //#endregion
560
- export { tagWhiteList as _, getContentByPath as a, getPages as c, resetStoreInfo as d, resolveAppAlias as f, hasCompileInfo as g, getAbsolutePath as h, getComponent as i, getTargetPath as l, collectAssets as m, getAppId as n, getNpmResolver as o, storeInfo as p, getAppName as r, getPageConfigInfo as s, getAppConfigInfo as t, getWorkPath as u, transformRpx as v, art_default as y };
782
+ export { tagWhiteList as C, miniProgramBuiltinTags as S, toMiniProgramModuleId as T, resolveAppAlias as _, getComponent as a, getAbsolutePath as b, getPageConfigInfo as c, getTargetPath as d, getTemplateExts as f, resetStoreInfo as g, getWorkPath as h, getAppStyleScopeId as i, getPages as l, getViewScriptTags as m, getAppId as n, getContentByPath as o, getViewScriptExts as p, getAppName as r, getNpmResolver as s, getAppConfigInfo as t, getStyleExts as u, storeInfo as v, transformRpx as w, hasCompileInfo as x, collectAssets as y };
package/dist/index.cjs CHANGED
@@ -1,15 +1,70 @@
1
- const require_env = require("./env-Dmnqp9bD.cjs");
1
+ const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
2
+ const require_env = require("./env-B4U3zZUm.cjs");
2
3
  let node_path = require("node:path");
3
- node_path = require_env.__toESM(node_path, 1);
4
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
5
+ let node_process = require("node:process");
6
+ node_process = require_rolldown_runtime.__toESM(node_process, 1);
4
7
  let node_url = require("node:url");
5
8
  let node_worker_threads = require("node:worker_threads");
6
9
  let listr2 = require("listr2");
7
- let node_process = require("node:process");
8
- node_process = require_env.__toESM(node_process, 1);
9
10
  let node_fs = require("node:fs");
10
- node_fs = require_env.__toESM(node_fs, 1);
11
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
11
12
  let node_os = require("node:os");
12
- node_os = require_env.__toESM(node_os, 1);
13
+ node_os = require_rolldown_runtime.__toESM(node_os, 1);
14
+ //#region src/common/compile-progress.js
15
+ var DEFAULT_TERMINAL_COLUMNS = 80;
16
+ var MIN_PROGRESS_WIDTH = 8;
17
+ var MAX_PROGRESS_WIDTH = 28;
18
+ var RENDERER_CHROME_WIDTH = 14;
19
+ var PROGRESS_BRACKETS_WIDTH = 2;
20
+ /**
21
+ * Format worker progress without assuming a fixed terminal width.
22
+ * The completed count remains the source of truth. Whole cells avoid the
23
+ * visible gap that partial block glyphs leave before the unfilled section.
24
+ */
25
+ function formatCompileProgress(completed, total, options = {}) {
26
+ const unicode = options.unicode ?? (0, listr2.isUnicodeSupported)();
27
+ const columns = normalizePositiveInteger(options.columns) || node_process.default.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
28
+ const safeTotal = normalizePositiveInteger(total);
29
+ const safeCompleted = Math.min(normalizePositiveInteger(completed), safeTotal);
30
+ const ratio = safeTotal === 0 ? 0 : safeCompleted / safeTotal;
31
+ const percentage = Math.round(ratio * 100);
32
+ const separator = unicode ? "·" : "|";
33
+ const metadata = `${String(safeCompleted).padStart(String(safeTotal).length)}/${safeTotal} ${separator} ${String(percentage).padStart(3)}%`;
34
+ const availableWidth = columns - metadata.length - RENDERER_CHROME_WIDTH - PROGRESS_BRACKETS_WIDTH;
35
+ if (availableWidth < MIN_PROGRESS_WIDTH) return metadata;
36
+ const width = Math.min(availableWidth, MAX_PROGRESS_WIDTH);
37
+ return `[${unicode ? createUnicodeBar(ratio, width) : createAsciiBar(ratio, width)}] ${metadata}`;
38
+ }
39
+ function createUnicodeBar(ratio, width) {
40
+ const completeWidth = Math.round(ratio * width);
41
+ const emptyWidth = width - completeWidth;
42
+ return `${"█".repeat(completeWidth)}${"░".repeat(emptyWidth)}`;
43
+ }
44
+ function createAsciiBar(ratio, width) {
45
+ const completeWidth = Math.floor(ratio * width);
46
+ const showHead = ratio > 0 && ratio < 1;
47
+ const bodyWidth = Math.max(0, completeWidth - (showHead ? 1 : 0));
48
+ const emptyWidth = width - bodyWidth - (showHead ? 1 : 0);
49
+ return `${"=".repeat(bodyWidth)}${showHead ? ">" : ""}${"-".repeat(emptyWidth)}`;
50
+ }
51
+ function normalizePositiveInteger(value) {
52
+ return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
53
+ }
54
+ //#endregion
55
+ //#region src/common/art.js
56
+ var artCode = `
57
+ ██████╗ ██╗███╗ ███╗██╗███╗ ██╗ █████╗
58
+ ██╔══██╗██║████╗ ████║██║████╗ ██║██╔══██╗
59
+ ██║ ██║██║██╔████╔██║██║██╔██╗ ██║███████║
60
+ ██║ ██║██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║
61
+ ██████╔╝██║██║ ╚═╝ ██║██║██║ ╚████║██║ ██║
62
+ ╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝
63
+ `;
64
+ function art_default() {
65
+ console.log(artCode);
66
+ }
67
+ //#endregion
13
68
  //#region src/common/publish.js
14
69
  function createDist() {
15
70
  const distPath = require_env.getTargetPath();
@@ -191,13 +246,10 @@ var NpmBuilder = class {
191
246
  const miniprogramExts = [
192
247
  ".js",
193
248
  ".json",
194
- ".wxml",
195
- ".wxss",
196
- ".wxs",
197
249
  ".ts",
198
- ".less",
199
- ".scss",
200
- ".styl"
250
+ ...require_env.getTemplateExts(),
251
+ ...require_env.getStyleExts(),
252
+ ...require_env.getViewScriptExts()
201
253
  ];
202
254
  const ext = node_path.default.extname(filename).toLowerCase();
203
255
  return miniprogramExts.includes(ext) || filename === "package.json" || filename === "README.md" || filename.startsWith(".");
@@ -312,26 +364,32 @@ function compileConfig() {
312
364
  //#endregion
313
365
  //#region src/index.js
314
366
  var isPrinted = false;
367
+ var previousCompatibilityWarnings = /* @__PURE__ */ new Map();
315
368
  /**
316
369
  * 构建命令入口
317
370
  * @param {string} targetPath 编译产物目标路径
318
371
  * @param {string} workPath 编译工作目录
319
- * @param {boolean} useAppIdDir 产物根目录是否包含appId
372
+ * @param {boolean} useAppIdDir 产物根目录是否包含 appId
373
+ * @param {object} [options] 构建选项
374
+ * @param {boolean} [options.sourcemap] 是否生成 sourcemap
375
+ * @param {{ template?: string[], style?: string[], viewScript?: string[] }} [options.fileTypes]
376
+ * 自定义文件类型,在内置 wx/dd 类型基础上追加;template 为模板扩展名,style 为样式扩展名,
377
+ * viewScript 为视图脚本扩展名和内联标签名
320
378
  */
321
379
  async function build(targetPath, workPath, useAppIdDir = true, options = {}) {
322
- const { sourcemap = false } = options;
380
+ const { sourcemap = false, fileTypes } = options;
323
381
  if (!isPrinted) {
324
- require_env.art_default();
382
+ art_default();
325
383
  isPrinted = true;
326
384
  }
327
385
  const tasks = new listr2.Listr([
328
386
  {
329
- title: "准备项目编译环境",
387
+ title: "初始化项目",
330
388
  task: (_, task) => task.newListr([
331
389
  {
332
390
  title: "收集配置信息",
333
391
  task: (ctx) => {
334
- ctx.storeInfo = require_env.storeInfo(workPath);
392
+ ctx.storeInfo = require_env.storeInfo(workPath, { fileTypes });
335
393
  }
336
394
  },
337
395
  {
@@ -355,29 +413,42 @@ async function build(targetPath, workPath, useAppIdDir = true, options = {}) {
355
413
  ], { concurrent: false })
356
414
  },
357
415
  {
358
- title: `开始编译:${workPath.split("/").pop()}`,
416
+ title: `编译项目 · ${node_path.default.basename(node_path.default.resolve(workPath))}`,
359
417
  task: (ctx, task) => {
360
418
  const pages = require_env.getPages();
361
419
  ctx.pages = pages;
420
+ ctx.compatibilityWarnings = /* @__PURE__ */ new Set();
362
421
  return task.newListr([
363
422
  {
364
- title: "编译页面文件",
423
+ title: "编译视图",
424
+ rendererOptions: {
425
+ outputBar: true,
426
+ persistentOutput: false
427
+ },
365
428
  task: async (ctx, task) => {
366
429
  return runCompileInWorker("view", ctx, task, { sourcemap });
367
430
  }
368
431
  },
369
432
  {
370
- title: "编译页面逻辑",
433
+ title: "编译逻辑",
434
+ rendererOptions: {
435
+ outputBar: true,
436
+ persistentOutput: false
437
+ },
371
438
  task: async (ctx, task) => {
372
439
  return runCompileInWorker("logic", ctx, task, { sourcemap });
373
440
  }
374
441
  },
375
442
  {
376
- title: "编译样式文件",
443
+ title: "编译样式",
444
+ rendererOptions: {
445
+ outputBar: true,
446
+ persistentOutput: false
447
+ },
377
448
  task: async (ctx, task) => {
378
449
  pages.mainPages.unshift({
379
450
  path: "app",
380
- id: ""
451
+ id: require_env.getAppStyleScopeId()
381
452
  });
382
453
  return runCompileInWorker("style", ctx, task, { sourcemap });
383
454
  }
@@ -386,14 +457,23 @@ async function build(targetPath, workPath, useAppIdDir = true, options = {}) {
386
457
  }
387
458
  },
388
459
  {
389
- title: "输出编译产物",
460
+ title: "写入编译产物",
390
461
  task: () => {
391
462
  publishToDist(targetPath, useAppIdDir);
392
463
  }
393
464
  }
394
- ], { concurrent: false });
465
+ ], {
466
+ concurrent: false,
467
+ rendererOptions: {
468
+ collapseSubtasks: true,
469
+ formatOutput: "truncate",
470
+ timer: listr2.PRESET_TIMER
471
+ },
472
+ fallbackRendererOptions: { timer: listr2.PRESET_TIMER }
473
+ });
395
474
  try {
396
475
  const context = await tasks.run();
476
+ printCompatibilityWarnings(workPath, context.compatibilityWarnings);
397
477
  return {
398
478
  appId: require_env.getAppId(),
399
479
  name: require_env.getAppName(),
@@ -422,16 +502,12 @@ function runCompileInWorker(script, ctx, task, options = {}) {
422
502
  });
423
503
  worker.on("message", (message) => {
424
504
  try {
425
- if (message.completedTasks) {
426
- const progress = message.completedTasks / totalTasks;
427
- const percentage = progress * 100;
428
- const barLength = 30;
429
- const filledLength = Math.ceil(barLength * progress);
430
- task.output = `[${"█".repeat(filledLength) + "░".repeat(barLength - filledLength)}] ${percentage.toFixed(2)}%`;
431
- }
505
+ for (const warning of message.compatibilityWarnings || []) ctx.compatibilityWarnings.add(warning);
506
+ if (node_process.default.stdout.isTTY && message.completedTasks !== void 0) task.output = formatCompileProgress(message.completedTasks, totalTasks);
432
507
  if (message.success) {
433
508
  if (isResolved) return;
434
509
  isResolved = true;
510
+ if (node_process.default.stdout.isTTY && totalTasks > 0) task.output = formatCompileProgress(totalTasks, totalTasks);
435
511
  worker.terminate();
436
512
  resolve();
437
513
  } else if (message.error) {
@@ -450,9 +526,25 @@ function runCompileInWorker(script, ctx, task, options = {}) {
450
526
  handleError(err);
451
527
  });
452
528
  worker.on("exit", (code) => {
453
- if (code !== 0 && !isResolved) handleError(workerError || /* @__PURE__ */ new Error(code === 1 ? "Worker terminated due to reaching memory limit: JS heap out of memory" : `Worker stopped with exit code ${code}`));
529
+ if (code !== 0 && !isResolved) {
530
+ const error = workerError || /* @__PURE__ */ new Error(code === 1 ? "Worker terminated due to reaching memory limit: JS heap out of memory" : `Worker stopped with exit code ${code}`);
531
+ handleError(error);
532
+ }
454
533
  });
455
534
  }));
456
535
  }
536
+ function printCompatibilityWarnings(workPath, warnings = /* @__PURE__ */ new Set()) {
537
+ const projectPath = node_path.default.resolve(workPath);
538
+ const hasPreviousResult = previousCompatibilityWarnings.has(projectPath);
539
+ const previousWarnings = previousCompatibilityWarnings.get(projectPath) || /* @__PURE__ */ new Set();
540
+ const currentWarnings = new Set(warnings);
541
+ const newWarnings = [...currentWarnings].filter((warning) => !previousWarnings.has(warning));
542
+ previousCompatibilityWarnings.set(projectPath, currentWarnings);
543
+ if (newWarnings.length === 0) return;
544
+ const qualifier = hasPreviousResult ? " new" : "";
545
+ const suffix = newWarnings.length === 1 ? "" : "s";
546
+ console.warn(`\n[compat] ${newWarnings.length}${qualifier} compatibility warning${suffix}`);
547
+ for (const warning of newWarnings) console.warn(` - ${warning.replace(/^\[compat\]\s*/, "")}`);
548
+ }
457
549
  //#endregion
458
550
  module.exports = build;