XuqmGroup-RNSDK/packages/update/metro/index.js

132 行
4.4 KiB
JavaScript

const fs = require('node:fs')
const path = require('node:path')
const { withXuqmConfig } = require('@xuqm/rn-common/metro')
const { ensureGeneratedEntries } = require('../scripts/generated-entries.cjs')
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, options = {}) {
const projectRoot = path.resolve(options.projectRoot ?? process.cwd())
ensureGeneratedEntries(projectRoot)
const configured = withXuqmConfig(metroConfig)
const moduleSerializer = createModuleSerializer()
const configuredFilter = configured.serializer?.processModuleFilter
const configuredEnhanceMiddleware = configured.server?.enhanceMiddleware
const result = {
...configured,
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({
projectRoot,
protocolVersion: 1,
}),
)
return
}
nextMiddleware(request, response, next)
}
},
},
}
if (!moduleSerializer) return result
return {
...result,
serializer: {
...configured.serializer,
...moduleSerializer,
processModuleFilter(module) {
return (
(configuredFilter ? configuredFilter(module) : true) &&
moduleSerializer.processModuleFilter(module)
)
},
},
}
}
module.exports = {
createModuleSerializer,
withXuqmModuleConfig,
}