2026-07-18 08:59:40 +08:00
|
|
|
const fs = require('node:fs')
|
|
|
|
|
const path = require('node:path')
|
|
|
|
|
|
|
|
|
|
const { withXuqmConfig } = require('@xuqm/rn-common/metro')
|
2026-07-29 10:44:56 +08:00
|
|
|
const { ensureGeneratedEntries } = require('../scripts/generated-entries.cjs')
|
2026-07-18 08:59:40 +08:00
|
|
|
|
|
|
|
|
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)
|
2026-07-20 19:31:43 +08:00
|
|
|
// processModuleFilter 会在 createModuleIdFactory 分配编号之后执行。必须冻结进入
|
|
|
|
|
// 当前 bundle 之前已经存在的共享模块;否则 common 新分配的模块刚写入缓存,随即
|
|
|
|
|
// 又会被当作 startup 已提供模块过滤,生成只含 require(entryId) 的无效空包。
|
|
|
|
|
const inheritedSharedModules = new Set(Object.keys(sharedCache))
|
2026-07-18 08:59:40 +08:00
|
|
|
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
|
2026-07-20 19:31:43 +08:00
|
|
|
return !inheritedSharedModules.has(module.path)
|
2026-07-18 08:59:40 +08:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 同一份 Metro 配置同时服务日常开发和插件构建。CLI 构建插件时注入模块上下文;
|
|
|
|
|
* 普通 `pnpm android/start` 没有这些变量,因此保持标准 React Native 行为。
|
|
|
|
|
*/
|
2026-07-29 10:44:56 +08:00
|
|
|
function withXuqmModuleConfig(metroConfig, options = {}) {
|
|
|
|
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd())
|
|
|
|
|
ensureGeneratedEntries(projectRoot)
|
2026-07-18 08:59:40 +08:00
|
|
|
const configured = withXuqmConfig(metroConfig)
|
|
|
|
|
const moduleSerializer = createModuleSerializer()
|
|
|
|
|
const configuredFilter = configured.serializer?.processModuleFilter
|
2026-07-27 16:32:38 +08:00
|
|
|
const configuredEnhanceMiddleware = configured.server?.enhanceMiddleware
|
|
|
|
|
const result = {
|
2026-07-18 08:59:40 +08:00
|
|
|
...configured,
|
2026-07-27 16:32:38 +08:00
|
|
|
server: {
|
|
|
|
|
...configured.server,
|
|
|
|
|
enhanceMiddleware(middleware, metroServer) {
|
|
|
|
|
const nextMiddleware = configuredEnhanceMiddleware
|
|
|
|
|
? configuredEnhanceMiddleware(middleware, metroServer)
|
|
|
|
|
: middleware
|
|
|
|
|
return (request, response, next) => {
|
|
|
|
|
if (request.url === '/xuqm/metro-info') {
|
|
|
|
|
response.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
|
|
|
response.end(
|
|
|
|
|
JSON.stringify({
|
2026-07-29 10:44:56 +08:00
|
|
|
projectRoot,
|
2026-07-27 16:32:38 +08:00
|
|
|
protocolVersion: 1,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
nextMiddleware(request, response, next)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
if (!moduleSerializer) return result
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
2026-07-18 08:59:40 +08:00
|
|
|
serializer: {
|
|
|
|
|
...configured.serializer,
|
|
|
|
|
...moduleSerializer,
|
|
|
|
|
processModuleFilter(module) {
|
|
|
|
|
return (
|
|
|
|
|
(configuredFilter ? configuredFilter(module) : true) &&
|
|
|
|
|
moduleSerializer.processModuleFilter(module)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
createModuleSerializer,
|
|
|
|
|
withXuqmModuleConfig,
|
|
|
|
|
}
|