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