@flun/node-mobile-app 4.1.2 → 4.2.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/Android/346/236/204/345/273/272/346/214/207/345/215/227.md +1 -1
- package/CHANGELOG.md +15 -22
- package/README.md +67 -13
- package/bin/cli.js +3 -3
- package/mobileAppConfig.js +17 -4
- package/package.json +1 -1
- package/scripts/copy-files.js +3 -2
- package/src/commands/build.js +105 -99
- package/src/commands/clean.js +12 -6
- package/src/commands/test.js +141 -133
- package/src/config.js +88 -52
- package/src/copy.js +302 -215
- package/src/fingerprint.js +96 -77
- package/src/generate.js +33 -21
- package/src/install.js +176 -162
- package/src/optimize-node-modules.js +5 -4
- package/src/patcher.js +438 -358
- package/src/platform-ios.js +122 -106
- package/src/utils.js +351 -252
- package/template/.prettierrc.js +8 -2
- package/template/App.tsx +98 -87
- package/template/babel.config.js +8 -2
- package/template/eslint.config.js +8 -2
- package/template/metro.config.js +9 -3
- package//350/207/252/345/256/232/344/271/211libnode/346/214/207/345/215/227.md +2 -2
package/src/fingerprint.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import crypto from 'crypto';
|
|
1
3
|
import fs from 'fs';
|
|
2
4
|
import path from 'path';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
5
|
import { ensureDir, removeDir } from './utils.js';
|
|
5
|
-
|
|
6
|
+
|
|
6
7
|
|
|
7
8
|
const FINGERPRINT_FILE = '.build-fingerprint.json',
|
|
8
9
|
hashUserDeps = userProjectDir => {
|
|
@@ -15,8 +16,7 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
|
|
|
15
16
|
}
|
|
16
17
|
else h.update('no-package');
|
|
17
18
|
return h.digest('hex');
|
|
18
|
-
},
|
|
19
|
-
writeFp = (file, fp) => fs.writeFileSync(file, JSON.stringify(fp, null, 2), 'utf-8'),
|
|
19
|
+
}, writeFp = (file, fp) => fs.writeFileSync(file, JSON.stringify(fp, null, 2), 'utf-8'),
|
|
20
20
|
clearGradleCaches = (buildDir, userProjectDir) => {
|
|
21
21
|
removeDir(path.join(buildDir, 'android', 'build'));
|
|
22
22
|
removeDir(path.join(buildDir, 'android', 'app', 'build'));
|
|
@@ -45,86 +45,105 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
|
|
|
45
45
|
};
|
|
46
46
|
walk(templateDir);
|
|
47
47
|
return h.digest('hex');
|
|
48
|
-
}
|
|
49
|
-
computeFingerprint = (userProjectDir, config, pkgRoot) => {
|
|
50
|
-
const abiFilters = [...(config.android.abiFilters || [])].sort(), libnode = {},
|
|
51
|
-
pluginDir = path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native'),
|
|
52
|
-
libnodeBin = path.join(pluginDir, 'android', 'libnode', 'bin');
|
|
48
|
+
};
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
/**
|
|
51
|
+
* 对比构建指纹,变化时清理对应缓存;指纹写入 buildDir
|
|
52
|
+
* >查看定义:@see {@link checkAndResetCache}
|
|
53
|
+
* @param {string} buildDir 构建目录
|
|
54
|
+
* @param {string} userProjectDir 用户项目目录
|
|
55
|
+
* @param {object} config 用户配置
|
|
56
|
+
* @param {string} pkgRoot CLI 包根目录
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
const checkAndResetCache = (buildDir, userProjectDir, config, pkgRoot) => {
|
|
60
|
+
ensureDir(buildDir);
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (fs.existsSync(cliPkg)) try { cliVersion = JSON.parse(fs.readFileSync(cliPkg, 'utf-8')).version; } catch { };
|
|
62
|
+
const fpFile = path.join(buildDir, FINGERPRINT_FILE), newFp = computeFingerprint(userProjectDir, config, pkgRoot);
|
|
63
|
+
let oldFp = null;
|
|
64
|
+
if (fs.existsSync(fpFile)) try { oldFp = JSON.parse(fs.readFileSync(fpFile, 'utf-8')); } catch { };
|
|
65
|
+
if (!oldFp) return console.log(chalk.green(' ✓ 全新构建,无缓存可清')), writeFp(fpFile, newFp);
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
targetSdkVersion: a.targetSdkVersion || null,
|
|
78
|
-
compileSdkVersion: a.compileSdkVersion || null,
|
|
79
|
-
buildToolsVersion: a.buildToolsVersion || null,
|
|
80
|
-
usesCleartextTraffic: a.usesCleartextTraffic !== false,
|
|
81
|
-
permissions: [...(a.permissions || [])].sort(),
|
|
82
|
-
};
|
|
67
|
+
const abiChanged = JSON.stringify(oldFp.abiFilters) !== JSON.stringify(newFp.abiFilters),
|
|
68
|
+
libnodeChanged = JSON.stringify(oldFp.libnode) !== JSON.stringify(newFp.libnode),
|
|
69
|
+
pluginChanged = oldFp.pluginVersion !== newFp.pluginVersion,
|
|
70
|
+
cliChanged = oldFp.cliVersion !== newFp.cliVersion, depsChanged = oldFp.depsHash !== newFp.depsHash,
|
|
71
|
+
appInfoChanged = JSON.stringify(oldFp.appInfo) !== JSON.stringify(newFp.appInfo),
|
|
72
|
+
templateChanged = oldFp.templateHash !== newFp.templateHash,
|
|
73
|
+
structural = abiChanged || libnodeChanged || pluginChanged || cliChanged || templateChanged;
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
if (structural) {
|
|
76
|
+
const reasons = [];
|
|
77
|
+
if (abiChanged) reasons.push('ABI 配置');
|
|
78
|
+
if (libnodeChanged) reasons.push('libnode.so');
|
|
79
|
+
if (pluginChanged) reasons.push('插件版本');
|
|
80
|
+
if (cliChanged) reasons.push('CLI 版本');
|
|
81
|
+
if (templateChanged) reasons.push('模板文件');
|
|
82
|
+
if (depsChanged) reasons.push('用户依赖');
|
|
83
|
+
if (appInfoChanged) reasons.push('应用信息');
|
|
84
|
+
console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 Gradle 缓存、CMake 缓存与依赖缓存'));
|
|
85
|
+
clearGradleCaches(buildDir, userProjectDir);
|
|
86
|
+
removeDir(path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native', 'android', '.cxx'));
|
|
87
|
+
clearDepsCaches(buildDir);
|
|
88
|
+
}
|
|
89
|
+
else if (depsChanged || appInfoChanged) {
|
|
90
|
+
const reasons = [], targets = [];
|
|
91
|
+
if (depsChanged) reasons.push('用户依赖'), targets.push('依赖缓存', 'Gradle 缓存');
|
|
92
|
+
if (appInfoChanged) reasons.push('应用信息'), targets.push('Gradle 缓存');
|
|
93
|
+
console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 ' + [...new Set(targets)].join('、')));
|
|
94
|
+
clearGradleCaches(buildDir, userProjectDir);
|
|
95
|
+
if (depsChanged) clearDepsCaches(buildDir);
|
|
96
|
+
}
|
|
97
|
+
else console.log(chalk.gray(' ✓ 缓存有效,跳过清理'));
|
|
89
98
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (fs.existsSync(fpFile)) try { oldFp = JSON.parse(fs.readFileSync(fpFile, 'utf-8')); } catch { };
|
|
93
|
-
if (!oldFp) return console.log(chalk.green(' ✓ 全新构建,无缓存可清')), writeFp(fpFile, newFp);
|
|
99
|
+
writeFp(fpFile, newFp);
|
|
100
|
+
};
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
/**
|
|
103
|
+
* 计算当前构建指纹,用于判断是否需要清理缓存
|
|
104
|
+
* >查看定义:@see {@link computeFingerprint}
|
|
105
|
+
* @param {string} userProjectDir 用户项目目录
|
|
106
|
+
* @param {object} config 用户配置
|
|
107
|
+
* @param {string} pkgRoot CLI 包根目录
|
|
108
|
+
* @returns {object} 含 abiFilters、libnode、pluginVersion、cliVersion、depsHash、appInfo、templateHash
|
|
109
|
+
*/
|
|
110
|
+
const computeFingerprint = (userProjectDir, config, pkgRoot) => {
|
|
111
|
+
const abiFilters = [...(config.android.abiFilters || [])].sort(), libnode = {},
|
|
112
|
+
pluginDir = path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native'),
|
|
113
|
+
libnodeBin = path.join(pluginDir, 'android', 'libnode', 'bin');
|
|
102
114
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (depsChanged) reasons.push('用户依赖');
|
|
111
|
-
if (appInfoChanged) reasons.push('应用信息');
|
|
112
|
-
console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 Gradle 缓存、CMake 缓存与依赖缓存'));
|
|
113
|
-
clearGradleCaches(buildDir, userProjectDir);
|
|
114
|
-
removeDir(path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native', 'android', '.cxx'));
|
|
115
|
-
clearDepsCaches(buildDir);
|
|
116
|
-
}
|
|
117
|
-
else if (depsChanged || appInfoChanged) {
|
|
118
|
-
const reasons = [], targets = [];
|
|
119
|
-
if (depsChanged) reasons.push('用户依赖'), targets.push('依赖缓存', 'Gradle 缓存');
|
|
120
|
-
if (appInfoChanged) reasons.push('应用信息'), targets.push('Gradle 缓存');
|
|
121
|
-
console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 ' + [...new Set(targets)].join('、')));
|
|
122
|
-
clearGradleCaches(buildDir, userProjectDir);
|
|
123
|
-
if (depsChanged) clearDepsCaches(buildDir);
|
|
115
|
+
if (fs.existsSync(libnodeBin)) {
|
|
116
|
+
for (const arch of fs.readdirSync(libnodeBin)) {
|
|
117
|
+
const so = path.join(libnodeBin, arch, 'libnode.so');
|
|
118
|
+
if (fs.existsSync(so)) {
|
|
119
|
+
const stat = fs.statSync(so);
|
|
120
|
+
libnode[arch] = { size: stat.size, mtime: Math.floor(stat.mtimeMs) };
|
|
121
|
+
}
|
|
124
122
|
}
|
|
125
|
-
|
|
123
|
+
}
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
let pluginVersion = null, cliVersion = null;
|
|
126
|
+
const pluginPkg = path.join(pluginDir, 'package.json'), cliPkg = path.join(pkgRoot, 'package.json');
|
|
127
|
+
if (fs.existsSync(pluginPkg))
|
|
128
|
+
try { pluginVersion = JSON.parse(fs.readFileSync(pluginPkg, 'utf-8')).version; } catch { };
|
|
129
|
+
if (fs.existsSync(cliPkg)) try { cliVersion = JSON.parse(fs.readFileSync(cliPkg, 'utf-8')).version; } catch { };
|
|
130
|
+
|
|
131
|
+
const depsHash = hashUserDeps(userProjectDir), a = config.android || {},
|
|
132
|
+
appInfo = {
|
|
133
|
+
appName: config.appName || null,
|
|
134
|
+
appId: config.appId || null,
|
|
135
|
+
versionCode: config.versionCode || null,
|
|
136
|
+
versionName: config.versionName || null,
|
|
137
|
+
minSdkVersion: a.minSdkVersion || null,
|
|
138
|
+
targetSdkVersion: a.targetSdkVersion || null,
|
|
139
|
+
compileSdkVersion: a.compileSdkVersion || null,
|
|
140
|
+
buildToolsVersion: a.buildToolsVersion || null,
|
|
141
|
+
usesCleartextTraffic: a.usesCleartextTraffic !== false,
|
|
142
|
+
permissions: [...(a.permissions || [])].sort(),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const templateHash = hashTemplate(path.join(pkgRoot, 'template'));
|
|
146
|
+
return { abiFilters, libnode, pluginVersion, cliVersion, depsHash, appInfo, templateHash };
|
|
147
|
+
};
|
|
129
148
|
|
|
130
|
-
export {
|
|
149
|
+
export { checkAndResetCache, computeFingerprint };
|
package/src/generate.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from 'chalk';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { writeIfChanged } from './utils.js';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* 生成 main.js(桥接代码,用户不需要手写)
|
|
@@ -9,6 +8,11 @@ import chalk from 'chalk';
|
|
|
9
8
|
* 上报机制(通过 rn-bridge channel 发消息给 App.tsx):
|
|
10
9
|
* - 'Node started on <URL>' server.js 成功调用 listen
|
|
11
10
|
* - 'Node failed: <原因>' 加载异常或 5 秒内未捕获 listen
|
|
11
|
+
* >查看定义:@see {@link generateMainJs}
|
|
12
|
+
* @param {string} targetDir 生成目录
|
|
13
|
+
* @param {object} config 用户配置
|
|
14
|
+
* @param {string} moduleType 'module' 时注入 createRequire 头
|
|
15
|
+
* @returns {void}
|
|
12
16
|
*/
|
|
13
17
|
const generateMainJs = (targetDir, config, moduleType) => {
|
|
14
18
|
const entry = config.serverPath.startsWith('./') ? config.serverPath : './' + config.serverPath,
|
|
@@ -75,8 +79,8 @@ const generateMainJs = (targetDir, config, moduleType) => {
|
|
|
75
79
|
})();
|
|
76
80
|
|
|
77
81
|
${config.advanced.keepAlive === false
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
? '// keepAlive: false - 不发送心跳'
|
|
83
|
+
: "setInterval(() => rn_bridge.channel.send('heartbeat'), " + hb + ");"}
|
|
80
84
|
|
|
81
85
|
rn_bridge.channel.on('message', msg => rn_bridge.channel.send('Echo: ' + msg));
|
|
82
86
|
})();`,
|
|
@@ -84,21 +88,29 @@ const generateMainJs = (targetDir, config, moduleType) => {
|
|
|
84
88
|
? `import { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\n\n` : '',
|
|
85
89
|
file = path.join(targetDir, 'main.js'), changed = writeIfChanged(file, header + body);
|
|
86
90
|
console.log(chalk.green(changed ? ' ✓ 已生成 main.js' : ' ✓ main.js 无变化'));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 生成 mobileApp.runtime.ts,写入 WebView 运行时配置
|
|
95
|
+
* >查看定义:@see {@link generateRuntime}
|
|
96
|
+
* @param {string} buildDir 构建目录
|
|
97
|
+
* @param {object} config 用户配置
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
100
|
+
const generateRuntime = (buildDir, config) => {
|
|
101
|
+
const data = {
|
|
102
|
+
showLoading: config.webview.showLoading,
|
|
103
|
+
loadingText: config.webview.loadingText,
|
|
104
|
+
webview: {
|
|
105
|
+
enableJavaScript: config.webview.enableJavaScript,
|
|
106
|
+
enableDomStorage: config.webview.enableDomStorage,
|
|
107
|
+
allowFileAccess: config.webview.allowFileAccess,
|
|
108
|
+
mixedContentMode: config.webview.mixedContentMode,
|
|
109
|
+
backgroundColor: config.webview.backgroundColor
|
|
110
|
+
},
|
|
111
|
+
}, content = `// 自动生成,请勿手改\nexport default ${JSON.stringify(data, null, 2)} as const;\n`,
|
|
112
|
+
changed = writeIfChanged(path.join(buildDir, 'mobileApp.runtime.ts'), content);
|
|
113
|
+
console.log(chalk.green(changed ? ' ✓ 已生成 mobileApp.runtime.ts' : ' ✓ mobileApp.runtime.ts 无变化'));
|
|
114
|
+
};
|
|
103
115
|
|
|
104
|
-
export { generateMainJs, generateRuntime }
|
|
116
|
+
export { generateMainJs, generateRuntime };
|