@bm-fe/react-native-multi-bundle 1.0.0-beta.0 → 1.0.0-beta.10
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/INTEGRATION.md +163 -62
- package/android/moduleloader/build.gradle +93 -0
- package/android/moduleloader/src/main/AndroidManifest.xml +4 -0
- package/android/moduleloader/src/main/AndroidManifestNew.xml +3 -0
- package/android/moduleloader/src/main/java/com/bitmart/exchange/module/loader/ModuleLoaderModule.kt +555 -0
- package/android/moduleloader/src/main/java/com/bitmart/exchange/module/loader/ModuleLoaderPackage.kt +25 -0
- package/ios/ModuleLoader/ModuleLoaderModule.h +12 -0
- package/ios/ModuleLoader/ModuleLoaderModule.mm +488 -0
- package/package.json +59 -33
- package/react-native-multi-bundle.podspec +29 -0
- package/react-native.config.js +26 -0
- package/scripts/build-multi-bundle.js +44 -5
- package/scripts/sync-bundles-to-assets.js +3 -3
- package/src/multi-bundle/LocalBundleManager.ts +28 -15
- package/src/multi-bundle/ModuleLoaderMock.ts +105 -17
- package/src/multi-bundle/ModuleRegistry.ts +1 -3
- package/src/multi-bundle/README.md +2 -0
- package/src/multi-bundle/init.ts +94 -51
- package/templates/metro.config.js.template +528 -5
|
@@ -13,17 +13,41 @@
|
|
|
13
13
|
* - --env: 'development' | 'staging' | 'production' (默认: 'production')
|
|
14
14
|
*
|
|
15
15
|
* 环境变量:
|
|
16
|
-
* - PROJECT_ROOT:
|
|
16
|
+
* - PROJECT_ROOT: 项目根目录(可选,默认从当前工作目录向上查找包含 multi-bundle.config.json 的目录)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const { execSync } = require('child_process');
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
/**
|
|
24
|
+
* 查找项目根目录
|
|
25
|
+
* 从当前工作目录向上查找,直到找到包含 multi-bundle.config.json 的目录
|
|
26
|
+
* 如果找不到,则使用当前工作目录(通常打包命令在项目根目录执行)
|
|
27
|
+
*/
|
|
28
|
+
function findProjectRoot() {
|
|
29
|
+
// 如果通过环境变量指定,直接使用
|
|
30
|
+
if (process.env.PROJECT_ROOT) {
|
|
31
|
+
return process.env.PROJECT_ROOT;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 从当前工作目录开始向上查找
|
|
35
|
+
let currentDir = process.cwd();
|
|
36
|
+
const root = path.parse(currentDir).root;
|
|
37
|
+
|
|
38
|
+
while (currentDir !== root) {
|
|
39
|
+
const configFile = path.join(currentDir, 'multi-bundle.config.json');
|
|
40
|
+
if (fs.existsSync(configFile)) {
|
|
41
|
+
return currentDir;
|
|
42
|
+
}
|
|
43
|
+
currentDir = path.dirname(currentDir);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 如果找不到配置文件,使用当前工作目录(通常打包命令在项目根目录执行)
|
|
47
|
+
return process.cwd();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PROJECT_ROOT = findProjectRoot();
|
|
27
51
|
const CONFIG_FILE = path.join(PROJECT_ROOT, 'multi-bundle.config.json');
|
|
28
52
|
const OUTPUT_DIR = path.join(PROJECT_ROOT, 'build/bundles');
|
|
29
53
|
|
|
@@ -176,6 +200,7 @@ function removePreludeFromBundle(bundlePath) {
|
|
|
176
200
|
function ensureOutputDir(platform) {
|
|
177
201
|
const platformDir = path.join(OUTPUT_DIR, platform);
|
|
178
202
|
const modulesDir = path.join(platformDir, 'modules');
|
|
203
|
+
const assetsDir = path.join(platformDir, 'assets');
|
|
179
204
|
|
|
180
205
|
if (!fs.existsSync(platformDir)) {
|
|
181
206
|
fs.mkdirSync(platformDir, { recursive: true });
|
|
@@ -183,6 +208,9 @@ function ensureOutputDir(platform) {
|
|
|
183
208
|
if (!fs.existsSync(modulesDir)) {
|
|
184
209
|
fs.mkdirSync(modulesDir, { recursive: true });
|
|
185
210
|
}
|
|
211
|
+
if (!fs.existsSync(assetsDir)) {
|
|
212
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
213
|
+
}
|
|
186
214
|
|
|
187
215
|
return platformDir;
|
|
188
216
|
}
|
|
@@ -209,6 +237,16 @@ function buildBundle(entryFile, outputFile, platform, env = 'development', progr
|
|
|
209
237
|
// ⭐ 关键:判断是否是模块 bundle(通过检查 entry 路径)
|
|
210
238
|
const isModuleBundle = entry.includes('src/modules/');
|
|
211
239
|
|
|
240
|
+
// 计算 assets 输出目录(统一放在平台目录下的 assets 文件夹)
|
|
241
|
+
const platformDir = path.dirname(outputFile);
|
|
242
|
+
const assetsDest = path.join(platformDir, 'assets');
|
|
243
|
+
const assetsDestRelative = path.relative(PROJECT_ROOT, assetsDest);
|
|
244
|
+
|
|
245
|
+
// 确保 assets 目录存在
|
|
246
|
+
if (!fs.existsSync(assetsDest)) {
|
|
247
|
+
fs.mkdirSync(assetsDest, { recursive: true });
|
|
248
|
+
}
|
|
249
|
+
|
|
212
250
|
const commandParts = [
|
|
213
251
|
// 主 bundle 和模块 bundle 都设置 RN_MULTI_BUNDLE_BUILD,但使用不同的 RN_BUILD_TYPE
|
|
214
252
|
'RN_MULTI_BUNDLE_BUILD=true',
|
|
@@ -218,6 +256,7 @@ function buildBundle(entryFile, outputFile, platform, env = 'development', progr
|
|
|
218
256
|
`--bundle-output ${out}`,
|
|
219
257
|
`--platform ${platform}`,
|
|
220
258
|
`--dev ${dev}`,
|
|
259
|
+
`--assets-dest ${assetsDestRelative}`,
|
|
221
260
|
'--reset-cache',
|
|
222
261
|
];
|
|
223
262
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* 同步 Bundle 到 Assets 目录脚本
|
|
5
5
|
* 将构建好的 bundle 文件同步到 Android/iOS 的 assets 目录
|
|
6
6
|
*
|
|
7
|
-
* Android: android/app/src/main/assets/
|
|
8
|
-
* iOS: ios/DemoProject/
|
|
7
|
+
* Android: android/app/src/main/assets/
|
|
8
|
+
* iOS: ios/DemoProject/Bundles/
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
@@ -14,7 +14,7 @@ const path = require('path');
|
|
|
14
14
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
15
15
|
const BUILD_DIR = path.join(PROJECT_ROOT, 'build/bundles');
|
|
16
16
|
const ANDROID_ASSETS_DIR = path.join(PROJECT_ROOT, 'android/app/src/main/assets');
|
|
17
|
-
const IOS_ASSETS_DIR = path.join(PROJECT_ROOT, 'ios/DemoProject/
|
|
17
|
+
const IOS_ASSETS_DIR = path.join(PROJECT_ROOT, 'ios/DemoProject/Bundles');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* ANSI 颜色工具函数
|
|
@@ -56,14 +56,37 @@ function convertNativeManifestToBundleManifest(nativeManifest: any): BundleManif
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* 获取当前激活包的 manifest
|
|
59
|
-
* -
|
|
60
|
-
* -
|
|
59
|
+
* - 优先从 Native 模块获取(支持 CodePush 等热更新场景)
|
|
60
|
+
* - 如果 Native 返回 null,在开发环境下降级到开发服务器获取
|
|
61
|
+
* - 生产环境下如果 Native 返回 null,抛出异常
|
|
61
62
|
*/
|
|
62
63
|
async function getCurrentBundleManifest(): Promise<BundleManifest> {
|
|
63
64
|
// 生产环境:从 Native 模块获取 manifest
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// if (!__DEV__) {
|
|
66
|
+
// try {
|
|
67
|
+
// const nativeManifest = await NativeModules.ModuleLoader.getCurrentBundleManifestContent();
|
|
68
|
+
// if (nativeManifest) {
|
|
69
|
+
// return convertNativeManifestToBundleManifest(nativeManifest);
|
|
70
|
+
// } else {
|
|
71
|
+
// // Native 模块返回 null 或 undefined
|
|
72
|
+
// throw new Error(
|
|
73
|
+
// '[LocalBundleManager] Native module returned null manifest. ' +
|
|
74
|
+
// 'Please ensure bundle-manifest.json exists in the app bundle.'
|
|
75
|
+
// );
|
|
76
|
+
// }
|
|
77
|
+
// } catch (error) {
|
|
78
|
+
// console.error(
|
|
79
|
+
// `[LocalBundleManager] Failed to get manifest from Native: ${error}`
|
|
80
|
+
// );
|
|
81
|
+
// // 生产环境无法获取 manifest 时抛出异常
|
|
82
|
+
// throw new Error(
|
|
83
|
+
// `[LocalBundleManager] Failed to get manifest from Native module: ${error}`
|
|
84
|
+
// );
|
|
85
|
+
// }
|
|
86
|
+
// }
|
|
87
|
+
console.log("ReactNativeJS","nativeManifest getCurrentBundleManifestContent")
|
|
88
|
+
const nativeManifest = await NativeModules.ModuleLoader.getCurrentBundleManifestContent();
|
|
89
|
+
console.log("ReactNativeJS","nativeManifest "+nativeManifest)
|
|
67
90
|
if (nativeManifest) {
|
|
68
91
|
return convertNativeManifestToBundleManifest(nativeManifest);
|
|
69
92
|
} else {
|
|
@@ -73,16 +96,6 @@ async function getCurrentBundleManifest(): Promise<BundleManifest> {
|
|
|
73
96
|
'Please ensure bundle-manifest.json exists in the app bundle.'
|
|
74
97
|
);
|
|
75
98
|
}
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error(
|
|
78
|
-
`[LocalBundleManager] Failed to get manifest from Native: ${error}`
|
|
79
|
-
);
|
|
80
|
-
// 生产环境无法获取 manifest 时抛出异常
|
|
81
|
-
throw new Error(
|
|
82
|
-
`[LocalBundleManager] Failed to get manifest from Native module: ${error}`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
99
|
|
|
87
100
|
// 开发环境:从开发服务器获取 manifest
|
|
88
101
|
const config = getGlobalConfig();
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { Platform } from 'react-native';
|
|
11
11
|
import { HMRClient } from './HMRClient';
|
|
12
|
+
import { getGlobalConfig } from './config';
|
|
12
13
|
|
|
13
14
|
interface LoadResult {
|
|
14
15
|
success: boolean;
|
|
@@ -22,29 +23,117 @@ const loadedBundles = new Set<string>();
|
|
|
22
23
|
// 正在加载中的 bundle(并发复用)
|
|
23
24
|
const inflightBundles = new Map<string, Promise<LoadResult>>();
|
|
24
25
|
|
|
26
|
+
// 缓存 multi-bundle.config.json(开发环境)
|
|
27
|
+
let cachedMultiBundleConfig: any = null;
|
|
28
|
+
let configLoadPromise: Promise<any> | null = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 从开发服务器获取 multi-bundle.config.json
|
|
32
|
+
*/
|
|
33
|
+
async function getMultiBundleConfig(): Promise<any> {
|
|
34
|
+
// 如果已缓存,直接返回
|
|
35
|
+
if (cachedMultiBundleConfig) {
|
|
36
|
+
return cachedMultiBundleConfig;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 如果正在加载,等待加载完成
|
|
40
|
+
if (configLoadPromise) {
|
|
41
|
+
return configLoadPromise;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 开始加载配置
|
|
45
|
+
configLoadPromise = (async () => {
|
|
46
|
+
try {
|
|
47
|
+
const config = getGlobalConfig();
|
|
48
|
+
const devServer = config?.devServer;
|
|
49
|
+
const host = devServer?.host || (Platform.OS === 'android' ? '10.0.2.2' : 'localhost');
|
|
50
|
+
const port = devServer?.port || 8081;
|
|
51
|
+
const protocol = devServer?.protocol || 'http';
|
|
52
|
+
|
|
53
|
+
const configUrl = `${protocol}://${host}:${port}/multi-bundle.config.json`;
|
|
54
|
+
const response = await fetch(configUrl);
|
|
55
|
+
|
|
56
|
+
if (response.ok) {
|
|
57
|
+
const configData = await response.json();
|
|
58
|
+
cachedMultiBundleConfig = configData;
|
|
59
|
+
return configData;
|
|
60
|
+
} else {
|
|
61
|
+
console.warn(
|
|
62
|
+
`[ModuleLoaderMock] Failed to fetch multi-bundle.config.json: HTTP ${response.status}`
|
|
63
|
+
);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.warn(
|
|
68
|
+
`[ModuleLoaderMock] Failed to fetch multi-bundle.config.json: ${error}`
|
|
69
|
+
);
|
|
70
|
+
return null;
|
|
71
|
+
} finally {
|
|
72
|
+
configLoadPromise = null;
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
|
|
76
|
+
return configLoadPromise;
|
|
77
|
+
}
|
|
78
|
+
|
|
25
79
|
/**
|
|
26
|
-
* 根据
|
|
80
|
+
* 根据 bundlePath 构建 HTTP URL(开发环境 HTTP 模式)
|
|
27
81
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
82
|
+
* 优先从 multi-bundle.config.json 的 entry 字段获取模块路径
|
|
83
|
+
* 如果无法获取,则使用 bundlePath 进行简单推断
|
|
84
|
+
*
|
|
85
|
+
* @param bundleId 模块 ID
|
|
86
|
+
* @param bundlePath bundle 文件路径(从 manifest 中获取,开发环境可能不使用)
|
|
30
87
|
*/
|
|
31
|
-
function
|
|
32
|
-
const
|
|
88
|
+
async function buildHttpUrlFromBundlePath(bundleId: string, bundlePath: string): Promise<string> {
|
|
89
|
+
const config = getGlobalConfig();
|
|
90
|
+
const devServer = config?.devServer;
|
|
91
|
+
const host = devServer?.host || (Platform.OS === 'android' ? '10.0.2.2' : 'localhost');
|
|
92
|
+
const port = devServer?.port || 8081;
|
|
93
|
+
const protocol = devServer?.protocol || 'http';
|
|
33
94
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
// 优先从 multi-bundle.config.json 的 entry 获取模块路径
|
|
96
|
+
let sourcePath: string | null = null;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const multiBundleConfig = await getMultiBundleConfig();
|
|
100
|
+
if (multiBundleConfig?.modules) {
|
|
101
|
+
const moduleConfig = multiBundleConfig.modules.find((m: any) => m.id === bundleId);
|
|
102
|
+
if (moduleConfig?.entry) {
|
|
103
|
+
sourcePath = moduleConfig.entry;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.warn(
|
|
108
|
+
`[ModuleLoaderMock] Failed to get entry from multi-bundle.config.json for module ${bundleId}: ${error}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 如果无法从配置文件获取,使用 bundlePath 进行简单推断
|
|
113
|
+
if (!sourcePath) {
|
|
114
|
+
// 如果 bundlePath 已经是源码路径,直接使用
|
|
115
|
+
if (bundlePath.startsWith('src/') || bundlePath.startsWith('./src/')) {
|
|
116
|
+
sourcePath = bundlePath;
|
|
117
|
+
} else {
|
|
118
|
+
// 生产环境路径(如 bundles/home/index.bundle),提取模块名
|
|
119
|
+
const match = bundlePath.match(/(?:bundles\/|modules\/)(?:[^/]+\/)?([^/]+)\//);
|
|
120
|
+
const moduleName = match?.[1] || bundleId.charAt(0).toUpperCase() + bundleId.slice(1);
|
|
121
|
+
sourcePath = `src/modules/${moduleName}/index.bundle`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 确保路径以 .bundle 结尾
|
|
126
|
+
if (!sourcePath.endsWith('.bundle')) {
|
|
127
|
+
sourcePath = sourcePath.replace(/\.(ts|tsx|js|jsx)$/, '.bundle') || sourcePath + '/index.bundle';
|
|
128
|
+
}
|
|
38
129
|
|
|
39
130
|
// 构建 Metro 请求 URL
|
|
40
|
-
|
|
41
|
-
// 注意:使用 .bundle 后缀,Metro 会识别并构建对应的 .ts/.tsx 文件
|
|
42
|
-
return `http://${host}:8081/src/modules/${moduleName}/index.bundle` +
|
|
131
|
+
return `${protocol}://${host}:${port}/${sourcePath}` +
|
|
43
132
|
`?platform=${Platform.OS}` +
|
|
44
133
|
`&dev=true` +
|
|
45
134
|
`&minify=false` +
|
|
46
|
-
`&modulesOnly=true` +
|
|
47
|
-
`&runModule=true`;
|
|
135
|
+
`&modulesOnly=true` +
|
|
136
|
+
`&runModule=true`;
|
|
48
137
|
}
|
|
49
138
|
|
|
50
139
|
/**
|
|
@@ -74,8 +163,8 @@ async function loadBusinessBundle(bundleId: string, bundlePath: string): Promise
|
|
|
74
163
|
};
|
|
75
164
|
}
|
|
76
165
|
|
|
77
|
-
// 开发环境:根据
|
|
78
|
-
const httpUrl =
|
|
166
|
+
// 开发环境:根据 bundlePath 动态构建 HTTP URL(优先从 multi-bundle.config.json 获取 entry)
|
|
167
|
+
const httpUrl = await buildHttpUrlFromBundlePath(bundleId, bundlePath);
|
|
79
168
|
|
|
80
169
|
// 已经加载过的 bundle,不再重复执行
|
|
81
170
|
if (loadedBundles.has(httpUrl)) {
|
|
@@ -166,4 +255,3 @@ export const ModuleLoader = {
|
|
|
166
255
|
};
|
|
167
256
|
|
|
168
257
|
export type { LoadResult };
|
|
169
|
-
|
|
@@ -137,8 +137,6 @@ function registerModule(moduleId: string, exportsObj: any): void {
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
map[moduleId] = 1;
|
|
141
|
-
|
|
142
140
|
moduleExports[moduleId] = exportsObj;
|
|
143
141
|
moduleState[moduleId] = 'loaded';
|
|
144
142
|
|
|
@@ -222,7 +220,7 @@ async function loadModule(moduleId: string): Promise<void> {
|
|
|
222
220
|
loader = NativeModules.ModuleLoader;
|
|
223
221
|
} else {
|
|
224
222
|
throw new Error(
|
|
225
|
-
'ModuleLoader not available: Native
|
|
223
|
+
'ModuleLoader not available: Native ModuleLoader module not found in production'
|
|
226
224
|
);
|
|
227
225
|
}
|
|
228
226
|
}
|
package/src/multi-bundle/init.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface InitResult {
|
|
|
18
18
|
manifest?: BundleManifest;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// 初始化状态标记
|
|
22
|
+
let isInitialized = false;
|
|
23
|
+
let initPromise: Promise<InitResult> | null = null;
|
|
24
|
+
|
|
21
25
|
/**
|
|
22
26
|
* 从 manifestProvider 获取 manifest
|
|
23
27
|
*/
|
|
@@ -53,6 +57,7 @@ async function getManifestFromProvider(
|
|
|
53
57
|
* 初始化多 Bundle 系统
|
|
54
58
|
*
|
|
55
59
|
* @param config 配置对象
|
|
60
|
+
* @param forceReinit 是否强制重新初始化(默认 false,已初始化时直接返回成功结果)
|
|
56
61
|
* @returns 初始化结果
|
|
57
62
|
*
|
|
58
63
|
* @example
|
|
@@ -64,68 +69,106 @@ async function getManifestFromProvider(
|
|
|
64
69
|
* });
|
|
65
70
|
* ```
|
|
66
71
|
*/
|
|
67
|
-
export async function initMultiBundle(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
export async function initMultiBundle(
|
|
73
|
+
config: MultiBundleConfig = {},
|
|
74
|
+
forceReinit: boolean = false
|
|
75
|
+
): Promise<InitResult> {
|
|
76
|
+
// 如果已经初始化且不强制重新初始化,直接返回成功
|
|
77
|
+
if (isInitialized && !forceReinit) {
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
manifest: undefined, // 不返回 manifest,因为已经初始化过了
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 如果正在初始化,等待正在进行的初始化完成
|
|
85
|
+
if (initPromise && !forceReinit) {
|
|
86
|
+
return initPromise;
|
|
87
|
+
}
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
// 开始新的初始化
|
|
90
|
+
initPromise = (async () => {
|
|
85
91
|
try {
|
|
86
|
-
|
|
92
|
+
// 1. 合并配置
|
|
93
|
+
const mergedConfig = mergeConfig(config);
|
|
94
|
+
|
|
95
|
+
// 2. 设置全局配置(供 Metro 配置等使用)
|
|
96
|
+
setGlobalConfig(mergedConfig);
|
|
97
|
+
|
|
98
|
+
// 2.5 初始化 HMR Client (开发环境)
|
|
99
|
+
if (__DEV__ && mergedConfig.devServer) {
|
|
100
|
+
HMRClient.getInstance().setup(
|
|
101
|
+
mergedConfig.devServer.host,
|
|
102
|
+
mergedConfig.devServer.port
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. 获取 manifest
|
|
107
|
+
let manifest: BundleManifest;
|
|
108
|
+
try {
|
|
109
|
+
manifest = await getManifestFromProvider(mergedConfig.manifestProvider);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
112
|
+
console.error('[initMultiBundle] Failed to get manifest:', err);
|
|
113
|
+
config.onError?.(err);
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: err,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. 设置自定义 ModuleLoader(如果提供)
|
|
121
|
+
if (mergedConfig.moduleLoader) {
|
|
122
|
+
ModuleRegistry.setModuleLoader(mergedConfig.moduleLoader);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 5. 初始化 ModuleRegistry
|
|
126
|
+
ModuleRegistry.init({ manifest });
|
|
127
|
+
|
|
128
|
+
// 6. 预加载模块
|
|
129
|
+
if (mergedConfig.preloadModules && mergedConfig.preloadModules.length > 0) {
|
|
130
|
+
const preloadPromises = mergedConfig.preloadModules.map((moduleId) =>
|
|
131
|
+
preloadModule(moduleId).catch((error) => {
|
|
132
|
+
// 预加载失败不影响初始化,只记录日志
|
|
133
|
+
console.warn(`[initMultiBundle] Preload module ${moduleId} failed:`, error);
|
|
134
|
+
config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
await Promise.allSettled(preloadPromises);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
isInitialized = true;
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
manifest,
|
|
144
|
+
};
|
|
87
145
|
} catch (error) {
|
|
88
146
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
89
|
-
console.error('[initMultiBundle]
|
|
147
|
+
console.error('[initMultiBundle] Initialization failed:', err);
|
|
90
148
|
config.onError?.(err);
|
|
149
|
+
// 初始化失败时,允许下次重试
|
|
150
|
+
isInitialized = false;
|
|
151
|
+
initPromise = null;
|
|
91
152
|
return {
|
|
92
153
|
success: false,
|
|
93
154
|
error: err,
|
|
94
155
|
};
|
|
156
|
+
} finally {
|
|
157
|
+
// 如果强制重新初始化,清除 promise(允许下次调用)
|
|
158
|
+
if (forceReinit) {
|
|
159
|
+
initPromise = null;
|
|
160
|
+
}
|
|
95
161
|
}
|
|
162
|
+
})();
|
|
96
163
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
ModuleRegistry.setModuleLoader(mergedConfig.moduleLoader);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 5. 初始化 ModuleRegistry
|
|
103
|
-
ModuleRegistry.init({ manifest });
|
|
104
|
-
|
|
105
|
-
// 6. 预加载模块
|
|
106
|
-
if (mergedConfig.preloadModules && mergedConfig.preloadModules.length > 0) {
|
|
107
|
-
const preloadPromises = mergedConfig.preloadModules.map((moduleId) =>
|
|
108
|
-
preloadModule(moduleId).catch((error) => {
|
|
109
|
-
// 预加载失败不影响初始化,只记录日志
|
|
110
|
-
console.warn(`[initMultiBundle] Preload module ${moduleId} failed:`, error);
|
|
111
|
-
config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
112
|
-
})
|
|
113
|
-
);
|
|
114
|
-
await Promise.allSettled(preloadPromises);
|
|
115
|
-
}
|
|
164
|
+
return initPromise;
|
|
165
|
+
}
|
|
116
166
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
console.error('[initMultiBundle] Initialization failed:', err);
|
|
124
|
-
config.onError?.(err);
|
|
125
|
-
return {
|
|
126
|
-
success: false,
|
|
127
|
-
error: err,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
167
|
+
/**
|
|
168
|
+
* 重置初始化状态(用于测试或特殊场景)
|
|
169
|
+
*/
|
|
170
|
+
export function resetInitState(): void {
|
|
171
|
+
isInitialized = false;
|
|
172
|
+
initPromise = null;
|
|
130
173
|
}
|
|
131
174
|
|