diff --git a/common/js/diy.js b/common/js/diy.js index bac5f51..a28ac45 100644 --- a/common/js/diy.js +++ b/common/js/diy.js @@ -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() { diff --git a/components-diy/diy-group.vue b/components-diy/diy-group.vue index edc192d..4577cdd 100644 --- a/components-diy/diy-group.vue +++ b/components-diy/diy-group.vue @@ -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 = []; diff --git a/stub-app-projects/mp-harmony/entry/README-HAR-DEPS.md b/stub-app-projects/mp-harmony/entry/README-HAR-DEPS.md new file mode 100644 index 0000000..e626221 --- /dev/null +++ b/stub-app-projects/mp-harmony/entry/README-HAR-DEPS.md @@ -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/` 目录改名/移走 + → file: 引用会变成 broken,运行失败 + +## 故障现象速查 + +| 现象 | 原因 | 修复 | +|---|---|---| +| 首页只显示 TabBar,无 DIY 内容 | components_diy 缺失 | 加 file: 依赖 + Sync + Build | +| 12s splash 后页面空白 | 任意一个 HAR 子包缺失 | 跑校验脚本看哪个缺失 | +| 商品分类页空白 | pages_goods 缺失 | 同上 | +| "应用未关联服务空间" 报错 | uniCloud 服务未关联(与本依赖无关) | uniCloud 目录右键关联服务空间 | \ No newline at end of file diff --git a/stub-app-projects/mp-harmony/entry/har-deps-required.json5 b/stub-app-projects/mp-harmony/entry/har-deps-required.json5 new file mode 100644 index 0000000..4c171f5 --- /dev/null +++ b/stub-app-projects/mp-harmony/entry/har-deps-required.json5 @@ -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 成 , + // 表现为: 首页 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//src/main/resources/rawfile// + // 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" }, + }, +} \ No newline at end of file diff --git a/stub-app-projects/mp-harmony/entry/oh-package-lock.json5 b/stub-app-projects/mp-harmony/entry/oh-package-lock.json5 index a3a1f8b..c0c91a0 100644 --- a/stub-app-projects/mp-harmony/entry/oh-package-lock.json5 +++ b/stub-app-projects/mp-harmony/entry/oh-package-lock.json5 @@ -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" } } } \ No newline at end of file diff --git a/stub-app-projects/mp-harmony/entry/oh-package.json5 b/stub-app-projects/mp-harmony/entry/oh-package.json5 index 74ed887..b8e3fdc 100644 --- a/stub-app-projects/mp-harmony/entry/oh-package.json5 +++ b/stub-app-projects/mp-harmony/entry/oh-package.json5 @@ -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" + } } \ No newline at end of file diff --git a/stub-app-projects/mp-harmony/entry/scripts/check-har-deps.js b/stub-app-projects/mp-harmony/entry/scripts/check-har-deps.js new file mode 100644 index 0000000..7735d3a --- /dev/null +++ b/stub-app-projects/mp-harmony/entry/scripts/check-har-deps.js @@ -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); +} \ No newline at end of file diff --git a/stub-app-projects/mp-harmony/entry/scripts/load-json5.js b/stub-app-projects/mp-harmony/entry/scripts/load-json5.js new file mode 100644 index 0000000..3282a27 --- /dev/null +++ b/stub-app-projects/mp-harmony/entry/scripts/load-json5.js @@ -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; \ No newline at end of file