@flun/node-mobile-app 3.0.0 → 3.1.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 +11 -5
- package/README.md +5 -5
- package/package.json +1 -1
- package/src/install.js +1 -1
- package/src/optimize-node-modules.js +65 -67
- package/src/utils.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# 变更日志
|
|
2
2
|
|
|
3
|
+
## [3.1.0] - 2026-10-15 20:15
|
|
4
|
+
### 修复
|
|
5
|
+
- **优化阶段步骤顺序调整(重要)**:构建时原优化顺序是「先合并 JS、后清空资源」——合并会删除 postinstall 脚本文件,导致清空阶段读不到脚本、无法提取资源目录,报「清空 0 项」、资源目录残留;现改为**先清空资源、后合并**:
|
|
6
|
+
- 清空资源时**跳过 js 文件**,留待合并阶段处理;
|
|
7
|
+
- 日志如实反映:「清空 N 个文件(js 文件保留,留待合并阶段处理)」;
|
|
8
|
+
- **`clearItem` 单文件分支漏判 js(重要)**:目录分支已有「跳过 js」判断,单文件分支遗漏,导致资源目录中的**单个 js 文件**被清空为 0 字节,合并阶段读到空文件、产物损坏;现补充 js 判断,js 一律保留给合并阶段;
|
|
9
|
+
- **合并阶段日志标题**:由「处理 JS 资源(用到则合并,未用到则提前清除)」改为「处理保留的资源 JS(被引用则合并,否则删除)」,与新流程呼应;
|
|
10
|
+
- **Gradle 原生编译报 postinstall 失败(重要)**:Gradle 编译原生模块时执行 `npm rebuild`,会触发各包 postinstall;而合并阶段已删除脚本文件,导致 `Cannot find module ... copy-files.js`、构建失败;现合并阶段同步**删除 `package.json` 的 `scripts` 字段**(运行时无安装期,scripts 无用),rebuild 不再触发;
|
|
11
|
+
- **`--debug` 下 `quiet` 命令仍被静默**:`run` 的 quiet 分支未判断 debug,导致 `--debug` 时 `gradlew` / `xcodebuild` 等命令输出仍被吞;现改为「debug 时忽略 quiet,全量输出」(不影响默认模式下 test 的精简行为);
|
|
12
|
+
|
|
3
13
|
## [3.0.0] - 2026-10-13 21:19
|
|
4
14
|
### 新增
|
|
5
15
|
- **node_modules 优化系统(默认开启)**:构建时自动执行六阶段优化,大幅减少打包文件数、加速启动。不改变项目源码,对任意 Node.js 项目通用;
|
|
@@ -32,8 +42,4 @@
|
|
|
32
42
|
|
|
33
43
|
## [2.1.2] - 2026-09-29 19:53
|
|
34
44
|
### 更改
|
|
35
|
-
- 加固了拷贝文件的一些逻辑;
|
|
36
|
-
|
|
37
|
-
## [2.1.1] - 2026-09-29 13:55
|
|
38
|
-
### 优化
|
|
39
|
-
- 优化了 'README.md' 文档;
|
|
45
|
+
- 加固了拷贝文件的一些逻辑;
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- **增量构建**:文件指纹判断缓存,无变化时 10 秒级重跑
|
|
18
18
|
- **跨平台**:Android(Windows / macOS / Linux)+ iOS(仅 macOS)
|
|
19
19
|
- **离线可用**:`build` 打出的 APK / IPA 内置 JS,无需连电脑也能跑
|
|
20
|
-
- **node_modules
|
|
20
|
+
- **node_modules 优化(默认开启)**:构建时自动执行六阶段优化——先清空 postinstall 资源(保留 js 供合并)、再合并 JS、清平台二进制、清开发文件(原生模块编译必需文件保留)、清中间产物、清冗余与空目录;大幅减少打包文件数与体积、加速启动,无需配置
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
│ ├── fingerprint.js # 缓存指纹(判断是否需要清理构建缓存)
|
|
55
55
|
│ ├── generate.js # 生成 main.js(桥接)+ mobileApp.runtime.ts(运行时配置)
|
|
56
56
|
│ ├── install.js # 依赖安装 + 插件 abiFilters patch + WebView SSL patch
|
|
57
|
-
│ ├── optimize-node-modules.js # node_modules
|
|
57
|
+
│ ├── optimize-node-modules.js # node_modules 优化(六阶段:清资源 / 合并 JS / 清平台二进制 / 清开发文件 / 清中间产物 / 清冗余)
|
|
58
58
|
│ ├── patcher.js # 应用信息 / Android / iOS 配置 patch
|
|
59
59
|
│ ├── platform-ios.js # iOS 构建(pod install / xcodebuild archive / export)
|
|
60
60
|
│ ├── utils.js # 通用工具(文件操作 / 进程调用 / adb / hermesc 定位)
|
|
@@ -350,8 +350,8 @@ signing: {
|
|
|
350
350
|
|
|
351
351
|
**六阶段流程**:
|
|
352
352
|
|
|
353
|
-
1.
|
|
354
|
-
2.
|
|
353
|
+
1. **清空资源**:扫描各包 `postinstall` 引用的目录/文件,保留目录结构、清空内容;js 文件一律保留,留待合并阶段处理;
|
|
354
|
+
2. **合并 JS**:按包入口 bundle,多个 JS 文件合并为单文件(`bundled/` 目录);合并后同步删除 `package.json` 的 `scripts` 字段(运行时无安装期,且 Gradle `npm rebuild` 会触发脚本导致失败);
|
|
355
355
|
3. **清平台二进制**:按目标平台 + `abiFilters` 逐文件判定,删除非本机二进制;
|
|
356
356
|
4. **清开发文件**:删除 `.md` / `.map` / `.d.ts` / `.h` / `.c` 等非运行文件;**原生模块包(含 `binding.gyp`)内的编译必需文件保留**;
|
|
357
357
|
5. **清中间产物**;
|
|
@@ -363,7 +363,7 @@ signing: {
|
|
|
363
363
|
|
|
364
364
|
- `optimize: { enabled: false }` 整体关闭优化;
|
|
365
365
|
- `optimize: { exclude: ['包名'] }` 排除优化时异常或不想优化的包;
|
|
366
|
-
- 加 `--debug`
|
|
366
|
+
- 加 `--debug` 查看优化各阶段明细与各命令完整输出。
|
|
367
367
|
|
|
368
368
|
## 服务地址说明
|
|
369
369
|
|
package/package.json
CHANGED
package/src/install.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { run } from './utils.js';
|
|
4
|
-
import {
|
|
4
|
+
import { clearVerified, optimizeNodeModules, SELF_HASH } from './optimize-node-modules.js';
|
|
5
5
|
const
|
|
6
6
|
/**
|
|
7
7
|
* 内部:从 libnode 的 node_version.h 读取运行时 Node 主版本号
|
|
@@ -33,35 +33,33 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
33
33
|
old.platform = targetPlatform, old.abiFilters = abiFilters, old.runtimeMajor = runtimeMajor;
|
|
34
34
|
old.optimizeHash = SELF_HASH, old.verified = false, old.optimized = true;
|
|
35
35
|
fs.writeFileSync(p, JSON.stringify(old, null, 2));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (fs.existsSync(nested)) result.push(...findPackages(nested));
|
|
36
|
+
},
|
|
37
|
+
// ==================== 扫描工具 ====================
|
|
38
|
+
findPackages = dir => {
|
|
39
|
+
const result = [];
|
|
40
|
+
if (!fs.existsSync(dir)) return result;
|
|
41
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
42
|
+
if (entry === '.bin' || entry.startsWith('.')) continue;
|
|
43
|
+
const full = path.join(dir, entry);
|
|
44
|
+
if (!fs.statSync(full).isDirectory()) continue;
|
|
45
|
+
if (entry === NM_DIR) { result.push(...findPackages(full)); continue; }
|
|
46
|
+
if (entry.startsWith('@')) {
|
|
47
|
+
for (const sub of fs.readdirSync(full)) {
|
|
48
|
+
const pkgDir = path.join(full, sub);
|
|
49
|
+
if (fs.statSync(pkgDir).isDirectory()) {
|
|
50
|
+
result.push(pkgDir);
|
|
51
|
+
const nested = path.join(pkgDir, NM_DIR);
|
|
52
|
+
if (fs.existsSync(nested)) result.push(...findPackages(nested));
|
|
53
|
+
}
|
|
55
54
|
}
|
|
55
|
+
} else {
|
|
56
|
+
result.push(full);
|
|
57
|
+
const nested = path.join(full, NM_DIR);
|
|
58
|
+
if (fs.existsSync(nested)) result.push(...findPackages(nested));
|
|
56
59
|
}
|
|
57
|
-
} else {
|
|
58
|
-
result.push(full);
|
|
59
|
-
const nested = path.join(full, NM_DIR);
|
|
60
|
-
if (fs.existsSync(nested)) result.push(...findPackages(nested));
|
|
61
60
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
},
|
|
61
|
+
return result;
|
|
62
|
+
},
|
|
65
63
|
hasNativeModule = dir => {
|
|
66
64
|
let found = false;
|
|
67
65
|
const walk = d => {
|
|
@@ -287,6 +285,8 @@ const findPackages = dir => {
|
|
|
287
285
|
j.main = isESM ? newExports['.'] : (newExports['.'].require || newExports['.'].default || newExports['.']);
|
|
288
286
|
else delete j.main;
|
|
289
287
|
if (isESM && j.main) j.module = j.main;
|
|
288
|
+
// 删除安装期脚本字段:移动端 Gradle 会 npm rebuild,触发 postinstall;运行时无安装期,scripts 无用
|
|
289
|
+
delete j.scripts;
|
|
290
290
|
fs.writeFileSync(pj, JSON.stringify(j, null, 2));
|
|
291
291
|
|
|
292
292
|
// 删除原 JS
|
|
@@ -315,7 +315,7 @@ const findPackages = dir => {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
if (resLog && resLog.length > 0) {
|
|
318
|
-
console.log('
|
|
318
|
+
console.log(' 处理保留的资源 JS(被引用则合并,否则删除):');
|
|
319
319
|
for (const line of resLog) console.log(line);
|
|
320
320
|
}
|
|
321
321
|
return { merged, skipped, totalDeleted };
|
|
@@ -363,20 +363,17 @@ const findPackages = dir => {
|
|
|
363
363
|
return [...new Set(items)];
|
|
364
364
|
},
|
|
365
365
|
clearItem = (p, refs) => {
|
|
366
|
+
let n = 0;
|
|
366
367
|
const st = fs.statSync(p);
|
|
367
368
|
if (st.isDirectory()) {
|
|
368
369
|
for (const f of fs.readdirSync(p)) {
|
|
369
370
|
const full = path.join(p, f), sub = fs.statSync(full);
|
|
370
|
-
if (sub.isDirectory()) clearItem(full, refs);
|
|
371
|
-
else {
|
|
372
|
-
if (refs.has(full)) continue;
|
|
373
|
-
fs.rmSync(full, { force: true });
|
|
374
|
-
}
|
|
371
|
+
if (sub.isDirectory()) n += clearItem(full, refs);
|
|
372
|
+
else if (!JS_RE.test(f) && !refs.has(full)) try { fs.rmSync(full, { force: true }), n++; } catch { }
|
|
375
373
|
}
|
|
376
|
-
} else {
|
|
377
|
-
if (refs.has(p)) return;
|
|
378
|
-
fs.writeFileSync(p, '');
|
|
379
374
|
}
|
|
375
|
+
else if (!JS_RE.test(path.basename(p)) && !refs.has(p)) fs.writeFileSync(p, ''), n++;
|
|
376
|
+
return n;
|
|
380
377
|
},
|
|
381
378
|
clearResources = async (nmDir, excludePkgs) => {
|
|
382
379
|
const refs = collectReferencedFiles(nmDir);
|
|
@@ -396,8 +393,10 @@ const findPackages = dir => {
|
|
|
396
393
|
const scriptAbs = path.resolve(pkgDir, scriptRel);
|
|
397
394
|
if (!fs.existsSync(scriptAbs)) continue;
|
|
398
395
|
const code = fs.readFileSync(scriptAbs, 'utf8'), items = extractResources(pkgDir, code);
|
|
399
|
-
for (const item of items)
|
|
400
|
-
|
|
396
|
+
for (const item of items) {
|
|
397
|
+
const cnt = clearItem(path.join(pkgDir, item), refs);
|
|
398
|
+
if (cnt > 0) cleared += cnt, pkgItems.push(`${item}(${cnt})`);
|
|
399
|
+
}
|
|
401
400
|
}
|
|
402
401
|
if (process.env.OPTIMIZE_DEBUG && pkgItems.length > 0) console.log(` ✓ ${pkgRel}: ${pkgItems.join(', ')}`);
|
|
403
402
|
}
|
|
@@ -586,11 +585,25 @@ const findPackages = dir => {
|
|
|
586
585
|
|
|
587
586
|
// ==================== 导出函数 ====================
|
|
588
587
|
/**
|
|
589
|
-
*
|
|
588
|
+
* 清除快照中的 verified 标记(优化开始或构建失败时调用)
|
|
590
589
|
* >查看定义:@see {@link clearVerified}
|
|
591
590
|
* @param {string} snapshotDir 快照目录
|
|
592
591
|
* @returns {Promise<void>}
|
|
593
592
|
*/
|
|
593
|
+
const clearVerified = async snapshotDir => {
|
|
594
|
+
const p = path.join(snapshotDir, SNAPSHOT_FILE);
|
|
595
|
+
if (!fs.existsSync(p)) return;
|
|
596
|
+
try {
|
|
597
|
+
const old = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
598
|
+
if (old.verified !== false) old.verified = false, fs.writeFileSync(p, JSON.stringify(old, null, 2));
|
|
599
|
+
} catch { }
|
|
600
|
+
};
|
|
601
|
+
/**
|
|
602
|
+
* 标记快照 verified=true(构建全流程成功后由 build.js / test.js 调用)
|
|
603
|
+
* >查看定义:@see {@link markVerified}
|
|
604
|
+
* @param {string} snapshotDir 快照目录
|
|
605
|
+
* @returns {Promise<void>}
|
|
606
|
+
*/
|
|
594
607
|
const markVerified = async snapshotDir => {
|
|
595
608
|
const p = path.join(snapshotDir, SNAPSHOT_FILE);
|
|
596
609
|
if (!fs.existsSync(p)) return;
|
|
@@ -601,7 +614,7 @@ const markVerified = async snapshotDir => {
|
|
|
601
614
|
};
|
|
602
615
|
/**
|
|
603
616
|
* 执行 node_modules 六阶段优化
|
|
604
|
-
* >查看定义:@see {@link
|
|
617
|
+
* >查看定义:@see {@link optimizeNodeModules}
|
|
605
618
|
* @param {string} tempDir node_modules 所在目录(nodejs-assets/nodejs-project)
|
|
606
619
|
* @param {object} [config] 优化配置
|
|
607
620
|
* @param {string} [config.snapshotDir] 快照目录(默认同 tempDir)
|
|
@@ -615,29 +628,28 @@ const optimizeNodeModules = async (tempDir, config = {}) => {
|
|
|
615
628
|
const targetPlatform = config.targetPlatform || 'android', abiFilters = config.abiFilters || ['arm64-v8a'],
|
|
616
629
|
runtimeMajor = config.runtimeMajor, excludePkgs = new Set(config.exclude || []),
|
|
617
630
|
nmDir = path.join(tempDir, NM_DIR), snapshotDir = config.snapshotDir || tempDir,
|
|
618
|
-
snapPath = path.join(snapshotDir, SNAPSHOT_FILE),
|
|
619
|
-
myPlat = TARGET_PLAT_MAP[targetPlatform] || [],
|
|
631
|
+
snapPath = path.join(snapshotDir, SNAPSHOT_FILE), myPlat = TARGET_PLAT_MAP[targetPlatform] || [],
|
|
620
632
|
myArch = targetPlatform === 'ios' ? IOS_ARCHES : (abiFilters || []).map(a => ABI_ARCH_MAP[a]).filter(Boolean);
|
|
621
|
-
if (!runtimeMajor) throw new Error('optimizeNodeModules 缺少 config.runtimeMajor(应从 libnode/node_version.h 读取)');
|
|
622
633
|
|
|
634
|
+
if (!runtimeMajor) throw new Error('optimizeNodeModules 缺少 config.runtimeMajor(应从 libnode/node_version.h 读取)');
|
|
623
635
|
if (!fs.existsSync(nmDir)) {
|
|
624
636
|
console.log('[优化] node_modules 不存在,跳过');
|
|
625
637
|
return { skipped: true };
|
|
626
638
|
}
|
|
639
|
+
|
|
627
640
|
await clearVerified(snapshotDir); // 执行优化前清 verified,构建成功后再由 build.js / test.js 写回 true
|
|
628
|
-
const t0 = Date.now(),
|
|
629
|
-
nativePkgs = findPackages(nmDir).filter(p => fs.existsSync(path.join(p, 'binding.gyp')));
|
|
641
|
+
const t0 = Date.now(), nativePkgs = findPackages(nmDir).filter(p => fs.existsSync(path.join(p, 'binding.gyp')));
|
|
630
642
|
try {
|
|
631
|
-
// 阶段1:
|
|
632
|
-
console.log(' [1/6]
|
|
643
|
+
// 阶段1: 清空资源(跳过 js,留待合并阶段处理)
|
|
644
|
+
console.log(' [1/6] 清空复制到项目根的资源...');
|
|
645
|
+
const c = await clearResources(nmDir, excludePkgs);
|
|
646
|
+
console.log(` 清空 ${c} 个文件(js 文件保留,留待合并阶段处理)`);
|
|
647
|
+
|
|
648
|
+
// 阶段2: 合并
|
|
649
|
+
console.log(' [2/6] 合并 JS...');
|
|
633
650
|
const m = await mergeAll(nmDir, excludePkgs, runtimeMajor);
|
|
634
651
|
console.log(` 合并 ${m.merged} 包,跳过 ${m.skipped},删 ${m.totalDeleted} 个 JS`);
|
|
635
652
|
|
|
636
|
-
// 阶段2: 清空资源
|
|
637
|
-
console.log(' [2/6] 清空复制到项目根的资源...');
|
|
638
|
-
const c = await clearResources(nmDir, excludePkgs);
|
|
639
|
-
console.log(` 清空 ${c} 项`);
|
|
640
|
-
|
|
641
653
|
// 阶段3: 清平台二进制
|
|
642
654
|
console.log(' [3/6] 清理非当前平台二进制...');
|
|
643
655
|
const p = await prunePlatform(nmDir, myPlat, myArch);
|
|
@@ -670,23 +682,9 @@ const optimizeNodeModules = async (tempDir, config = {}) => {
|
|
|
670
682
|
throw err;
|
|
671
683
|
}
|
|
672
684
|
};
|
|
673
|
-
/**
|
|
674
|
-
* 清除快照中的 verified 标记(优化开始或构建失败时调用)
|
|
675
|
-
* >查看定义:@see {@link markVerified}
|
|
676
|
-
* @param {string} snapshotDir 快照目录
|
|
677
|
-
* @returns {Promise<void>}
|
|
678
|
-
*/
|
|
679
|
-
const clearVerified = async snapshotDir => {
|
|
680
|
-
const p = path.join(snapshotDir, SNAPSHOT_FILE);
|
|
681
|
-
if (!fs.existsSync(p)) return;
|
|
682
|
-
try {
|
|
683
|
-
const old = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
684
|
-
if (old.verified !== false) old.verified = false, fs.writeFileSync(p, JSON.stringify(old, null, 2));
|
|
685
|
-
} catch { }
|
|
686
|
-
};
|
|
687
685
|
/**
|
|
688
686
|
* 当前优化文件的 SHA256 前 16 位(缓存指纹:优化代码一改,缓存自动失效)
|
|
689
|
-
* >查看定义:@see {@link
|
|
687
|
+
* >查看定义:@see {@link SELF_HASH}
|
|
690
688
|
*/
|
|
691
689
|
const SELF_HASH = (() => {
|
|
692
690
|
try {
|
|
@@ -695,4 +693,4 @@ const SELF_HASH = (() => {
|
|
|
695
693
|
} catch { return 'unknown'; }
|
|
696
694
|
})();
|
|
697
695
|
|
|
698
|
-
export { markVerified, optimizeNodeModules,
|
|
696
|
+
export { clearVerified, markVerified, optimizeNodeModules, SELF_HASH };
|
package/src/utils.js
CHANGED
|
@@ -85,7 +85,7 @@ const ensureDir = dir => {
|
|
|
85
85
|
*/
|
|
86
86
|
run = (bin, args, opts = {}) => {
|
|
87
87
|
const { quiet, ...restOpts } = opts;
|
|
88
|
-
if (!quiet) {
|
|
88
|
+
if (!quiet || process.env.OPTIMIZE_DEBUG) {
|
|
89
89
|
const r = spawnCmd(bin, args, { stdio: 'inherit', ...restOpts });
|
|
90
90
|
if (r.status !== 0) throw new Error(`${bin} ${args.join(' ')} 退出码 ${r.status}`);
|
|
91
91
|
return r;
|