@bm-fe/react-native-multi-bundle 1.0.0-beta.0 → 1.0.0-beta.1

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 CHANGED
@@ -326,6 +326,53 @@ export const createHomeScreen = createModuleRouteLoader('home', 'Home');
326
326
  <Stack.Screen name="Home" getComponent={createHomeScreen} />
327
327
  ```
328
328
 
329
+ **路由 Props 传递说明:**
330
+
331
+ `createModuleRouteLoader` 会自动将 React Navigation 的路由 props 传递给模块组件,包括:
332
+
333
+ - `navigation`:导航对象,用于页面跳转
334
+ - `route`:路由对象,包含路由参数和配置
335
+
336
+ 模块组件可以直接使用这些 props:
337
+
338
+ ```typescript
339
+ // 在模块组件中(如 HomeScreen.tsx)
340
+ import React from 'react';
341
+ import { View, Text } from 'react-native';
342
+ import { NativeStackScreenProps } from '@react-navigation/native-stack';
343
+
344
+ type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
345
+
346
+ const HomeScreen = ({ navigation, route }: Props) => {
347
+ // 可以使用 navigation 进行页面跳转
348
+ const handleNavigate = () => {
349
+ navigation.navigate('Details', { id: '123' });
350
+ };
351
+
352
+ // 可以使用 route.params 获取路由参数
353
+ const params = route.params;
354
+
355
+ return (
356
+ <View>
357
+ <Text>Home Screen</Text>
358
+ {/* ... */}
359
+ </View>
360
+ );
361
+ };
362
+
363
+ export default HomeScreen;
364
+ ```
365
+
366
+ 如果需要传递额外的 props,可以通过 React Navigation 的 `initialParams` 或 `getInitialProps`:
367
+
368
+ ```typescript
369
+ <Stack.Screen
370
+ name="Home"
371
+ getComponent={createHomeScreen}
372
+ initialParams={{ customProp: 'value' }}
373
+ />
374
+ ```
375
+
329
376
  ### 方式二:使用路由注册系统
330
377
 
331
378
  ```typescript
@@ -364,6 +411,18 @@ const HomeScreen = getModuleRoute('home', 'Home');
364
411
  - 检查 `devServer` 配置是否正确
365
412
  - 验证 manifest URL 是否可访问
366
413
 
414
+ ### 5. 配置文件找不到
415
+
416
+ 如果执行打包命令时提示 `Config file not found`:
417
+
418
+ - **确认配置文件位置**:`multi-bundle.config.json` 应该在项目根目录(与 `package.json` 同级)
419
+ - **确认执行目录**:打包命令应该在项目根目录执行,或者通过 npm scripts 执行
420
+ - **手动指定项目根目录**:如果仍有问题,可以通过环境变量指定:
421
+ ```bash
422
+ PROJECT_ROOT=/path/to/your/project node node_modules/@bm-fe/react-native-multi-bundle/scripts/build-multi-bundle.js ios
423
+ ```
424
+ - **检查文件路径**:确认错误信息中的路径是否正确指向你的项目根目录
425
+
367
426
  ## 更多信息
368
427
 
369
428
  - [API 参考](./src/multi-bundle/README.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bm-fe/react-native-multi-bundle",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "React Native 多 Bundle 系统 - 支持模块按需加载和独立更新",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -34,7 +34,7 @@
34
34
  "version:patch": "standard-version --release-as patch",
35
35
  "version:minor": "standard-version --release-as minor",
36
36
  "version:major": "standard-version --release-as major",
37
- "version:beta": "npm version prerelease --preid=beta --no-git-tag-version",
37
+ "version:beta": "npm version prerelease --preid=beta --no-git-tag-version --no-scripts",
38
38
  "publish:beta": "npm publish --tag beta",
39
39
  "release:beta": "npm run version:beta && npm run publish:beta",
40
40
  "release": "npm run version && npm publish"
@@ -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
- // 如果作为 npm 包使用,PROJECT_ROOT 应该是调用脚本的项目根目录
25
- // 如果直接运行,PROJECT_ROOT 是脚本所在目录的父目录
26
- const PROJECT_ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
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
 
@@ -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
- * 根据 bundleId 构建 HTTP URL(开发环境)
80
+ * 根据 bundlePath 构建 HTTP URL(开发环境 HTTP 模式)
27
81
  *
28
- * 在开发环境中,我们通过 Metro bundler HTTP 服务器加载 bundle
29
- * 根据 bundleId 直接构建对应的 HTTP URL
82
+ * 优先从 multi-bundle.config.jsonentry 字段获取模块路径
83
+ * 如果无法获取,则使用 bundlePath 进行简单推断
84
+ *
85
+ * @param bundleId 模块 ID
86
+ * @param bundlePath bundle 文件路径(从 manifest 中获取,开发环境可能不使用)
30
87
  */
31
- function buildHttpUrlFromBundleId(bundleId: string): string {
32
- const host = Platform.OS === 'android' ? '10.0.2.2' : 'localhost';
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
- // 简单的模块 ID 到目录名的映射
35
- // 假设模块 ID 为小写,目录名为首字母大写
36
- // 例如: home -> Home
37
- const moduleName = bundleId.charAt(0).toUpperCase() + bundleId.slice(1);
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
- // 请求源码入口文件,Metro 会自动打包
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` + // ✅ 只生成 __d(...),不带 runtime
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
- // 开发环境:根据 bundleId 构建 HTTP URL
78
- const httpUrl = buildHttpUrlFromBundleId(bundleId);
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
 
@@ -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(config: MultiBundleConfig = {}): Promise<InitResult> {
68
- try {
69
- // 1. 合并配置
70
- const mergedConfig = mergeConfig(config);
71
-
72
- // 2. 设置全局配置(供 Metro 配置等使用)
73
- setGlobalConfig(mergedConfig);
74
-
75
- // 2.5 初始化 HMR Client (开发环境)
76
- if (__DEV__ && mergedConfig.devServer) {
77
- HMRClient.getInstance().setup(
78
- mergedConfig.devServer.host,
79
- mergedConfig.devServer.port
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
- // 3. 获取 manifest
84
- let manifest: BundleManifest;
89
+ // 开始新的初始化
90
+ initPromise = (async () => {
85
91
  try {
86
- manifest = await getManifestFromProvider(mergedConfig.manifestProvider);
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] Failed to get manifest:', err);
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
- // 4. 设置自定义 ModuleLoader(如果提供)
98
- if (mergedConfig.moduleLoader) {
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
- return {
118
- success: true,
119
- manifest,
120
- };
121
- } catch (error) {
122
- const err = error instanceof Error ? error : new Error(String(error));
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
 
@@ -37,9 +37,161 @@ const sharedDependencies = multiBundleConfig.sharedDependencies || [
37
37
  'src/navigation/**'
38
38
  ];
39
39
 
40
- // ... 其余配置代码与主 metro.config.js 相同
41
- // 请参考 @bm-fe/react-native-multi-bundle 的完整实现
40
+ // ⚠️ 注意:这是"请求级别"的标记,由 middleware 动态设置
41
+ let isDevModuleRequest = false;
42
42
 
43
- module.exports = mergeConfig(defaultConfig, {
44
- // 你的自定义配置
45
- });
43
+ /**
44
+ * 加载 multi-bundle.config.json 配置
45
+ */
46
+ function loadMultiBundleConfig() {
47
+ try {
48
+ if (fs.existsSync(MULTI_BUNDLE_CONFIG_FILE)) {
49
+ const configContent = fs.readFileSync(MULTI_BUNDLE_CONFIG_FILE, 'utf-8');
50
+ return JSON.parse(configContent);
51
+ }
52
+ } catch (error) {
53
+ console.warn('[Metro] Failed to load multi-bundle.config.json:', error);
54
+ }
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * 获取主 bundle 文件名(根据平台)
60
+ */
61
+ function getMainBundleFileName(platform) {
62
+ if (platform === 'ios') {
63
+ return 'main.jsbundle';
64
+ } else if (platform === 'android') {
65
+ return 'index.android.bundle';
66
+ }
67
+ throw new Error(`Unsupported platform: ${platform}`);
68
+ }
69
+
70
+ /**
71
+ * 获取模块 bundle 文件名(根据平台和模块 ID)
72
+ */
73
+ function getModuleBundleFileName(moduleId, platform) {
74
+ if (platform === 'ios') {
75
+ return `${moduleId}.jsbundle`;
76
+ } else if (platform === 'android') {
77
+ return `${moduleId}.bundle`;
78
+ }
79
+ throw new Error(`Unsupported platform: ${platform}`);
80
+ }
81
+
82
+ /**
83
+ * 从配置生成 manifest
84
+ */
85
+ function generateManifestFromConfig(config, platform) {
86
+ if (!config || !config.modules) return null;
87
+
88
+ const mainFileName = getMainBundleFileName(platform);
89
+ const manifest = {
90
+ formatVersion: config.formatVersion || 1,
91
+ main: {
92
+ file: mainFileName,
93
+ version: config.main?.version || '1.0.0',
94
+ },
95
+ modules: (config.modules || []).map((module) => ({
96
+ id: module.id,
97
+ file: `modules/${getModuleBundleFileName(module.id, platform)}`,
98
+ version: module.version || '1.0.0',
99
+ dependencies: module.dependencies || [],
100
+ lazy: module.lazy !== false,
101
+ })),
102
+ };
103
+
104
+ return manifest;
105
+ }
106
+
107
+ const config = {
108
+ projectRoot: path.resolve(__dirname),
109
+
110
+ watcher: {
111
+ healthCheck: { enabled: true },
112
+ },
113
+
114
+ server: {
115
+ enhanceMiddleware: (middleware) => {
116
+ return (req, res, next) => {
117
+ // ⭐ 处理 bundle-manifest.json 请求
118
+ // 基于 multi-bundle.config.json 动态生成 manifest
119
+ if (req.url && req.url.startsWith('/bundle-manifest.json')) {
120
+ // 从查询参数中提取 platform,默认为 android
121
+ const urlParts = req.url.split('?');
122
+ let platform = 'android';
123
+ if (urlParts.length > 1) {
124
+ const params = new URLSearchParams(urlParts[1]);
125
+ platform = params.get('platform') || 'android';
126
+ }
127
+
128
+ try {
129
+ // 优先尝试读取已构建的 manifest 文件
130
+ const manifestPath = path.join(projectRoot, 'build', 'bundles', platform, 'bundle-manifest.json');
131
+ if (fs.existsSync(manifestPath)) {
132
+ const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
133
+ res.setHeader('Content-Type', 'application/json');
134
+ res.statusCode = 200;
135
+ res.end(manifestContent);
136
+ return;
137
+ }
138
+
139
+ // 如果 manifest 文件不存在,基于 multi-bundle.config.json 动态生成
140
+ const config = loadMultiBundleConfig();
141
+ if (config) {
142
+ const manifest = generateManifestFromConfig(config, platform);
143
+ if (manifest) {
144
+ res.setHeader('Content-Type', 'application/json');
145
+ res.statusCode = 200;
146
+ res.end(JSON.stringify(manifest, null, 2));
147
+ return;
148
+ }
149
+ }
150
+
151
+ // 如果配置不存在或生成失败,返回错误
152
+ res.statusCode = 404;
153
+ res.setHeader('Content-Type', 'application/json');
154
+ res.end(JSON.stringify({
155
+ error: 'Manifest not found',
156
+ message: 'bundle-manifest.json not found and multi-bundle.config.json is missing or invalid'
157
+ }));
158
+ return;
159
+ } catch (error) {
160
+ res.statusCode = 500;
161
+ res.setHeader('Content-Type', 'application/json');
162
+ res.end(JSON.stringify({ error: 'Failed to generate manifest', message: error.message }));
163
+ return;
164
+ }
165
+ }
166
+
167
+ // 使用配置化的模块路径规则判断是否是模块 bundle 请求
168
+ if (req.url) {
169
+ // 去掉查询参数和 URL 前缀,只保留路径部分
170
+ let pathToCheck = req.url.split('?')[0];
171
+ // 去掉前导斜杠(如果存在)
172
+ if (pathToCheck.startsWith('/')) {
173
+ pathToCheck = pathToCheck.substring(1);
174
+ }
175
+
176
+ // 检查路径是否匹配模块路径规则
177
+ if (isModulePathHelper(pathToCheck, modulePaths)) {
178
+ isDevModuleRequest = true;
179
+ } else {
180
+ isDevModuleRequest = false;
181
+ }
182
+ } else {
183
+ isDevModuleRequest = false;
184
+ }
185
+ return middleware(req, res, next);
186
+ };
187
+ },
188
+ },
189
+
190
+ // ... 其余配置代码(serializer、transformer 等)请参考完整实现
191
+ // 完整的 Metro 配置包含:
192
+ // - serializer: 自定义模块 ID 工厂、processModuleFilter、customSerializer
193
+ // - transformer: getTransformOptions
194
+ // 这些配置较为复杂,建议参考 @bm-fe/react-native-multi-bundle 的完整实现
195
+ };
196
+
197
+ module.exports = mergeConfig(defaultConfig, config);