@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/template/App.tsx CHANGED
@@ -1,105 +1,116 @@
1
+ import * as NodeMobile from '@flun/nodejs-mobile-react-native';
1
2
  import { useEffect, useState } from 'react';
2
- import { View, Text, ActivityIndicator, StyleSheet, useColorScheme } from 'react-native';
3
+ import { ActivityIndicator, StyleSheet, Text, useColorScheme, View } from 'react-native';
3
4
  import { WebView } from 'react-native-webview';
4
- import * as NodeMobile from '@flun/nodejs-mobile-react-native';
5
5
  import runtime from './mobileApp.runtime';
6
6
 
7
- // 背景色解析:支持以下三种配置
8
- // '#ffffff' / 'white' 等字符串 → 固定色
9
- // 'system' → 跟随系统(内部用默认浅/深色)
10
- // { light: '#fff', dark: '#000' } → 按系统主题切换
7
+ /**
8
+ * 背景色解析,支持三种配置:
9
+ * - '#ffffff' / 'white' 等字符串:固定色
10
+ * - 'system':跟随系统(内部用默认浅/深色)
11
+ * - { light: '#fff', dark: '#000' }:按系统主题切换
12
+ * @param {string|object} bg 背景色配置
13
+ * @param {string|null|undefined} theme 当前系统主题
14
+ * @returns {string} 解析后的颜色值
15
+ */
11
16
  const resolveBgColor = (bg: string | { light: string; dark: string }, theme: string | null | undefined): string => {
12
17
  if (typeof bg === 'object' && bg !== null)
13
18
  return (theme === 'dark' ? bg.dark : bg.light) || bg.light || '#ffffff';
14
19
  if (bg === 'system') return theme === 'dark' ? '#121212' : '#ffffff';
15
20
  return bg || '#ffffff';
16
- },
17
- App = () => {
18
- const systemTheme = useColorScheme(),
19
- [url, setUrl] = useState<string | null>(null), [failedReason, setFailedReason] = useState<string | null>(null),
20
- [showDetail, setShowDetail] = useState(false),
21
- styles = StyleSheet.create({
22
- fill: { flex: 1 },
23
- loading: { flex: 1, justifyContent: 'center', alignItems: 'center' },
24
- errorContainer: {
25
- flex: 1,
26
- justifyContent: 'center',
27
- alignItems: 'center',
28
- padding: 24,
29
- },
30
- errorTitle: { fontSize: 20, fontWeight: '600', marginBottom: 16, color: '#c00' },
31
- errorHint: { fontSize: 14, textAlign: 'center', color: '#888' },
32
- detailToggle: { fontSize: 13, marginTop: 24, color: '#06c' },
33
- detailText: {
34
- fontSize: 12,
35
- marginTop: 12,
36
- paddingHorizontal: 16,
37
- color: '#666',
38
- fontFamily: 'monospace',
39
- textAlign: 'center',
40
- },
41
- });
42
- useEffect(() => {
43
- NodeMobile.start('main.js');
21
+ };
44
22
 
45
- const listener = (msg: unknown) => {
46
- if (typeof msg !== 'string') return;
47
- if (msg.startsWith('Node started on ')) {
48
- const target = msg.slice('Node started on '.length).trim();
49
- setFailedReason(null);
50
- setUrl(target);
51
- } else if (msg.startsWith('Node failed: ')) {
52
- const reason = msg.slice('Node failed: '.length).trim();
53
- setFailedReason(reason);
54
- }
55
- };
23
+ /**
24
+ * 应用根组件:启动 Node 服务,按消息切换 loading / 错误页 / WebView
25
+ * >查看定义:@see {@link App}
26
+ * @returns {JSX.Element} 当前状态对应的视图
27
+ */
28
+ const App = () => {
29
+ const systemTheme = useColorScheme(),
30
+ [url, setUrl] = useState<string | null>(null), [failedReason, setFailedReason] = useState<string | null>(null),
31
+ [showDetail, setShowDetail] = useState(false),
32
+ styles = StyleSheet.create({
33
+ fill: { flex: 1 },
34
+ loading: { flex: 1, justifyContent: 'center', alignItems: 'center' },
35
+ errorContainer: {
36
+ flex: 1,
37
+ justifyContent: 'center',
38
+ alignItems: 'center',
39
+ padding: 24,
40
+ },
41
+ errorTitle: { fontSize: 20, fontWeight: '600', marginBottom: 16, color: '#c00' },
42
+ errorHint: { fontSize: 14, textAlign: 'center', color: '#888' },
43
+ detailToggle: { fontSize: 13, marginTop: 24, color: '#06c' },
44
+ detailText: {
45
+ fontSize: 12,
46
+ marginTop: 12,
47
+ paddingHorizontal: 16,
48
+ color: '#666',
49
+ fontFamily: 'monospace',
50
+ textAlign: 'center',
51
+ },
52
+ });
53
+ useEffect(() => {
54
+ NodeMobile.start('main.js');
56
55
 
57
- NodeMobile.channel.addListener('message', listener);
58
- return () => NodeMobile.channel.removeListener('message', listener);
59
- }, []);
56
+ const listener = (msg: unknown) => {
57
+ if (typeof msg !== 'string') return;
58
+ if (msg.startsWith('Node started on ')) {
59
+ const target = msg.slice('Node started on '.length).trim();
60
+ setFailedReason(null);
61
+ setUrl(target);
62
+ } else if (msg.startsWith('Node failed: ')) {
63
+ const reason = msg.slice('Node failed: '.length).trim();
64
+ setFailedReason(reason);
65
+ }
66
+ };
60
67
 
61
- // 失败:显示错误页(用户看提示,开发者点开看详情)
62
- if (failedReason) {
63
- return (
64
- <View style={styles.errorContainer}>
65
- <Text style={styles.errorTitle}>应用启动失败</Text>
66
- <Text style={styles.errorHint}>请稍后重试,或联系应用发布者</Text>
67
- <Text
68
- style={styles.detailToggle}
69
- onPress={() => setShowDetail((v) => !v)}
70
- >
71
- {showDetail ? '▼ 收起详细信息' : '▶ 查看详细信息'}
72
- </Text>
73
- {showDetail && (
74
- <Text style={styles.detailText}>{failedReason}</Text>
75
- )}
76
- </View>
77
- );
78
- }
68
+ NodeMobile.channel.addListener('message', listener);
69
+ return () => NodeMobile.channel.removeListener('message', listener);
70
+ }, []);
79
71
 
80
- // 尚未就绪:loading
81
- if (!url) {
82
- if (!runtime.showLoading) return <View style={styles.fill} />;
83
- return (
84
- <View style={styles.loading}>
85
- <ActivityIndicator size="large" />
86
- <Text>{runtime.loadingText}</Text>
87
- </View>
88
- );
89
- }
72
+ // 失败:显示错误页(用户看提示,开发者点开看详情)
73
+ if (failedReason) {
74
+ return (
75
+ <View style={styles.errorContainer}>
76
+ <Text style={styles.errorTitle}>应用启动失败</Text>
77
+ <Text style={styles.errorHint}>请稍后重试,或联系应用发布者</Text>
78
+ <Text
79
+ style={styles.detailToggle}
80
+ onPress={() => setShowDetail((v) => !v)}
81
+ >
82
+ {showDetail ? '▼ 收起详细信息' : '▶ 查看详细信息'}
83
+ </Text>
84
+ {showDetail && (
85
+ <Text style={styles.detailText}>{failedReason}</Text>
86
+ )}
87
+ </View>
88
+ );
89
+ }
90
90
 
91
+ // 尚未就绪:loading
92
+ if (!url) {
93
+ if (!runtime.showLoading) return <View style={styles.fill} />;
91
94
  return (
92
- <WebView
93
- source={{ uri: url }}
94
- style={[styles.fill, { backgroundColor: resolveBgColor(runtime.webview.backgroundColor, systemTheme) }]}
95
- originWhitelist={['*']}
96
- javaScriptEnabled={runtime.webview.enableJavaScript}
97
- domStorageEnabled={runtime.webview.enableDomStorage}
98
- allowFileAccess={runtime.webview.allowFileAccess}
99
- allowUniversalAccessFromFileURLs={true}
100
- mixedContentMode={runtime.webview.mixedContentMode}
101
- />
95
+ <View style={styles.loading}>
96
+ <ActivityIndicator size="large" />
97
+ <Text>{runtime.loadingText}</Text>
98
+ </View>
102
99
  );
103
- };
100
+ }
101
+
102
+ return (
103
+ <WebView
104
+ source={{ uri: url }}
105
+ style={[styles.fill, { backgroundColor: resolveBgColor(runtime.webview.backgroundColor, systemTheme) }]}
106
+ originWhitelist={['*']}
107
+ javaScriptEnabled={runtime.webview.enableJavaScript}
108
+ domStorageEnabled={runtime.webview.enableDomStorage}
109
+ allowFileAccess={runtime.webview.allowFileAccess}
110
+ allowUniversalAccessFromFileURLs={true}
111
+ mixedContentMode={runtime.webview.mixedContentMode}
112
+ />
113
+ );
114
+ };
104
115
 
105
116
  export { App };
@@ -1,3 +1,9 @@
1
- export default {
1
+ /**
2
+ * Babel 编译配置,使用 React Native 官方预设
3
+ * >查看定义:@see {@link babelConfig}
4
+ */
5
+ const babelConfig = {
2
6
  presets: ['module:@react-native/babel-preset'],
3
- };
7
+ };
8
+
9
+ export default babelConfig;
@@ -1,6 +1,12 @@
1
1
  import reactNativeConfig from '@react-native/eslint-config/flat';
2
2
 
3
- export default [
3
+ /**
4
+ * ESLint flat config,基于 React Native 官方配置
5
+ * >查看定义:@see {@link eslintConfig}
6
+ */
7
+ const eslintConfig = [
4
8
  ...reactNativeConfig,
5
9
  // 你的自定义配置写在这里
6
- ];
10
+ ];
11
+
12
+ export default eslintConfig;
@@ -3,8 +3,8 @@ import exclusionListModule from 'metro-config/private/defaults/exclusionList';
3
3
  import { dirname, resolve } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
 
6
- const exclusionList = exclusionListModule.default,
7
- __filename = fileURLToPath(import.meta.url), __dirname = dirname(__filename), workspaceRoot = resolve(__dirname, '..'),
6
+ const exclusionList = exclusionListModule.default, __filename = fileURLToPath(import.meta.url),
7
+ __dirname = dirname(__filename), workspaceRoot = resolve(__dirname, '..'),
8
8
  config = {
9
9
  watchFolders: [__dirname, resolve(workspaceRoot, 'node_modules')],
10
10
  resolver: {
@@ -13,4 +13,10 @@ const exclusionList = exclusionListModule.default,
13
13
  }
14
14
  };
15
15
 
16
- export default mergeConfig(getDefaultConfig(__dirname), config);
16
+ /**
17
+ * Metro 打包配置,合并默认配置与 workspace 解析路径
18
+ * >查看定义:@see {@link metroConfig}
19
+ */
20
+ const metroConfig = mergeConfig(getDefaultConfig(__dirname), config);
21
+
22
+ export default metroConfig;
@@ -40,7 +40,7 @@
40
40
  - GitHub
41
41
  - 源码仓库:[OpenFlun/nodejs-mobile](https://github.com/OpenFlun/nodejs-mobile)
42
42
  - Release 页面:[Releases](https://github.com/OpenFlun/nodejs-mobile/releases)
43
- - Gitee(国内)
43
+ - Gitee(中国)
44
44
  - 源码仓库:[OpenFlun/nodejs-mobile](https://gitee.com/OpenFlun/nodejs-mobile)
45
45
  - Release 页面:[Releases](https://gitee.com/OpenFlun/nodejs-mobile/releases)
46
46
 
@@ -221,7 +221,7 @@ Start-Sleep -Seconds 5
221
221
  ### 3.1 准备源码与 NDK
222
222
 
223
223
  ```bash
224
- # 克隆源码(国内推荐 Gitee)
224
+ # 克隆源码(中国推荐 Gitee)
225
225
  git clone https://gitee.com/OpenFlun/nodejs-mobile.git
226
226
  cd nodejs-mobile
227
227