@flun/node-mobile-app 3.1.2 → 3.1.4
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 +10 -13
- package/README.md +10 -0
- package/package.json +2 -2
- package/src/copy.js +50 -0
- package/src/optimize-node-modules.js +3 -2
- package/template/android/build.gradle +2 -0
- package/template/android/settings.gradle +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# 变更日志
|
|
2
|
-
## [3.1.
|
|
2
|
+
## [3.1.4] - 2026-10-17 11:32
|
|
3
3
|
### 修复
|
|
4
|
-
-
|
|
4
|
+
- **修复 `react-native bundle` 报 `Cannot resolve @react-native/metro-config`**:个别场景下用户根 `node_modules` 中缺失该包(如历史安装残留、依赖布局异常),RN CLI 从其自身位置向上解析不到,导致 `test` / `build` 的 bundle 阶段中断;现由 `copyTemplate` 调用新增的 `linkNestedDeps`:遍历主包 `node_modules` 顶层全部条目(含 `@scope/` 子项,不列举包名),对从用户根解析不到的条目,在用户根 `node_modules` 下建 Junction(Windows)/ symlink(macOS/Linux)指向主包物理路径;用户根已能解析的条目跳过、不改 `package.json` / `package-lock.json`、建链接失败即中断;
|
|
5
|
+
|
|
6
|
+
### 变更
|
|
7
|
+
- **Android 模板仓库增加阿里云镜像兜底**:`settings.gradle` 的 `pluginManagement.repositories` 与 `build.gradle` 的 `buildscript.repositories` 在 `google()` / `mavenCentral()` 之前插入阿里云镜像(gradle-plugin / google / public),改善国内网络拉取 `react-android`、`hermes-android` 等 AAR 时卡在官方源的问题;镜像在前、官方源在后,海外网络镜像不命中会自动回退官方源,仅多一次往返,不影响功能;不写死镜像为唯一源,镜像故障时仍可由官方源兜住;
|
|
5
8
|
|
|
6
|
-
## [3.1.
|
|
7
|
-
###
|
|
8
|
-
-
|
|
9
|
+
## [3.1.3] - 2026-10-15 23:10
|
|
10
|
+
### 修复
|
|
11
|
+
- **删除 `scripts` 字段的范围扩大**:原仅对被合并的包删除 `package.json` 的 `scripts`;跳过的包(如原生模块、JS 较少的包)仍保留,其安装期脚本会被 Gradle `npm rebuild` 触发,若脚本文件已被清理则报 `Cannot find module`、构建失败;现改为**对所有非原生包删除**(原生模块包保留,其 scripts 是编译必需);
|
|
9
12
|
|
|
10
|
-
## [3.1.
|
|
13
|
+
## [3.1.2] - 2026-10-15 21:05
|
|
11
14
|
### 修复
|
|
12
|
-
-
|
|
13
|
-
- 清空资源时**跳过 js 文件**,留待合并阶段处理;
|
|
14
|
-
- 日志如实反映:「清空 N 个文件(js 文件保留,留待合并阶段处理)」;
|
|
15
|
-
- **`clearItem` 单文件分支漏判 js(重要)**:目录分支已有「跳过 js」判断,单文件分支遗漏,导致资源目录中的**单个 js 文件**被清空为 0 字节,合并阶段读到空文件、产物损坏;现补充 js 判断,js 一律保留给合并阶段;
|
|
16
|
-
- **合并阶段日志标题**:由「处理 JS 资源(用到则合并,未用到则提前清除)」改为「处理保留的资源 JS(被引用则合并,否则删除)」,与新流程呼应;
|
|
17
|
-
- **Gradle 原生编译报 postinstall 失败(重要)**:Gradle 编译原生模块时执行 `npm rebuild`,会触发各包 postinstall;而合并阶段已删除脚本文件,导致 `Cannot find module ... copy-files.js`、构建失败;现合并阶段同步**删除 `package.json` 的 `scripts` 字段**(运行时无安装期,scripts 无用),rebuild 不再触发;
|
|
18
|
-
- **`--debug` 下 `quiet` 命令仍被静默**:`run` 的 quiet 分支未判断 debug,导致 `--debug` 时 `gradlew` / `xcodebuild` 等命令输出仍被吞;现改为「debug 时忽略 quiet,全量输出」(不影响默认模式下 test 的精简行为);
|
|
15
|
+
- 修复了 "src\optimize-node-modules.js" 中 prunePlatform 函数一些边界问题;
|
package/README.md
CHANGED
|
@@ -476,6 +476,16 @@ adb logcat | grep -E "NODEJS-MOBILE|ReactNativeJS"
|
|
|
476
476
|
- 请检查 你的 'package.json' 中 "allowScripts" 字段
|
|
477
477
|
|
|
478
478
|
|
|
479
|
+
### Q:Android 构建卡在拉取依赖(`react-android` / `hermes-android` 等)?
|
|
480
|
+
|
|
481
|
+
Android 模板的 `settings.gradle` 与 `build.gradle` 已在 `google()` / `mavenCentral()` 之前插入阿里云镜像,国内网络优先走镜像;海外网络镜像不命中会自动回退官方源,不影响功能。
|
|
482
|
+
|
|
483
|
+
若仍卡住:
|
|
484
|
+
|
|
485
|
+
- 检查镜像可达:浏览器打开 `https://maven.aliyun.com/repository/public` 是否正常;
|
|
486
|
+
- 检查本机 `~/.gradle/init.gradle` 是否有与仓库顺序冲突的配置(如强制替换仓库),有则临时移除以排除干扰;
|
|
487
|
+
- 停掉残留 Daemon 后重试:在 `node-mobile-app-build/android/` 下执行 `gradlew --stop`,再重跑 `test` / `build`。
|
|
488
|
+
|
|
479
489
|
### Q:Express 版本怎么选?
|
|
480
490
|
|
|
481
491
|
- **Android**:内置完整 ICU(full-icu)→ 可以用 Express 5
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flun/node-mobile-app",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"description": "将 Node.js 项目一键打包为 Android / iOS 移动应用,运行时由 @flun/nodejs-mobile-react-native 提供,无需修改你的 Node 代码;",
|
|
5
5
|
"author": "flun <open@flun.top>",
|
|
6
6
|
"keywords": [
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"jimp": "^1.6.1",
|
|
61
61
|
"prettier": "^3.9.10",
|
|
62
62
|
"react": "^19.3.1",
|
|
63
|
-
"react-native": "^0.
|
|
63
|
+
"react-native": "^0.88.0",
|
|
64
64
|
"react-native-webview": "^14.0.1"
|
|
65
65
|
},
|
|
66
66
|
"allowScripts": {
|
package/src/copy.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ensureDir, removeDir, readJson, isExcluded, copyFileIfChanged, writeIfChanged, buildEslintConfigSource
|
|
5
5
|
} from './utils.js';
|
|
6
6
|
import { resolveSdkPath } from './config.js';
|
|
7
|
+
import { createRequire } from 'module';
|
|
7
8
|
|
|
8
9
|
const BUILD_DIR_NAME = 'node-mobile-app-build',
|
|
9
10
|
TEMPLATE_EXCLUDE = [
|
|
@@ -94,6 +95,54 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
|
|
|
94
95
|
);
|
|
95
96
|
}
|
|
96
97
|
},
|
|
98
|
+
/**
|
|
99
|
+
* 把主包 node_modules 里被 npm 嵌套(未提升到用户根)的依赖,按需链接到用户根 node_modules 下,
|
|
100
|
+
* 让 RN CLI / Metro 从用户根也能解析到它们。
|
|
101
|
+
* - 判据:从用户根视角解析不到,才建链接;能解析则跳过;
|
|
102
|
+
* - 范围:遍历主包 node_modules 顶层全部条目(含 @scope/ 下每个子项),不列举任何包名;
|
|
103
|
+
* - 落点:只在用户根 node_modules 缺失条目时建 symlink,不覆盖已有内容、不碰 package.json / package-lock.json;
|
|
104
|
+
* - 建链接失败:中断报错,不静默。
|
|
105
|
+
*/
|
|
106
|
+
linkNestedDeps = (pkgRoot, buildDir) => {
|
|
107
|
+
const pkgNm = path.join(pkgRoot, 'node_modules');
|
|
108
|
+
if (!fs.existsSync(pkgNm)) return;
|
|
109
|
+
|
|
110
|
+
const userProjectDir = path.dirname(buildDir),
|
|
111
|
+
userNm = path.join(userProjectDir, 'node_modules'),
|
|
112
|
+
req = createRequire(path.join(userProjectDir, 'index.js')),
|
|
113
|
+
candidates = [];
|
|
114
|
+
for (const entry of fs.readdirSync(pkgNm, { withFileTypes: true })) {
|
|
115
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
116
|
+
if (entry.name.startsWith('@')) {
|
|
117
|
+
const scopeDir = path.join(pkgNm, entry.name);
|
|
118
|
+
for (const sub of fs.readdirSync(scopeDir, { withFileTypes: true }))
|
|
119
|
+
if (sub.isDirectory()) candidates.push(entry.name + '/' + sub.name);
|
|
120
|
+
} else candidates.push(entry.name);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let linked = 0;
|
|
124
|
+
for (const name of candidates) {
|
|
125
|
+
try { req.resolve(name); continue; } catch { /* 解析不到,进入兜底 */ }
|
|
126
|
+
const src = path.join(pkgNm, name);
|
|
127
|
+
if (!fs.existsSync(src)) continue;
|
|
128
|
+
const realSrc = fs.realpathSync(src), dst = path.join(userNm, name);
|
|
129
|
+
let occupied = false;
|
|
130
|
+
try { fs.lstatSync(dst); occupied = true; } catch { /* 不存在 */ }
|
|
131
|
+
if (occupied) continue;
|
|
132
|
+
ensureDir(path.dirname(dst));
|
|
133
|
+
try {
|
|
134
|
+
fs.symlinkSync(realSrc, dst, 'junction');
|
|
135
|
+
linked++;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'建立兜底链接失败:' + dst + ' -> ' + realSrc + '\n' +
|
|
139
|
+
' 原因:' + err.message + '\n' +
|
|
140
|
+
' 请检查 node_modules 是否被其它程序占用后重试'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (linked > 0) console.log(' ✓ 已兜底链接 ' + linked + ' 个嵌套依赖到用户根 node_modules');
|
|
145
|
+
},
|
|
97
146
|
/**
|
|
98
147
|
* 把 CLI 包里的 template/ 逐文件覆盖到 build 目录
|
|
99
148
|
* - 只覆盖模板里存在的文件,不删除 buildDir 里已有的其他内容(保留 Gradle 增量缓存)
|
|
@@ -110,6 +159,7 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
|
|
|
110
159
|
copyDirOverwrite(templateDir, buildDir, TEMPLATE_EXCLUDE, buildDir, keepSet);
|
|
111
160
|
fs.writeFileSync(path.join(buildDir, 'eslint.config.js'), buildEslintConfigSource(), 'utf-8');
|
|
112
161
|
syncTemplateDependencies(pkgRoot, buildDir);
|
|
162
|
+
linkNestedDeps(pkgRoot, buildDir);
|
|
113
163
|
console.log(' ✓ 模板已更新到', BUILD_DIR_NAME + '/');
|
|
114
164
|
if (keepSet.size > 0) console.log(' 保留文件:', Array.from(keepSet).join(', '));
|
|
115
165
|
},
|
|
@@ -190,6 +190,9 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
190
190
|
if (excludePkgs.has(relName)) { skipped++; continue; }
|
|
191
191
|
if (SKIP_PKGS.has(relName)) { skipped++; continue; }
|
|
192
192
|
if (hasNativeModule(pkgDir)) { skipped++; continue; }
|
|
193
|
+
// 删除安装期脚本字段:运行时无安装期,且 Gradle npm rebuild 会触发脚本导致失败
|
|
194
|
+
// 原生模块包已在上一步跳过(其 scripts 是编译必需,必须保留)
|
|
195
|
+
if (j.scripts) { delete j.scripts; fs.writeFileSync(pj, JSON.stringify(j, null, 2)); }
|
|
193
196
|
if (countJS(pkgDir) <= 1) { skipped++; continue; }
|
|
194
197
|
// 已优化过的包跳过
|
|
195
198
|
const mainStr = String(j.main || ''), exportsStr = JSON.stringify(j.exports || '');
|
|
@@ -285,8 +288,6 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
285
288
|
j.main = isESM ? newExports['.'] : (newExports['.'].require || newExports['.'].default || newExports['.']);
|
|
286
289
|
else delete j.main;
|
|
287
290
|
if (isESM && j.main) j.module = j.main;
|
|
288
|
-
// 删除安装期脚本字段:移动端 Gradle 会 npm rebuild,触发 postinstall;运行时无安装期,scripts 无用
|
|
289
|
-
delete j.scripts;
|
|
290
291
|
fs.writeFileSync(pj, JSON.stringify(j, null, 2));
|
|
291
292
|
|
|
292
293
|
// 删除原 JS
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
pluginManagement {
|
|
1
|
+
pluginManagement {
|
|
2
|
+
repositories {
|
|
3
|
+
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
|
|
4
|
+
maven { url 'https://maven.aliyun.com/repository/google' }
|
|
5
|
+
maven { url 'https://maven.aliyun.com/repository/public' }
|
|
6
|
+
google()
|
|
7
|
+
mavenCentral()
|
|
8
|
+
}
|
|
9
|
+
includeBuild("../../node_modules/@react-native/gradle-plugin")
|
|
10
|
+
}
|
|
2
11
|
plugins { id("com.facebook.react.settings") }
|
|
3
12
|
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
|
|
4
13
|
rootProject.name = 'MyApp'
|