const fs = require('node:fs') const path = require('node:path') const { withXuqmConfig } = require('@xuqm/rn-common/metro') const MODULE_RANGE_SIZE = 10_000_000 function readCache(file) { if (!fs.existsSync(file)) return {} try { return JSON.parse(fs.readFileSync(file, 'utf8')) } catch { return {} } } function writeCache(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }) fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`) } function hasModule(cache, modulePath) { return Object.prototype.hasOwnProperty.call(cache, modulePath) } function isRuntimePrelude(modulePath) { return modulePath.includes('__prelude__') || modulePath.includes('polyfills') } function createModuleSerializer(environment = process.env) { const moduleId = environment.XUQM_MODULE_ID const moduleType = environment.XUQM_MODULE_TYPE if (!moduleId || !moduleType) return null const cacheFile = path.resolve( environment.XUQM_MODULE_CACHE_FILE ?? '.xuqm-cache/common-module-ids.json', ) if (environment.XUQM_RESET_MODULE_CACHE === 'true') writeCache(cacheFile, {}) const sharedCache = readCache(cacheFile) // processModuleFilter 会在 createModuleIdFactory 分配编号之后执行。必须冻结进入 // 当前 bundle 之前已经存在的共享模块;否则 common 新分配的模块刚写入缓存,随即 // 又会被当作 startup 已提供模块过滤,生成只含 require(entryId) 的无效空包。 const inheritedSharedModules = new Set(Object.keys(sharedCache)) const writesSharedCache = moduleType === 'startup' || moduleType === 'common' const moduleIndex = Number.parseInt(environment.XUQM_MODULE_INDEX ?? '0', 10) let nextId = writesSharedCache ? Object.values(sharedCache).reduce((maximum, value) => Math.max(maximum, Number(value)), -1) + 1 : (Number.isFinite(moduleIndex) && moduleIndex >= 0 ? moduleIndex + 1 : 1) * MODULE_RANGE_SIZE const localIds = new Map() return { createModuleIdFactory() { return modulePath => { if (hasModule(sharedCache, modulePath)) return sharedCache[modulePath] const existing = localIds.get(modulePath) if (existing !== undefined) return existing const id = nextId++ localIds.set(modulePath, id) if (writesSharedCache) { sharedCache[modulePath] = id writeCache(cacheFile, sharedCache) } return id } }, processModuleFilter(module) { if (moduleType === 'startup') return true if (isRuntimePrelude(module.path)) return false return !inheritedSharedModules.has(module.path) }, } } /** * 同一份 Metro 配置同时服务日常开发和插件构建。CLI 构建插件时注入模块上下文; * 普通 `pnpm android/start` 没有这些变量,因此保持标准 React Native 行为。 */ function withXuqmModuleConfig(metroConfig) { const configured = withXuqmConfig(metroConfig) const moduleSerializer = createModuleSerializer() if (!moduleSerializer) return configured const configuredFilter = configured.serializer?.processModuleFilter return { ...configured, serializer: { ...configured.serializer, ...moduleSerializer, processModuleFilter(module) { return ( (configuredFilter ? configuredFilter(module) : true) && moduleSerializer.processModuleFilter(module) ) }, }, } } module.exports = { createModuleSerializer, withXuqmModuleConfig, }