100 行
3.0 KiB
JavaScript
100 行
3.0 KiB
JavaScript
|
|
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)
|
||
|
|
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 !hasModule(sharedCache, 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,
|
||
|
|
}
|