@flun/node-mobile-app 3.1.4 → 4.0.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.
- package/CHANGELOG.md +46 -15
- package/README.md +559 -545
- package/bin/cli.js +3 -2
- package/package.json +4 -3
- package/src/commands/build.js +115 -114
- package/src/commands/clean.js +4 -3
- package/src/commands/test.js +160 -109
- package/src/config.js +6 -5
- package/src/copy.js +320 -319
- package/src/fingerprint.js +123 -122
- package/src/generate.js +105 -104
- package/src/install.js +317 -215
- package/src/optimize-node-modules.js +57 -54
- package/src/patcher.js +389 -388
- package/src/platform-ios.js +6 -5
- package/src/utils.js +92 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
|
+
import chalk from 'chalk';
|
|
4
5
|
|
|
5
6
|
const require = createRequire(import.meta.url), subpath = (...parts) => './' + path.posix.join(...parts),
|
|
6
7
|
BUNDLED_DIR = 'bundled', NM_DIR = 'node_modules', PKG_JSON = 'package.json',
|
|
@@ -190,8 +191,8 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
190
191
|
if (excludePkgs.has(relName)) { skipped++; continue; }
|
|
191
192
|
if (SKIP_PKGS.has(relName)) { skipped++; continue; }
|
|
192
193
|
if (hasNativeModule(pkgDir)) { skipped++; continue; }
|
|
193
|
-
//
|
|
194
|
-
//
|
|
194
|
+
// 删除安装期脚本字段:运行时无安装期,且 Gradle npm rebuild 会触发脚本导致失败
|
|
195
|
+
// 原生模块包已在上一步跳过(其 scripts 是编译必需,必须保留)
|
|
195
196
|
if (j.scripts) { delete j.scripts; fs.writeFileSync(pj, JSON.stringify(j, null, 2)); }
|
|
196
197
|
if (countJS(pkgDir) <= 1) { skipped++; continue; }
|
|
197
198
|
// 已优化过的包跳过
|
|
@@ -230,7 +231,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
230
231
|
}
|
|
231
232
|
if (isESM) newExports[e.key] = subpath(BUNDLED_DIR, outName);
|
|
232
233
|
else {
|
|
233
|
-
// CJS
|
|
234
|
+
// CJS 包:若 bundle 末尾是 module.exports = require_xxx()(整体赋值),
|
|
234
235
|
// cjs-module-lexer 静态分析拿不到命名导出,此时定位主入口的 __commonJS 块,
|
|
235
236
|
// 块内抓 exports.X = 形式,追加显式声明让 lexer 识别;
|
|
236
237
|
try {
|
|
@@ -312,12 +313,12 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
312
313
|
walkDel(pkgDir), totalDeleted += deleted, merged++;
|
|
313
314
|
if (resJsHits && resJsHits.size > 0 && resLog)
|
|
314
315
|
for (const [k, v] of resJsHits) resLog.push(` ${relName}: ${k} (${v} 个 JS)`);
|
|
315
|
-
if (process.env.OPTIMIZE_DEBUG) console.log(` ✓ ${relName} (${parse.entries.length} 入口)`);
|
|
316
|
+
if (process.env.OPTIMIZE_DEBUG) console.log(chalk.gray(` ✓ ${relName} (${parse.entries.length} 入口)`));
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
if (resLog && resLog.length > 0) {
|
|
319
|
-
console.log(' 处理保留的资源 JS
|
|
320
|
-
for (const line of resLog) console.log(line);
|
|
320
|
+
console.log(chalk.gray(' 处理保留的资源 JS(被引用则合并,否则删除):'));
|
|
321
|
+
for (const line of resLog) console.log(chalk.gray(line));
|
|
321
322
|
}
|
|
322
323
|
return { merged, skipped, totalDeleted };
|
|
323
324
|
},
|
|
@@ -399,12 +400,12 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
399
400
|
if (cnt > 0) cleared += cnt, pkgItems.push(`${item}(${cnt})`);
|
|
400
401
|
}
|
|
401
402
|
}
|
|
402
|
-
if (process.env.OPTIMIZE_DEBUG && pkgItems.length > 0) console.log(` ✓ ${pkgRel}: ${pkgItems.join(', ')}`);
|
|
403
|
+
if (process.env.OPTIMIZE_DEBUG && pkgItems.length > 0) console.log(chalk.gray(` ✓ ${pkgRel}: ${pkgItems.join(', ')}`));
|
|
403
404
|
}
|
|
404
405
|
return cleared;
|
|
405
406
|
},
|
|
406
407
|
// ==================== 清平台 ====================
|
|
407
|
-
prunePlatform = async (nmDir, myPlat, myArch) => {
|
|
408
|
+
prunePlatform = async (nmDir, myPlat, myArch, targetPlatform) => {
|
|
408
409
|
let removed = 0;
|
|
409
410
|
const readHead = file => {
|
|
410
411
|
let fd;
|
|
@@ -463,13 +464,16 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
463
464
|
return null;
|
|
464
465
|
},
|
|
465
466
|
delFile = p => {
|
|
466
|
-
try { fs.unlinkSync(p), removed++; } catch { }
|
|
467
|
+
try { fs.statSync(p).isDirectory() ? fs.rmSync(p, { recursive: true, force: true }) : fs.unlinkSync(p), removed++; } catch { }
|
|
467
468
|
},
|
|
468
469
|
processDir = d => {
|
|
469
470
|
let entries;
|
|
470
471
|
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
471
472
|
for (const e of entries) {
|
|
472
473
|
const full = path.join(d, e.name);
|
|
474
|
+
const expectedPrebuilds = targetPlatform === 'ios' ? ['darwin'] : ['android'],
|
|
475
|
+
prebuildMatch = full.match(/[\\/]prebuilds[\\/](linux|darwin|win32|android)-/i);
|
|
476
|
+
if (prebuildMatch && !expectedPrebuilds.includes(prebuildMatch[1].toLowerCase())) { delFile(full); continue; }
|
|
473
477
|
if (e.isDirectory()) { processDir(full); continue; }
|
|
474
478
|
if (JS_RE.test(e.name)) continue;
|
|
475
479
|
let plat = suffixPlat(e.name), arch = null;
|
|
@@ -482,7 +486,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
482
486
|
if (platBad || archBad) {
|
|
483
487
|
if (process.env.OPTIMIZE_DEBUG) {
|
|
484
488
|
const rel = toPosix(path.relative(nmDir, full));
|
|
485
|
-
console.log(` ✓ ${rel} (${platBad ? '平台不符: ' + plat : '架构不符: ' + arch})`);
|
|
489
|
+
console.log(chalk.gray(` ✓ ${rel} (${platBad ? '平台不符: ' + plat : '架构不符: ' + arch})`));
|
|
486
490
|
}
|
|
487
491
|
delFile(full);
|
|
488
492
|
}
|
|
@@ -492,21 +496,19 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
492
496
|
return removed;
|
|
493
497
|
},
|
|
494
498
|
// ==================== 清开发文件 ====================
|
|
495
|
-
pruneDevFiles = async (nmDir
|
|
499
|
+
pruneDevFiles = async (nmDir) => {
|
|
496
500
|
let deleted = 0;
|
|
497
501
|
const PATTERNS = [
|
|
498
502
|
/(^|\/)tsconfig\.json$/,
|
|
499
503
|
/(^|\/)\.nycrc(\.(json|yml))?$/,
|
|
500
504
|
/(^|\/)\.editorconfig$/, /(^|\/)\.prettierignore$/, /(^|\/)\.eslintignore$/,
|
|
501
505
|
/(^|\/)\.dockerignore$/, /(^|\/)\.gitignore$/, /(^|\/)\.gitattributes$/, /(^|\/)\.npmignore$/,
|
|
502
|
-
/\.(md|markdown)$/i, /\.(
|
|
503
|
-
/\.
|
|
506
|
+
/\.(md|markdown)$/i, /\.(yml|yaml)$/i, /\.(gyp|gypi|h|hh|hpp|c|cc|cpp|cxx|m|mm)$/i,
|
|
507
|
+
/\.map$/, /metafile-.*\.json$/,
|
|
504
508
|
/(^|\/)\.eslintrc(\.(json|yml|js|cjs))?$/, /(^|\/)\.babelrc(\.(json|js))?$/,
|
|
505
509
|
/(^|\/)package-lock\.json$/, /(^|\/)\.package-lock\.json$/, /(^|\/)yarn\.lock$/,
|
|
506
510
|
/(^|\/)package-support\.json$/,
|
|
507
511
|
], DIR_SKIP = new Set([BUNDLED_DIR]),
|
|
508
|
-
COMPILE_EXTS = /\.(gyp|gypi|h|hh|hpp|c|cc|cpp|cxx|m|mm)$/i,
|
|
509
|
-
inNativePkg = p => nativePkgs.some(nd => p.startsWith(nd + path.sep)),
|
|
510
512
|
walk = d => {
|
|
511
513
|
for (const f of fs.readdirSync(d, { withFileTypes: true })) {
|
|
512
514
|
const full = path.join(d, f.name);
|
|
@@ -519,7 +521,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
519
521
|
walk(full);
|
|
520
522
|
} else {
|
|
521
523
|
const rel = toPosix(path.relative(nmDir, full));
|
|
522
|
-
if (PATTERNS.some(re => re.test(rel))
|
|
524
|
+
if (PATTERNS.some(re => re.test(rel)))
|
|
523
525
|
try { fs.unlinkSync(full), deleted++; } catch { }
|
|
524
526
|
}
|
|
525
527
|
}
|
|
@@ -529,19 +531,20 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
529
531
|
},
|
|
530
532
|
pruneIntermediates = async nmDir => {
|
|
531
533
|
let deleted = 0;
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
534
|
+
const MID_RE = /\.(o|d|mk|stamp|Makefile|iobj|ipdb|pdb|exp|lib|obj|vcxproj|tlog|recipe|lastbuildstate|nativecodeanalysis\.xml)$/i,
|
|
535
|
+
walk = d => {
|
|
536
|
+
for (const f of fs.readdirSync(d, { withFileTypes: true })) {
|
|
537
|
+
const full = path.join(d, f.name);
|
|
538
|
+
if (f.isDirectory()) walk(full);
|
|
539
|
+
else if (full.includes(path.sep + 'obj.target' + path.sep) || f.name === 'Makefile' || f.name.endsWith('.orig') || MID_RE.test(f.name)) try { fs.unlinkSync(full), deleted++; } catch { }
|
|
540
|
+
}
|
|
541
|
+
};
|
|
539
542
|
walk(nmDir);
|
|
540
543
|
return deleted;
|
|
541
544
|
},
|
|
542
545
|
applyFilesRules = async nmDir => {
|
|
543
546
|
let deleted = 0;
|
|
544
|
-
// 1) 删顶层 node
|
|
547
|
+
// 1) 删顶层 node 假包(npm 上名为 node 的包,仅提供 node.exe,运行时不需要)
|
|
545
548
|
const LICENSE_RE = /^licen[sc]e/i, TS_RE = /\.(ts|cts|mts)$/, nodePkg = path.join(nmDir, 'node');
|
|
546
549
|
if (fs.existsSync(nodePkg)) try { fs.rmSync(nodePkg, { recursive: true, force: true }), deleted++; } catch { }
|
|
547
550
|
|
|
@@ -591,7 +594,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
591
594
|
|
|
592
595
|
// ==================== 导出函数 ====================
|
|
593
596
|
/**
|
|
594
|
-
* 清除快照中的 verified
|
|
597
|
+
* 清除快照中的 verified 标记(优化开始或构建失败时调用)
|
|
595
598
|
* >查看定义:@see {@link clearVerified}
|
|
596
599
|
* @param {string} snapshotDir 快照目录
|
|
597
600
|
* @returns {Promise<void>}
|
|
@@ -605,7 +608,7 @@ const clearVerified = async snapshotDir => {
|
|
|
605
608
|
} catch { }
|
|
606
609
|
};
|
|
607
610
|
/**
|
|
608
|
-
* 标记快照 verified=true
|
|
611
|
+
* 标记快照 verified=true(构建全流程成功后由 build.js / test.js 调用)
|
|
609
612
|
* >查看定义:@see {@link markVerified}
|
|
610
613
|
* @param {string} snapshotDir 快照目录
|
|
611
614
|
* @returns {Promise<void>}
|
|
@@ -621,14 +624,14 @@ const markVerified = async snapshotDir => {
|
|
|
621
624
|
/**
|
|
622
625
|
* 执行 node_modules 六阶段优化
|
|
623
626
|
* >查看定义:@see {@link optimizeNodeModules}
|
|
624
|
-
* @param {string} tempDir node_modules
|
|
627
|
+
* @param {string} tempDir node_modules 所在目录(nodejs-assets/nodejs-project)
|
|
625
628
|
* @param {object} [config] 优化配置
|
|
626
|
-
* @param {string} [config.snapshotDir]
|
|
627
|
-
* @param {string} [config.targetPlatform]
|
|
628
|
-
* @param {string[]} [config.abiFilters] Android
|
|
629
|
-
* @param {string} config.runtimeMajor 运行时 Node
|
|
629
|
+
* @param {string} [config.snapshotDir] 快照目录(默认同 tempDir)
|
|
630
|
+
* @param {string} [config.targetPlatform] 目标平台:'android'(默认)或 'ios'
|
|
631
|
+
* @param {string[]} [config.abiFilters] Android 目标架构(默认 ['arm64-v8a']);iOS 固定保留 arm64 + x64
|
|
632
|
+
* @param {string} config.runtimeMajor 运行时 Node 主版本号(必填,从 libnode/node_version.h 读取)
|
|
630
633
|
* @param {string[]} [config.exclude] 排除的包名列表
|
|
631
|
-
* @returns {Promise<object>}
|
|
634
|
+
* @returns {Promise<object>} 各阶段统计(merged / cleared / platform / devFiles / intermediates / filesRules)
|
|
632
635
|
*/
|
|
633
636
|
const optimizeNodeModules = async (tempDir, config = {}) => {
|
|
634
637
|
const targetPlatform = config.targetPlatform || 'android', abiFilters = config.abiFilters || ['arm64-v8a'],
|
|
@@ -637,49 +640,49 @@ const optimizeNodeModules = async (tempDir, config = {}) => {
|
|
|
637
640
|
snapPath = path.join(snapshotDir, SNAPSHOT_FILE), myPlat = TARGET_PLAT_MAP[targetPlatform] || [],
|
|
638
641
|
myArch = targetPlatform === 'ios' ? IOS_ARCHES : (abiFilters || []).map(a => ABI_ARCH_MAP[a]).filter(Boolean);
|
|
639
642
|
|
|
640
|
-
if (!runtimeMajor) throw new Error('optimizeNodeModules 缺少 config.runtimeMajor
|
|
643
|
+
if (!runtimeMajor) throw new Error('optimizeNodeModules 缺少 config.runtimeMajor(应从 libnode/node_version.h 读取)');
|
|
641
644
|
if (!fs.existsSync(nmDir)) {
|
|
642
|
-
console.log('[优化] node_modules 不存在,跳过');
|
|
645
|
+
console.log(chalk.gray('[优化] node_modules 不存在,跳过'));
|
|
643
646
|
return { skipped: true };
|
|
644
647
|
}
|
|
645
648
|
|
|
646
649
|
await clearVerified(snapshotDir); // 执行优化前清 verified,构建成功后再由 build.js / test.js 写回 true
|
|
647
|
-
const t0 = Date.now()
|
|
650
|
+
const t0 = Date.now();
|
|
648
651
|
try {
|
|
649
|
-
// 阶段1:
|
|
650
|
-
console.log('
|
|
652
|
+
// 阶段1: 清空资源(跳过 js,留待合并阶段处理)
|
|
653
|
+
console.log(chalk.cyan(' [1/6] 清空复制到项目根的资源...'));
|
|
651
654
|
const c = await clearResources(nmDir, excludePkgs);
|
|
652
|
-
console.log(`
|
|
655
|
+
console.log(chalk.green(` 清空 ${c} 个文件(js 文件保留,留待合并阶段处理)`));
|
|
653
656
|
|
|
654
657
|
// 阶段2: 合并
|
|
655
|
-
console.log('
|
|
658
|
+
console.log(chalk.cyan(' [2/6] 合并 JS...'));
|
|
656
659
|
const m = await mergeAll(nmDir, excludePkgs, runtimeMajor);
|
|
657
|
-
console.log(`
|
|
660
|
+
console.log(chalk.green(` 合并 ${m.merged} 包,跳过 ${m.skipped},删 ${m.totalDeleted} 个 JS`));
|
|
658
661
|
|
|
659
662
|
// 阶段3: 清平台二进制
|
|
660
|
-
console.log('
|
|
661
|
-
const p = await prunePlatform(nmDir, myPlat, myArch);
|
|
662
|
-
console.log(`
|
|
663
|
+
console.log(chalk.cyan(' [3/6] 清理非当前平台二进制...'));
|
|
664
|
+
const p = await prunePlatform(nmDir, myPlat, myArch, targetPlatform);
|
|
665
|
+
console.log(chalk.green(` 清理 ${p} 项`));
|
|
663
666
|
|
|
664
667
|
// 阶段4: 清开发文件
|
|
665
|
-
console.log('
|
|
666
|
-
const d = await pruneDevFiles(nmDir
|
|
667
|
-
console.log(`
|
|
668
|
+
console.log(chalk.cyan(' [4/6] 清理开发文件...'));
|
|
669
|
+
const d = await pruneDevFiles(nmDir);
|
|
670
|
+
console.log(chalk.green(` 清理 ${d} 个文件`));
|
|
668
671
|
|
|
669
672
|
// 阶段5: 清中间产物
|
|
670
|
-
console.log('
|
|
673
|
+
console.log(chalk.cyan(' [5/6] 清理中间产物...'));
|
|
671
674
|
const i = await pruneIntermediates(nmDir);
|
|
672
|
-
console.log(`
|
|
675
|
+
console.log(chalk.green(` 清理 ${i} 个文件`));
|
|
673
676
|
|
|
674
|
-
// 阶段6:
|
|
675
|
-
console.log('
|
|
677
|
+
// 阶段6: 清除不参与运行的文件(与 build.js 的 files 数组对齐)
|
|
678
|
+
console.log(chalk.cyan(' [6/6] 清除不参与运行的文件和空目录...'));
|
|
676
679
|
const fr = await applyFilesRules(nmDir);
|
|
677
|
-
console.log(`
|
|
680
|
+
console.log(chalk.green(` 清理 ${fr} 项`));
|
|
678
681
|
|
|
679
682
|
// 写 platform/abiFilters/runtimeMajor,同时 verified=false
|
|
680
683
|
await writeSnapshotFlags(snapshotDir, targetPlatform, abiFilters, runtimeMajor);
|
|
681
684
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
682
|
-
console.log(`[信息] 优化完成,耗时 ${elapsed}s`);
|
|
685
|
+
console.log(chalk.green(` [信息] 优化完成,耗时 ${elapsed}s`));
|
|
683
686
|
|
|
684
687
|
return { merged: m.merged, cleared: c, platform: p, devFiles: d, intermediates: i, filesRules: fr };
|
|
685
688
|
} catch (err) {
|
|
@@ -689,7 +692,7 @@ const optimizeNodeModules = async (tempDir, config = {}) => {
|
|
|
689
692
|
}
|
|
690
693
|
};
|
|
691
694
|
/**
|
|
692
|
-
* 当前优化文件的 SHA256 前 16
|
|
695
|
+
* 当前优化文件的 SHA256 前 16 位(缓存指纹:优化代码一改,缓存自动失效)
|
|
693
696
|
* >查看定义:@see {@link SELF_HASH}
|
|
694
697
|
*/
|
|
695
698
|
const SELF_HASH = (() => {
|
|
@@ -699,4 +702,4 @@ const SELF_HASH = (() => {
|
|
|
699
702
|
} catch { return 'unknown'; }
|
|
700
703
|
})();
|
|
701
704
|
|
|
702
|
-
export { clearVerified, markVerified, optimizeNodeModules, SELF_HASH };
|
|
705
|
+
export { clearVerified, markVerified, optimizeNodeModules, SELF_HASH };
|