refactor(mp-harmony): add HAR dependency checks and debug logs
1. add debug logs for diy component lifecycle and data flow 2. add required HAR dependency declarations for mp-harmony entry module 3. add har-deps config, validation script and documentation
This commit is contained in:
@@ -232,6 +232,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[DIY][init] calling getDiyInfo, storeToken=', !!this.storeToken);
|
||||
await this.getDiyInfo();
|
||||
//获取启动广告
|
||||
await this.getDiyAdv();
|
||||
@@ -362,10 +363,15 @@ export default {
|
||||
// 节流:正在请求 / N 秒内已加载过 / 语言未变 -> 跳过
|
||||
const now = Date.now();
|
||||
const currentLang = uni.getStorageSync("lang") || '';
|
||||
console.log('[DIY][getDiyInfo] start', 'force=', force, 'id=', this.id, 'name=', this.name, 'lang=', currentLang, 'loading=', this.diyInfoLoading, 'lastAt=', this.lastDiyInfoTime, 'lastLang=', this.lastDiyInfoLang);
|
||||
if (!force) {
|
||||
if (this.diyInfoLoading) return;
|
||||
if (this.diyInfoLoading) {
|
||||
console.log('[DIY][getDiyInfo] skip: already loading');
|
||||
return;
|
||||
}
|
||||
if (this.lastDiyInfoTime && (now - this.lastDiyInfoTime) < this.diyInfoTtl
|
||||
&& this.lastDiyInfoLang === currentLang) {
|
||||
console.log('[DIY][getDiyInfo] skip: throttled by ttl');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -386,7 +392,7 @@ export default {
|
||||
async: false
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('getDiyInfo request failed', e);
|
||||
console.error('[DIY][getDiyInfo] request failed', e);
|
||||
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
|
||||
this.diyInfoLoading = false;
|
||||
// 无法从网络获取数据时,页面内显示错误横幅并提供重试(带详细原因 + 请求地址)
|
||||
@@ -395,8 +401,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.diyInfoLoading = false;
|
||||
console.log('[DIY][getDiyInfo] got response', 'code=', res && res.code, 'hasData=', !!(res && res.data), 'valueType=', res && res.data && typeof res.data.value, 'valueLen=', res && res.data && (typeof res.data.value === 'string' ? res.data.value.length : (res.data.value ? res.data.value.length : null)));
|
||||
|
||||
if (!res || res.code != 0 || !res.data) {
|
||||
console.warn('[DIY][getDiyInfo] invalid response', JSON.stringify(res));
|
||||
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
|
||||
|
||||
// 完全没有拿到网络响应,视为无法从网络获取数据,页面内显示错误横幅
|
||||
@@ -435,7 +443,13 @@ export default {
|
||||
|
||||
let diyDataValue = res.data;
|
||||
if (diyDataValue.value) {
|
||||
this.diyData = JSON.parse(diyDataValue.value);
|
||||
try {
|
||||
this.diyData = JSON.parse(diyDataValue.value);
|
||||
} catch (parseErr) {
|
||||
console.error('[DIY][getDiyInfo] JSON.parse(value) failed', parseErr, 'value preview=', String(diyDataValue.value).slice(0, 200));
|
||||
throw parseErr;
|
||||
}
|
||||
console.log('[DIY][getDiyInfo] parsed diyData', 'globalKeys=', this.diyData && this.diyData.global ? Object.keys(this.diyData.global) : null, 'valueCount=', this.diyData && this.diyData.value && this.diyData.value.length, 'topIndexValue before=', this.topIndexValue);
|
||||
|
||||
// 导航栏标题要根据语言环境切换
|
||||
// 优先使用 title_map 多语言 map,没有则回退到 title / en_title
|
||||
@@ -523,6 +537,9 @@ export default {
|
||||
this.openBottomNav = this.diyData.global.openBottomNav;
|
||||
}
|
||||
this.isShowCopyRight = true;
|
||||
console.log('[DIY][getDiyInfo] done', 'topIndexValue after=', this.topIndexValue && this.topIndexValue.componentName, 'valueCount=', this.diyData.value.length, 'openBottomNav=', this.openBottomNav, 'isShowCopyRight=', this.isShowCopyRight);
|
||||
} else {
|
||||
console.warn('[DIY][getDiyInfo] no value in res.data', JSON.stringify(diyDataValue).slice(0, 200));
|
||||
}
|
||||
},
|
||||
closePopupWindow() {
|
||||
|
||||
@@ -306,9 +306,11 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
console.log('[DIY-GROUP][created] hasDiyData=', !!this.diyData, 'valueLen=', this.diyData && this.diyData.value && this.diyData.value.length);
|
||||
this.processData();
|
||||
},
|
||||
mounted() {
|
||||
console.log('[DIY-GROUP][mounted] hasDiyData=', !!this.diyData, 'valueLen=', this.diyData && this.diyData.value && this.diyData.value.length);
|
||||
if (this.diyData && this.diyData.value) {
|
||||
this._notifyLoaded();
|
||||
}
|
||||
@@ -349,11 +351,13 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
_notifyLoaded() {
|
||||
if (this._loadedTimer) clearTimeout(this._loadedTimer);
|
||||
this._loadedTimer = setTimeout(() => {
|
||||
this.$emit('loaded');
|
||||
}, 300);
|
||||
},
|
||||
console.log('[DIY-GROUP][_notifyLoaded] enter, will emit loaded in 300ms');
|
||||
if (this._loadedTimer) clearTimeout(this._loadedTimer);
|
||||
this._loadedTimer = setTimeout(() => {
|
||||
console.log('[DIY-GROUP][_notifyLoaded] emit loaded');
|
||||
this.$emit('loaded');
|
||||
}, 300);
|
||||
},
|
||||
processData() {
|
||||
if (!this.diyGlobalData || !this.diyGlobalData.value) {
|
||||
this.processedData = [];
|
||||
|
||||
73
stub-app-projects/mp-harmony/entry/README-HAR-DEPS.md
Normal file
73
stub-app-projects/mp-harmony/entry/README-HAR-DEPS.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 鸿蒙元服务 entry 模块依赖说明
|
||||
|
||||
> ⚠️ **本模块的 HAR 依赖必须保留**,否则运行时会缺失子包 chunk(DIY 首页、商品分类、订单、营销页全部失效)。
|
||||
|
||||
## 背景
|
||||
|
||||
`mp-harmony` 项目把 uniapp 编译产物拆成 6 个鸿蒙模块:
|
||||
|
||||
| 模块名 | srcPath | 类型 | 内含 |
|
||||
|---|---|---|---|
|
||||
| entry | `./entry` | 主 HAP | 入口页面、首页骨架、TabBar |
|
||||
| components_diy | `./ascf/ascf_build/components_diy` | shared HAR | 50+ diy-* 组件的 pack.js |
|
||||
| pages_goods | `./ascf/ascf_build/pages_goods` | shared HAR | 商品分类/详情/列表/购物车 |
|
||||
| pages_order | `./ascf/ascf_build/pages_order` | shared HAR | 订单/支付/详情 |
|
||||
| pages_promotion | `./ascf/ascf_build/pages_promotion` | shared HAR | 分销/积分/商户 |
|
||||
| pages_tool | `./ascf/ascf_build/pages_tool` | shared HAR | 会员/支付/客服/工具页 |
|
||||
|
||||
`entry` 必须把这 5 个 HAR 加为依赖,否则运行时鸿蒙端加载 entry HAP 时找不到
|
||||
`components-diy/diy-group.pack.js` 等子包 chunk,所有 diy 组件被
|
||||
`componentPlaceholder: view` 替换成空 view,DIY 内容不显示,12s splash 兜底超时。
|
||||
|
||||
## 必须保留的依赖
|
||||
|
||||
`entry/oh-package.json5` 的 `dependencies` 必须包含:
|
||||
|
||||
```json5
|
||||
{
|
||||
"components_diy": "file:../ascf/ascf_build/components_diy",
|
||||
"pages_goods": "file:../ascf/ascf_build/pages_goods",
|
||||
"pages_order": "file:../ascf/ascf_build/pages_order",
|
||||
"pages_promotion": "file:../ascf/ascf_build/pages_promotion",
|
||||
"pages_tool": "file:../ascf/ascf_build/pages_tool",
|
||||
}
|
||||
```
|
||||
|
||||
## 防回归
|
||||
|
||||
```bash
|
||||
# 检查 module.json + oh_modules + oh-package.json5 是否完整
|
||||
node scripts/check-har-deps.js --rawfile
|
||||
|
||||
# 严格模式: 任何缺失即 exit 1 (CI 用)
|
||||
node scripts/check-har-deps.js --rawfile --strict
|
||||
```
|
||||
|
||||
校验脚本读取 `./har-deps-required.json5`(白名单单一事实来源)。
|
||||
|
||||
## 如果你需要新增/删除子包
|
||||
|
||||
1. 修改 `ascf_build/<新包>/oh-package.json5` 的 `name` 字段
|
||||
2. 在 `mp-harmony/build-profile.json5` 的 `modules` 数组里增/减模块
|
||||
3. 在 `entry/oh-package.json5` 的 `dependencies` 里增/减 file: 依赖
|
||||
4. **同步更新 `entry/har-deps-required.json5` 的 `requiredHarDeps` / `ohModulesRoots`**
|
||||
5. 在 DevEco Studio 中 Sync 项目 → 重新 Build → 运行 `node scripts/check-har-deps.js --rawfile --strict`
|
||||
6. 通知 ascf 负责人对齐
|
||||
|
||||
## 常见误操作
|
||||
|
||||
- ❌ 在 DevEco 的 "Manage Dependencies" 对话框里只保留 `@atomicservice/ascfapi`
|
||||
→ 5 个 file: 依赖会被清空,运行失败
|
||||
- ❌ 删除 `entry/oh-package.json5` 的 file: 依赖后用 ohpm install 重装
|
||||
→ ohpm 会重新同步 file: 依赖,**只要 file: 声明还在就是安全的**
|
||||
- ❌ 把 `ascf_build/<pkg>` 目录改名/移走
|
||||
→ file: 引用会变成 broken,运行失败
|
||||
|
||||
## 故障现象速查
|
||||
|
||||
| 现象 | 原因 | 修复 |
|
||||
|---|---|---|
|
||||
| 首页只显示 TabBar,无 DIY 内容 | components_diy 缺失 | 加 file: 依赖 + Sync + Build |
|
||||
| 12s splash 后页面空白 | 任意一个 HAR 子包缺失 | 跑校验脚本看哪个缺失 |
|
||||
| 商品分类页空白 | pages_goods 缺失 | 同上 |
|
||||
| "应用未关联服务空间" 报错 | uniCloud 服务未关联(与本依赖无关) | uniCloud 目录右键关联服务空间 |
|
||||
63
stub-app-projects/mp-harmony/entry/har-deps-required.json5
Normal file
63
stub-app-projects/mp-harmony/entry/har-deps-required.json5
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
// ============================================================================
|
||||
// 鸿蒙元服务 entry 模块的必需 HAR 依赖白名单
|
||||
//
|
||||
// 背景:
|
||||
// mp-harmony 项目把 uniapp 编译产物拆成了 6 个鸿蒙模块:
|
||||
// - entry (主入口 HAP, 由 DevEco 编译)
|
||||
// - components_diy (50+ diy-* 组件的 pack.js, shared HAR)
|
||||
// - pages_goods (商品分类/详情/列表/购物车, shared HAR)
|
||||
// - pages_order (订单相关页面, shared HAR)
|
||||
// - pages_promotion (分销/积分/商户等营销页面, shared HAR)
|
||||
// - pages_tool (会员/支付/客服/工具页, shared HAR)
|
||||
//
|
||||
// entry 的 module.json 默认只会声明 oh-package.json5 里 dependencies 中的 HAR
|
||||
// 作为 module dependencies。如果这里不显式声明, 鸿蒙运行时会找不到
|
||||
// components-diy/diy-group.pack.js 等子包 chunk,
|
||||
// DIY 首页 / 商品分类 / 订单 / 营销页 全部被 componentPlaceholder fallback 成 <view>,
|
||||
// 表现为: 首页 DIY 内容不显示, 12s splash 兜底超时, TabBar 点击无反应。
|
||||
//
|
||||
// 必须:
|
||||
// entry/oh-package.json5 的 dependencies 必须保留以下 5 个 file: 依赖。
|
||||
// 不要轻易删除, 删除前请先和 ascf 子包负责人对齐。
|
||||
//
|
||||
// 验证:
|
||||
// 执行 `node scripts/check-har-deps.js` 校验当前 module.json 是否包含这些依赖。
|
||||
// ============================================================================
|
||||
requiredHarDeps: [
|
||||
"components_diy",
|
||||
"pages_goods",
|
||||
"pages_order",
|
||||
"pages_promotion",
|
||||
"pages_tool",
|
||||
],
|
||||
|
||||
// oh-package.json5 里依赖声明的预期 source 字符串
|
||||
// (DevEco/ohpm 解析后会规范化, 这里保留原始声明便于核对)
|
||||
expectedDependencySources: {
|
||||
components_diy: "file:../ascf/ascf_build/components_diy",
|
||||
pages_goods: "file:../ascf/ascf_build/pages_goods",
|
||||
pages_order: "file:../ascf/ascf_build/pages_order",
|
||||
pages_promotion: "file:../ascf/ascf_build/pages_promotion",
|
||||
pages_tool: "file:../ascf/ascf_build/pages_tool",
|
||||
},
|
||||
|
||||
// 校验脚本要扫描的产物路径 (相对 entry 目录)
|
||||
buildArtifacts: {
|
||||
moduleJson: "build/default/intermediates/package/default/module.json",
|
||||
},
|
||||
|
||||
// oh_modules 子包内 rawfile 根目录映射 (用于 --rawfile 检查)
|
||||
// 子包 HAR 通过 ohpm 同步后落在 entry/oh_modules/<pkg>/src/main/resources/rawfile/<root>/
|
||||
// HAR 资源在运行时由鸿蒙端通过 HAR 共享机制加载, 不是 entry 的 rawfile 路径
|
||||
ohModulesRoots: {
|
||||
components_diy: {
|
||||
pkg: "components_diy",
|
||||
root: "components-diy",
|
||||
},
|
||||
pages_goods: { pkg: "pages_goods", root: "pages_goods" },
|
||||
pages_order: { pkg: "pages_order", root: "pages_order" },
|
||||
pages_promotion:{ pkg: "pages_promotion",root: "pages_promotion" },
|
||||
pages_tool: { pkg: "pages_tool", root: "pages_tool" },
|
||||
},
|
||||
}
|
||||
@@ -6,7 +6,12 @@
|
||||
"lockfileVersion": 3,
|
||||
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
|
||||
"specifiers": {
|
||||
"@atomicservice/ascfapi@^1.0.6": "@atomicservice/ascfapi@1.0.23"
|
||||
"@atomicservice/ascfapi@^1.0.6": "@atomicservice/ascfapi@1.0.23",
|
||||
"components_diy@../ascf/ascf_build/components_diy": "components_diy@../ascf/ascf_build/components_diy",
|
||||
"pages_goods@../ascf/ascf_build/pages_goods": "pages_goods@../ascf/ascf_build/pages_goods",
|
||||
"pages_order@../ascf/ascf_build/pages_order": "pages_order@../ascf/ascf_build/pages_order",
|
||||
"pages_promotion@../ascf/ascf_build/pages_promotion": "pages_promotion@../ascf/ascf_build/pages_promotion",
|
||||
"pages_tool@../ascf/ascf_build/pages_tool": "pages_tool@../ascf/ascf_build/pages_tool"
|
||||
},
|
||||
"packages": {
|
||||
"@atomicservice/ascfapi@1.0.23": {
|
||||
@@ -16,6 +21,41 @@
|
||||
"resolved": "https://ohpm.openharmony.cn/ohpm/@atomicservice/ascfapi/-/ascfapi-1.0.23.har",
|
||||
"registryType": "ohpm",
|
||||
"packageType": "InterfaceHar"
|
||||
},
|
||||
"components_diy@../ascf/ascf_build/components_diy": {
|
||||
"name": "components_diy",
|
||||
"version": "1.0.0",
|
||||
"resolved": "../ascf/ascf_build/components_diy",
|
||||
"registryType": "local",
|
||||
"packageType": "InterfaceHar"
|
||||
},
|
||||
"pages_goods@../ascf/ascf_build/pages_goods": {
|
||||
"name": "pages_goods",
|
||||
"version": "1.0.0",
|
||||
"resolved": "../ascf/ascf_build/pages_goods",
|
||||
"registryType": "local",
|
||||
"packageType": "InterfaceHar"
|
||||
},
|
||||
"pages_order@../ascf/ascf_build/pages_order": {
|
||||
"name": "pages_order",
|
||||
"version": "1.0.0",
|
||||
"resolved": "../ascf/ascf_build/pages_order",
|
||||
"registryType": "local",
|
||||
"packageType": "InterfaceHar"
|
||||
},
|
||||
"pages_promotion@../ascf/ascf_build/pages_promotion": {
|
||||
"name": "pages_promotion",
|
||||
"version": "1.0.0",
|
||||
"resolved": "../ascf/ascf_build/pages_promotion",
|
||||
"registryType": "local",
|
||||
"packageType": "InterfaceHar"
|
||||
},
|
||||
"pages_tool@../ascf/ascf_build/pages_tool": {
|
||||
"name": "pages_tool",
|
||||
"version": "1.0.0",
|
||||
"resolved": "../ascf/ascf_build/pages_tool",
|
||||
"registryType": "local",
|
||||
"packageType": "InterfaceHar"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,28 @@
|
||||
{
|
||||
"name": "entry",
|
||||
"version": "1.0.0",
|
||||
"description": "Please describe the basic information.",
|
||||
"modelVersion": "6.0.0",
|
||||
"main": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@atomicservice/ascfapi": "^1.0.6"
|
||||
}
|
||||
// ============================================================================
|
||||
// 鸿蒙元服务 entry 模块依赖声明
|
||||
//
|
||||
// ⚠️ 不要随意删除下面 5 个 file: 依赖。
|
||||
// 它们是 mp-harmony 项目的 5 个 shared HAR 子包 (components_diy / pages_goods /
|
||||
// pages_order / pages_promotion / pages_tool), 内含 uniapp 编译产物的 *.pack.js。
|
||||
//
|
||||
// 详见 ./har-deps-required.json5 与 ./scripts/check-har-deps.js。
|
||||
//
|
||||
// 如要新增/删除子包, 请同步更新 har-deps-required.json5 并通知 ascf 负责人。
|
||||
// ============================================================================
|
||||
{
|
||||
"name": "entry",
|
||||
"version": "1.0.0",
|
||||
"description": "Please describe the basic information.",
|
||||
"modelVersion": "6.0.0",
|
||||
"main": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@atomicservice/ascfapi": "^1.0.6",
|
||||
"components_diy": "file:../ascf/ascf_build/components_diy",
|
||||
"pages_goods": "file:../ascf/ascf_build/pages_goods",
|
||||
"pages_order": "file:../ascf/ascf_build/pages_order",
|
||||
"pages_promotion": "file:../ascf/ascf_build/pages_promotion",
|
||||
"pages_tool": "file:../ascf/ascf_build/pages_tool"
|
||||
}
|
||||
}
|
||||
202
stub-app-projects/mp-harmony/entry/scripts/check-har-deps.js
Normal file
202
stub-app-projects/mp-harmony/entry/scripts/check-har-deps.js
Normal file
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* 鸿蒙元服务 entry 模块依赖校验脚本
|
||||
*
|
||||
* 目的:
|
||||
* 防止 entry 的 HAP 打包时丢失 5 个 ascf shared HAR 子包依赖,
|
||||
* 否则运行时会找不到 components-diy/diy-group.pack.js 等子包 chunk,
|
||||
* DIY 首页 + 商品分类 + 订单 + 营销页全部失效 (componentPlaceholder fallback 成 view)。
|
||||
*
|
||||
* 使用:
|
||||
* node scripts/check-har-deps.js # 仅校验 entry/build/.../module.json
|
||||
* node scripts/check-har-deps.js --rawfile # 同时校验 rawfile 资源是否就位
|
||||
* node scripts/check-har-deps.js --strict # 任一缺失即 exit 1
|
||||
*
|
||||
* 默认情况:
|
||||
* 缺失时仅打印警告 (CI 友好, 不阻塞开发), 加 --strict 才严格失败。
|
||||
*
|
||||
* 依赖:
|
||||
* ./har-deps-required.json5 (本脚本的单一事实来源)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const parseJson5 = require('./load-json5');
|
||||
|
||||
const ENTRY_DIR = path.resolve(__dirname, '..');
|
||||
const CONFIG_PATH = path.join(ENTRY_DIR, 'har-deps-required.json5');
|
||||
|
||||
function loadJson5(filePath) {
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
return parseJson5(raw);
|
||||
}
|
||||
|
||||
function exists(p) {
|
||||
try {
|
||||
fs.accessSync(p, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function listDir(p) {
|
||||
try {
|
||||
return fs.readdirSync(p);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function color(s, c) {
|
||||
const codes = {
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
cyan: '\x1b[36m',
|
||||
gray: '\x1b[90m',
|
||||
reset: '\x1b[0m',
|
||||
};
|
||||
return process.stdout.isTTY ? codes[c] + s + codes.reset : s;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const checkRawfile = args.includes('--rawfile');
|
||||
const strict = args.includes('--strict');
|
||||
|
||||
if (!exists(CONFIG_PATH)) {
|
||||
console.error(color(`✘ 缺少白名单配置: ${CONFIG_PATH}`, 'red'));
|
||||
process.exit(strict ? 1 : 0);
|
||||
}
|
||||
|
||||
const config = loadJson5(CONFIG_PATH);
|
||||
const requiredHarDeps = config.requiredHarDeps || [];
|
||||
const expectedSources = config.expectedDependencySources || {};
|
||||
const moduleJsonRel = (config.buildArtifacts && config.buildArtifacts.moduleJson)
|
||||
|| 'build/default/intermediates/package/default/module.json';
|
||||
|
||||
const moduleJsonPath = path.join(ENTRY_DIR, moduleJsonRel);
|
||||
|
||||
console.log(color('=== HAR 子包依赖校验 ===', 'cyan'));
|
||||
console.log(`配置白名单: ${requiredHarDeps.join(', ')}`);
|
||||
console.log(`module.json: ${moduleJsonPath}`);
|
||||
console.log();
|
||||
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
// ---- 1. 校验 module.json dependencies ----
|
||||
if (!exists(moduleJsonPath)) {
|
||||
warnings.push(`module.json 不存在 (${moduleJsonPath}), 跳过依赖检查。可能尚未 Build。`);
|
||||
} else {
|
||||
let mj;
|
||||
try {
|
||||
mj = JSON.parse(fs.readFileSync(moduleJsonPath, 'utf8'));
|
||||
} catch (e) {
|
||||
errors.push(`module.json 解析失败: ${e.message}`);
|
||||
}
|
||||
if (mj) {
|
||||
const deps = (mj.module && mj.module.dependencies) || mj.dependencies || [];
|
||||
const depNames = new Set(deps.map(d => d.moduleName));
|
||||
for (const har of requiredHarDeps) {
|
||||
if (depNames.has(har)) {
|
||||
console.log(color(` ✔ module.json.dependencies 包含 ${har}`, 'green'));
|
||||
} else {
|
||||
errors.push(`module.json.dependencies 缺失 ${har} (实际: ${[...depNames].join(', ') || '空'})`);
|
||||
console.log(color(` ✘ module.json.dependencies 缺失 ${har}`, 'red'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 2. 校验 oh_modules 子包资源是否就位 (可选) ----
|
||||
if (checkRawfile) {
|
||||
console.log();
|
||||
console.log(color('--- oh_modules 子包资源检查 ---', 'cyan'));
|
||||
const ohModulesRoots = (config.ohModulesRoots) || {};
|
||||
const ohModulesDir = path.join(ENTRY_DIR, 'oh_modules');
|
||||
if (!exists(ohModulesDir)) {
|
||||
warnings.push(`oh_modules 目录不存在 (${ohModulesDir}), 请先在 DevEco 中 Sync 项目`);
|
||||
}
|
||||
for (const har of requiredHarDeps) {
|
||||
const cfg = ohModulesRoots[har];
|
||||
if (!cfg) continue;
|
||||
const subDir = path.join(ohModulesDir, cfg.pkg, 'src', 'main', 'resources', 'rawfile', cfg.root);
|
||||
if (!exists(subDir)) {
|
||||
errors.push(`oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 缺失 (鸿蒙运行时会找不到组件)`);
|
||||
console.log(color(` ✘ oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 缺失`, 'red'));
|
||||
} else {
|
||||
const files = listDir(subDir);
|
||||
const packFiles = files.filter(f => f.endsWith('.pack.js'));
|
||||
const inner = listDir(subDir).filter(f => {
|
||||
try { return fs.statSync(path.join(subDir, f)).isDirectory(); } catch { return false; }
|
||||
});
|
||||
const totalPack = packFiles.length + inner.reduce((acc, d) => {
|
||||
try { return acc + fs.readdirSync(path.join(subDir, d)).filter(f => f.endsWith('.pack.js')).length; }
|
||||
catch { return acc; }
|
||||
}, 0);
|
||||
if (totalPack === 0) {
|
||||
warnings.push(`oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 存在但无 .pack.js`);
|
||||
console.log(color(` ⚠ oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 无 .pack.js`, 'yellow'));
|
||||
} else {
|
||||
console.log(color(` ✔ oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 存在, ${totalPack} 个 .pack.js`, 'green'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. 校验 oh-package.json5 的 dependencies 与白名单一致 ----
|
||||
const ohPkgPath = path.join(ENTRY_DIR, 'oh-package.json5');
|
||||
if (exists(ohPkgPath)) {
|
||||
console.log();
|
||||
console.log(color('--- oh-package.json5 检查 ---', 'cyan'));
|
||||
const ohPkg = loadJson5(ohPkgPath);
|
||||
const ohDeps = ohPkg.dependencies || {};
|
||||
for (const har of requiredHarDeps) {
|
||||
if (!(har in ohDeps)) {
|
||||
errors.push(`oh-package.json5.dependencies 缺失 ${har}`);
|
||||
console.log(color(` ✘ oh-package.json5 缺失 ${har}`, 'red'));
|
||||
} else {
|
||||
const expected = expectedSources[har];
|
||||
const actual = ohDeps[har];
|
||||
if (expected && actual !== expected) {
|
||||
warnings.push(`oh-package.json5 中 ${har} 的 source 不是预期: ${actual} (预期 ${expected})`);
|
||||
console.log(color(` ⚠ ${har} source 异常: ${actual}`, 'yellow'));
|
||||
} else {
|
||||
console.log(color(` ✔ oh-package.json5 包含 ${har} (${actual})`, 'green'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 总结 ----
|
||||
console.log();
|
||||
if (warnings.length) {
|
||||
console.log(color(`警告 (${warnings.length}):`, 'yellow'));
|
||||
for (const w of warnings) console.log(' - ' + w);
|
||||
}
|
||||
if (errors.length) {
|
||||
console.log();
|
||||
console.log(color(`错误 (${errors.length}):`, 'red'));
|
||||
for (const e of errors) console.log(' - ' + e);
|
||||
console.log();
|
||||
console.log(color('✘ 校验失败: entry 模块缺少必要的 HAR 子包依赖。', 'red'));
|
||||
console.log(color(' 原因: 这会导致运行时找不到 components-diy/diy-group.pack.js 等子包 chunk,', 'gray'));
|
||||
console.log(color(' DIY 首页 + 商品分类 + 订单 + 营销页全部失效, 12s splash 兜底超时。', 'gray'));
|
||||
console.log(color(' 修复: 在 entry/oh-package.json5 的 dependencies 里补齐 5 个 file: 依赖,', 'gray'));
|
||||
console.log(color(' 同步 ./har-deps-required.json5, 然后重新 Build。', 'gray'));
|
||||
process.exit(strict ? 1 : 0);
|
||||
}
|
||||
console.log(color('✔ 校验通过', 'green'));
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
console.error(color(`脚本异常: ${e.stack || e.message}`, 'red'));
|
||||
process.exit(1);
|
||||
}
|
||||
144
stub-app-projects/mp-harmony/entry/scripts/load-json5.js
Normal file
144
stub-app-projects/mp-harmony/entry/scripts/load-json5.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 简单 json5 解析 (支持 // 单行注释, /* 多行注释 *\/, 字符串, 转义)
|
||||
* 不支持: 数字下划线, 十六进制等扩展
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function parseJson5(text) {
|
||||
let i = 0;
|
||||
const len = text.length;
|
||||
|
||||
function peek() { return text[i]; }
|
||||
function next() { return text[i++]; }
|
||||
|
||||
function skipWsAndComments() {
|
||||
while (i < len) {
|
||||
const c = text[i];
|
||||
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c === '/' && text[i + 1] === '/') {
|
||||
while (i < len && text[i] !== '\n') i++;
|
||||
continue;
|
||||
}
|
||||
if (c === '/' && text[i + 1] === '*') {
|
||||
i += 2;
|
||||
while (i < len && !(text[i] === '*' && text[i + 1] === '/')) i++;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function readString() {
|
||||
const quote = next(); // " or '
|
||||
let out = '';
|
||||
while (i < len) {
|
||||
const c = next();
|
||||
if (c === '\\') {
|
||||
const esc = next();
|
||||
switch (esc) {
|
||||
case 'n': out += '\n'; break;
|
||||
case 't': out += '\t'; break;
|
||||
case 'r': out += '\r'; break;
|
||||
case '\\': out += '\\'; break;
|
||||
case '"': out += '"'; break;
|
||||
case "'": out += "'"; break;
|
||||
case '/': out += '/'; break;
|
||||
case 'b': out += '\b'; break;
|
||||
case 'f': out += '\f'; break;
|
||||
case 'u': {
|
||||
const hex = text.substr(i, 4);
|
||||
i += 4;
|
||||
out += String.fromCharCode(parseInt(hex, 16));
|
||||
break;
|
||||
}
|
||||
default: out += esc;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c === quote) return out;
|
||||
out += c;
|
||||
}
|
||||
throw new Error('unterminated string');
|
||||
}
|
||||
|
||||
function readValue() {
|
||||
skipWsAndComments();
|
||||
const c = peek();
|
||||
if (c === '"' || c === "'") return readString();
|
||||
if (c === '{') return readObject();
|
||||
if (c === '[') return readArray();
|
||||
if (c === 't' || c === 'f') return readBoolOrNull();
|
||||
if (c === 'n') return readBoolOrNull();
|
||||
if (c === '-' || (c >= '0' && c <= '9')) return readNumber();
|
||||
throw new Error(`unexpected char '${c}' at ${i}`);
|
||||
}
|
||||
|
||||
function readObject() {
|
||||
next(); // {
|
||||
const obj = {};
|
||||
while (true) {
|
||||
skipWsAndComments();
|
||||
if (peek() === '}') { next(); return obj; }
|
||||
// key
|
||||
let key;
|
||||
if (peek() === '"' || peek() === "'") key = readString();
|
||||
else {
|
||||
// bareword (json5 allows unquoted keys)
|
||||
let s = '';
|
||||
while (i < len && /[A-Za-z0-9_$-]/.test(text[i])) s += text[i++];
|
||||
key = s;
|
||||
}
|
||||
skipWsAndComments();
|
||||
if (next() !== ':') throw new Error(`expected ':' at ${i}`);
|
||||
obj[key] = readValue();
|
||||
skipWsAndComments();
|
||||
const nc = next();
|
||||
if (nc === ',') continue;
|
||||
if (nc === '}') return obj;
|
||||
throw new Error(`expected , or } at ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readArray() {
|
||||
next(); // [
|
||||
const arr = [];
|
||||
while (true) {
|
||||
skipWsAndComments();
|
||||
if (peek() === ']') { next(); return arr; }
|
||||
arr.push(readValue());
|
||||
skipWsAndComments();
|
||||
const nc = next();
|
||||
if (nc === ',') continue;
|
||||
if (nc === ']') return arr;
|
||||
throw new Error(`expected , or ] at ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readBoolOrNull() {
|
||||
if (text.substr(i, 4) === 'true') { i += 4; return true; }
|
||||
if (text.substr(i, 5) === 'false') { i += 5; return false; }
|
||||
if (text.substr(i, 4) === 'null') { i += 4; return null; }
|
||||
throw new Error(`bad literal at ${i}`);
|
||||
}
|
||||
|
||||
function readNumber() {
|
||||
let s = '';
|
||||
if (peek() === '-') s += next();
|
||||
while (i < len && /[0-9.eE+-]/.test(text[i])) s += next();
|
||||
const n = parseFloat(s);
|
||||
if (isNaN(n)) throw new Error(`bad number '${s}'`);
|
||||
return n;
|
||||
}
|
||||
|
||||
skipWsAndComments();
|
||||
const result = readValue();
|
||||
skipWsAndComments();
|
||||
if (i < len) throw new Error(`trailing chars at ${i}: ${text.substr(i, 30)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = parseJson5;
|
||||
Reference in New Issue
Block a user