新增鸿蒙组件级componentPlaceholder自动注入脚本,解决diy组件编译为asyncComponent导致的运行时报错,自动遍历处理非页面组件的json配置文件,同时添加异常捕获避免构建中断
180 lines
6.7 KiB
JavaScript
180 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Post-build: 给鸿蒙产物里所有"非页面"组件 .json 注入 componentPlaceholder.
|
|
*
|
|
* 触发原因:
|
|
* uni-app 把每个 easycom diy-* 编译成 function () { return Promise.all([...]) } 工厂
|
|
* —— Vue 视为 asyncComponent. 鸿蒙/FormAbility 运行时在子组件级检查 placeholder,
|
|
* 不会自页面级继承. 即使子组件 .json 的 usingComponents 已列出 diy-icon,
|
|
* 仍触发 "is asyncComponent please add placeHolder".
|
|
*
|
|
* 检测策略:
|
|
* 鸿蒙构建产物里没有 .vue 文件 (已编译成 .js / .hxml). 改用 .json 上的 usingComponents:
|
|
* 若任意 diy-* 名称 (出现在右侧 target 路径 /components-diy/diy-*) → 该组件需要 placeholder.
|
|
*
|
|
* 工作流:
|
|
* 1. 给定构建产物根目录
|
|
* 2. 递归遍历所有 .json
|
|
* a. 跳过顶层 app.json 与登记在 app.json 的页面 (那些由 scripts/inject-placeholder.js 处理)
|
|
* b. 跳过没有 usingComponents 的
|
|
* c. 扫 usingComponents, 收集所有 diy-* 名字
|
|
* d. 若已经含 componentPlaceholder, 跳过
|
|
* e. 否则在对象内插入 componentPlaceholder 块
|
|
* 3. 仅在文件变化时写回
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
const DIY_DIR = path.join(PROJECT_ROOT, 'components-diy');
|
|
const APP_JSON_NAME = 'app.json';
|
|
|
|
function collectDiyNames() {
|
|
if (!fs.existsSync(DIY_DIR)) return [];
|
|
return fs.readdirSync(DIY_DIR)
|
|
.filter(f => f.startsWith('diy-') && f.endsWith('.vue'))
|
|
.map(f => f.slice(0, -'.vue'.length))
|
|
.sort();
|
|
}
|
|
const DIY_LIST = collectDiyNames();
|
|
const DIY_SET = new Set(DIY_LIST);
|
|
|
|
/**
|
|
* 从 app.json 读取哪些路径是页面 (顶层 pages + 各级 subpackages[].pages).
|
|
*/
|
|
function collectPageSet(rootDir) {
|
|
const appPath = path.join(rootDir, APP_JSON_NAME);
|
|
const set = new Set();
|
|
if (!fs.existsSync(appPath)) return set;
|
|
let json;
|
|
try { json = JSON.parse(fs.readFileSync(appPath, 'utf-8')); }
|
|
catch (e) { return set; }
|
|
function addList(prefix, arr) {
|
|
for (const p of arr || []) {
|
|
const full = prefix ? `${prefix}/${p}` : p;
|
|
set.add(full);
|
|
}
|
|
}
|
|
addList('', json.pages);
|
|
for (const sp of json.subpackages || []) {
|
|
addList(sp.root, sp.pages);
|
|
}
|
|
return set;
|
|
}
|
|
|
|
function hasPlaceholder(txt) {
|
|
return /"componentPlaceholder"\s*:/.test(txt);
|
|
}
|
|
|
|
/**
|
|
* 把 componentPlaceholder 块插入到 JSON 顶层首个键之前, 末尾带逗号.
|
|
* 输入: { "key1": ..., "key2": ... }
|
|
* 输出: { "componentPlaceholder": { ... }, "key1": ..., "key2": ... }
|
|
*/
|
|
function injectPlaceholder(jsonText) {
|
|
const sampleKey = jsonText.match(/[\r\n]([\t ]*)"[^"\r\n]+"\s*:/);
|
|
if (!sampleKey) return null;
|
|
const topIndent = sampleKey[1];
|
|
const innerIndent = topIndent + '\t';
|
|
const EOL = jsonText.includes('\r\n') ? '\r\n' : '\n';
|
|
|
|
const firstKeyMatch = jsonText.match(/[\r\n][\t ]*"[^"\r\n]+"\s*:/);
|
|
if (!firstKeyMatch) return null;
|
|
const eolLen = jsonText[firstKeyMatch.index] === '\r' ? 2 : 1;
|
|
const insertLineStart = firstKeyMatch.index + eolLen;
|
|
|
|
const blockLines = [];
|
|
blockLines.push(`${topIndent}"componentPlaceholder": {`);
|
|
for (let i = 0; i < DIY_LIST.length; i++) {
|
|
const name = DIY_LIST[i];
|
|
const isLast = i === DIY_LIST.length - 1;
|
|
blockLines.push(`${innerIndent}"${name}": "view"${isLast ? '' : ','}`);
|
|
}
|
|
blockLines.push(`${topIndent}},`);
|
|
const blockText = blockLines.join(EOL) + EOL;
|
|
|
|
const before = jsonText.slice(0, insertLineStart);
|
|
const after = jsonText.slice(insertLineStart);
|
|
const newText = before + blockText + after;
|
|
return newText === jsonText ? null : newText;
|
|
}
|
|
|
|
function processJson(jsonPath, rootDir, pageSet) {
|
|
const rel = path.relative(rootDir, jsonPath).replace(/\\/g, '/');
|
|
if (rel === APP_JSON_NAME) return 'app-skip';
|
|
// 顶层页面 .json 跳过 (页面级由 scripts/inject-placeholder.js 处理)
|
|
if (pageSet.has(rel.replace(/\.json$/, ''))) return 'page-skip';
|
|
|
|
const txt = fs.readFileSync(jsonPath, 'utf-8');
|
|
if (hasPlaceholder(txt)) return 'has-placeholder';
|
|
|
|
const m = txt.match(/"usingComponents"\s*:\s*\{([\s\S]*?)\}/);
|
|
if (!m) return 'no-usingComponents';
|
|
const inner = m[1];
|
|
const diyRefs = [];
|
|
const reKey = /"([^"]+)"\s*:/g;
|
|
let km;
|
|
while ((km = reKey.exec(inner)) !== null) {
|
|
if (DIY_SET.has(km[1])) diyRefs.push(km[1]);
|
|
}
|
|
if (diyRefs.length === 0) return 'no-diy';
|
|
|
|
const newText = injectPlaceholder(txt);
|
|
if (!newText) return 'inject-skipped';
|
|
fs.writeFileSync(jsonPath, newText, 'utf-8');
|
|
return 'injected';
|
|
}
|
|
|
|
function walkDir(root, onJson) {
|
|
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
for (const e of entries) {
|
|
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
const p = path.join(root, e.name);
|
|
if (e.isDirectory()) {
|
|
walkDir(p, onJson);
|
|
} else if (e.isFile() && e.name.endsWith('.json')) {
|
|
onJson(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
function run(rootDir) {
|
|
if (!rootDir) {
|
|
console.error('用法: node scripts/harmony-component-placeholder.js <产物根目录>');
|
|
process.exit(1);
|
|
}
|
|
if (!fs.existsSync(rootDir)) {
|
|
console.error(`目录不存在: ${rootDir}`);
|
|
process.exit(1);
|
|
}
|
|
const pageSet = collectPageSet(rootDir);
|
|
console.log(`[HarmonyPlaceholder] 扫描根: ${rootDir}`);
|
|
console.log(`[HarmonyPlaceholder] 页面清单大小: ${pageSet.size}, diy 组件清单大小: ${DIY_LIST.length}`);
|
|
|
|
const stats = { injected: 0, hasPlaceholder: 0, pageSkip: 0, noDiy: 0, noUsingComponents: 0, errors: 0 };
|
|
walkDir(rootDir, (jsonPath) => {
|
|
try {
|
|
const rel = path.relative(rootDir, jsonPath).replace(/\\/g, '/');
|
|
const r = processJson(jsonPath, rootDir, pageSet);
|
|
if (r === 'injected') {
|
|
stats.injected++;
|
|
console.log(` ✓ 注入: ${rel}`);
|
|
} else if (r === 'has-placeholder') {
|
|
stats.hasPlaceholder++;
|
|
} else if (r === 'page-skip') {
|
|
stats.pageSkip++;
|
|
} else if (r === 'no-diy') {
|
|
stats.noDiy++;
|
|
} else if (r === 'no-usingComponents') {
|
|
stats.noUsingComponents++;
|
|
}
|
|
} catch (e) {
|
|
stats.errors++;
|
|
console.error(` ERR ${jsonPath}: ${e.message}`);
|
|
}
|
|
});
|
|
console.log(`[HarmonyPlaceholder] 注入: ${stats.injected}, 已有 placeholder: ${stats.hasPlaceholder}, 跳过 (页面): ${stats.pageSkip}, 无 diy: ${stats.noDiy}, 无 usingComponents: ${stats.noUsingComponents}, 错误: ${stats.errors}`);
|
|
}
|
|
|
|
const target = process.argv[2];
|
|
run(target); |