mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2026-08-01 20:15:49 +00:00
374 lines
12 KiB
JavaScript
374 lines
12 KiB
JavaScript
/**
|
||
* 小程序 DIY bridge 合并:原生 router 补丁 + 每插件独立 bridge(无需 uni 编译)
|
||
*/
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
const { patchDiyGroupInlineComponents } = require('./mp-diy-group-inline.cjs')
|
||
|
||
const ROUTER_REL = 'components/diy-dynamic-bridge'
|
||
const ROUTER_BASENAME = 'diy-dynamic-bridge'
|
||
const MERGE_MARKER = '<!-- MP_MERGE_ADDON_BRIDGES -->'
|
||
const ROUTER_TEMPLATE_DIR = fs.existsSync(path.join(__dirname, 'mp-templates', 'diy-dynamic-bridge'))
|
||
? path.join(__dirname, 'mp-templates', 'diy-dynamic-bridge')
|
||
: path.join(__dirname, '..', 'mp-templates', 'diy-dynamic-bridge')
|
||
|
||
function readJson(filePath) {
|
||
return JSON.parse(fs.readFileSync(filePath, 'utf-8'))
|
||
}
|
||
|
||
function writeJson(filePath, data) {
|
||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8')
|
||
}
|
||
|
||
function rmDir(dir) {
|
||
if (!fs.existsSync(dir)) return
|
||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
||
}
|
||
|
||
function copyDir(src, dest) {
|
||
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
||
fs.cpSync(src, dest, { recursive: true, force: true })
|
||
}
|
||
|
||
function getAddonBridgeRel(key) {
|
||
return `components/diy-dynamic-bridge-${key}`
|
||
}
|
||
|
||
function getAddonBridgeTag(key) {
|
||
return `diy-dynamic-bridge-${key}`
|
||
}
|
||
|
||
function getRouterDir(frameworkDir) {
|
||
return path.join(frameworkDir, ROUTER_REL)
|
||
}
|
||
|
||
function getRouterComponentPath(routerDir, ext) {
|
||
return path.join(routerDir, `${ROUTER_BASENAME}.${ext}`)
|
||
}
|
||
|
||
function getRegistryPath(frameworkDir) {
|
||
return path.join(getRouterDir(frameworkDir), 'diy-registry.merged.js')
|
||
}
|
||
|
||
function writeMergedRegistry(frameworkDir, data) {
|
||
const filePath = getRegistryPath(frameworkDir)
|
||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
||
fs.writeFileSync(filePath, `module.exports = ${JSON.stringify(data || {}, null, 2)}\n`, 'utf-8')
|
||
}
|
||
|
||
function readMergedRegistry(frameworkDir) {
|
||
const filePath = getRegistryPath(frameworkDir)
|
||
if (!fs.existsSync(filePath)) {
|
||
const legacyJson = path.join(getRouterDir(frameworkDir), 'diy-registry.merged.json')
|
||
if (fs.existsSync(legacyJson)) {
|
||
return readJson(legacyJson)
|
||
}
|
||
return {}
|
||
}
|
||
delete require.cache[path.resolve(filePath)]
|
||
return require(filePath)
|
||
}
|
||
|
||
function buildAppDiyRegistryMap(scanAllDiyComponents) {
|
||
const { app } = scanAllDiyComponents()
|
||
const map = {}
|
||
for (const entry of app) {
|
||
map[entry.vueComponentName] = {
|
||
scope: 'app',
|
||
folder: entry.folder,
|
||
scrollBool: entry.scrollBool,
|
||
formRef: entry.formRef
|
||
}
|
||
}
|
||
return map
|
||
}
|
||
|
||
function buildAddonDiyRegistryMap(scanAllDiyComponents, addonKey) {
|
||
const { addon } = scanAllDiyComponents({ addonKeys: [addonKey] })
|
||
const map = {}
|
||
for (const entry of addon) {
|
||
map[entry.vueComponentName] = {
|
||
scope: 'addon',
|
||
addonKey: entry.addonKey,
|
||
folder: entry.folder,
|
||
scrollBool: entry.scrollBool,
|
||
formRef: entry.formRef
|
||
}
|
||
}
|
||
return map
|
||
}
|
||
|
||
function installNativeDiyBridgeRouter(frameworkDir, appRegistryMap, templateDir = ROUTER_TEMPLATE_DIR) {
|
||
const routerDir = getRouterDir(frameworkDir)
|
||
rmDir(routerDir)
|
||
fs.mkdirSync(routerDir, { recursive: true })
|
||
|
||
const templateMap = {
|
||
js: 'index.js',
|
||
wxml: 'index.wxml',
|
||
json: 'index.json'
|
||
}
|
||
for (const [ext, tplName] of Object.entries(templateMap)) {
|
||
fs.copyFileSync(
|
||
path.join(templateDir, tplName),
|
||
getRouterComponentPath(routerDir, ext)
|
||
)
|
||
}
|
||
writeMergedRegistry(frameworkDir, appRegistryMap || {})
|
||
return routerDir
|
||
}
|
||
|
||
function mergeRegistryEntries(frameworkDir, entries) {
|
||
const current = readMergedRegistry(frameworkDir)
|
||
writeMergedRegistry(frameworkDir, { ...current, ...entries })
|
||
}
|
||
|
||
function listRouterAddonKeys(frameworkDir) {
|
||
const wxmlPath = getRouterComponentPath(getRouterDir(frameworkDir), 'wxml')
|
||
if (!fs.existsSync(wxmlPath)) return []
|
||
const content = fs.readFileSync(wxmlPath, 'utf-8')
|
||
const keys = []
|
||
const re = /id="bridge-([a-zA-Z0-9_-]+)"/g
|
||
let m
|
||
while ((m = re.exec(content))) {
|
||
if (m[1] !== 'app') keys.push(m[1])
|
||
}
|
||
return [...new Set(keys)].sort()
|
||
}
|
||
|
||
function buildAddonBridgeWxmlBlock(key) {
|
||
const tag = getAddonBridgeTag(key)
|
||
return ` <${tag}
|
||
wx:elif="{{routeBridge === '${key}'}}"
|
||
id="bridge-${key}"
|
||
componentName="{{componentName}}"
|
||
component="{{component}}"
|
||
global="{{global}}"
|
||
index="{{index}}"
|
||
scrollBool="{{scrollBool}}"
|
||
bindupdateComponentIsShow="forwardShow"
|
||
bindloadingFn="forwardLoading"
|
||
/>
|
||
${MERGE_MARKER}`
|
||
}
|
||
|
||
function appendRouterAddonBlock(frameworkDir, key) {
|
||
const routerDir = getRouterDir(frameworkDir)
|
||
const wxmlPath = getRouterComponentPath(routerDir, 'wxml')
|
||
const jsonPath = getRouterComponentPath(routerDir, 'json')
|
||
const tag = getAddonBridgeTag(key)
|
||
|
||
if (listRouterAddonKeys(frameworkDir).includes(key)) {
|
||
return false
|
||
}
|
||
|
||
let wxml = fs.readFileSync(wxmlPath, 'utf-8')
|
||
if (!wxml.includes(MERGE_MARKER)) {
|
||
wxml = wxml.trimEnd() + '\n ' + MERGE_MARKER + '\n'
|
||
}
|
||
wxml = wxml.replace(MERGE_MARKER, buildAddonBridgeWxmlBlock(key))
|
||
fs.writeFileSync(wxmlPath, wxml, 'utf-8')
|
||
|
||
const json = readJson(jsonPath)
|
||
json.usingComponents = json.usingComponents || {}
|
||
json.componentPlaceholder = json.componentPlaceholder || {}
|
||
json.usingComponents[tag] = `../diy-dynamic-bridge-${key}/diy-dynamic-bridge-${key}`
|
||
json.componentPlaceholder[tag] = 'view'
|
||
writeJson(jsonPath, json)
|
||
return true
|
||
}
|
||
|
||
function copyAddonBridgeFromPackage(frameworkDir, pkgDir, key) {
|
||
const rel = getAddonBridgeRel(key)
|
||
const src = path.join(pkgDir, rel)
|
||
if (!fs.existsSync(src)) {
|
||
return 0
|
||
}
|
||
const dest = path.join(frameworkDir, rel)
|
||
rmDir(dest)
|
||
copyDir(src, dest)
|
||
const bridgeDir = dest
|
||
const tag = getAddonBridgeTag(key)
|
||
const jsonPath = fs.existsSync(path.join(bridgeDir, 'index.json'))
|
||
? path.join(bridgeDir, 'index.json')
|
||
: path.join(bridgeDir, `${tag}.json`)
|
||
if (!fs.existsSync(jsonPath)) return 0
|
||
try {
|
||
return Object.keys(readJson(jsonPath).usingComponents || {}).length
|
||
} catch {
|
||
return 0
|
||
}
|
||
}
|
||
|
||
function resolveMpComponentDir(bridgeJsonDir, relPath) {
|
||
return path.normalize(path.join(bridgeJsonDir, relPath))
|
||
}
|
||
|
||
function validateAddonBridgePaths(frameworkDir, key) {
|
||
const rel = getAddonBridgeRel(key)
|
||
const bridgeDir = path.join(frameworkDir, rel)
|
||
const tag = getAddonBridgeTag(key)
|
||
const jsonPath = fs.existsSync(path.join(bridgeDir, 'index.json'))
|
||
? path.join(bridgeDir, 'index.json')
|
||
: path.join(bridgeDir, `${tag}.json`)
|
||
if (!fs.existsSync(jsonPath)) {
|
||
return [{ name: tag, rel: rel }]
|
||
}
|
||
const using = readJson(jsonPath).usingComponents || {}
|
||
const missing = []
|
||
for (const [name, compRel] of Object.entries(using)) {
|
||
const compDir = resolveMpComponentDir(bridgeDir, compRel)
|
||
const hasJson = fs.existsSync(`${compDir}.json`)
|
||
const hasJs = fs.existsSync(`${compDir}.js`)
|
||
if (!hasJson && !hasJs) {
|
||
missing.push({ name, rel: compRel.replace(/\\/g, '/'), bridge: key })
|
||
}
|
||
}
|
||
return missing
|
||
}
|
||
|
||
function applyRouterBridgePlaceholders(frameworkDir) {
|
||
const jsonPath = getRouterComponentPath(getRouterDir(frameworkDir), 'json')
|
||
if (!fs.existsSync(jsonPath)) return
|
||
const data = readJson(jsonPath)
|
||
data.componentPlaceholder = data.componentPlaceholder || {}
|
||
for (const key of Object.keys(data.usingComponents || {})) {
|
||
if (!data.componentPlaceholder[key]) {
|
||
data.componentPlaceholder[key] = 'view'
|
||
}
|
||
}
|
||
writeJson(jsonPath, data)
|
||
}
|
||
|
||
function applyAddonBridgePlaceholders(frameworkDir, key) {
|
||
const rel = getAddonBridgeRel(key)
|
||
const tag = getAddonBridgeTag(key)
|
||
const bridgeDir = path.join(frameworkDir, rel)
|
||
const jsonPath = fs.existsSync(path.join(bridgeDir, 'index.json'))
|
||
? path.join(bridgeDir, 'index.json')
|
||
: path.join(bridgeDir, `${tag}.json`)
|
||
if (!fs.existsSync(jsonPath)) return
|
||
const data = readJson(jsonPath)
|
||
data.componentPlaceholder = data.componentPlaceholder || {}
|
||
for (const compKey of Object.keys(data.usingComponents || {})) {
|
||
if (!data.componentPlaceholder[compKey]) {
|
||
data.componentPlaceholder[compKey] = 'view'
|
||
}
|
||
}
|
||
writeJson(jsonPath, data)
|
||
}
|
||
|
||
function syncMergedDiyBridgeRouter(frameworkDir, pkgRoot, mergedAddonKeys, options = {}) {
|
||
if (!mergedAddonKeys.length) {
|
||
return { routerAddons: [], componentCount: 0, diyGroupInlineCount: 0 }
|
||
}
|
||
|
||
let appRegistry = {}
|
||
const existing = readMergedRegistry(frameworkDir)
|
||
for (const [name, meta] of Object.entries(existing)) {
|
||
if (!meta.addonKey) appRegistry[name] = meta
|
||
}
|
||
if (!Object.keys(appRegistry).length && options.scanAllDiyComponents) {
|
||
appRegistry = buildAppDiyRegistryMap(options.scanAllDiyComponents)
|
||
}
|
||
installNativeDiyBridgeRouter(frameworkDir, appRegistry)
|
||
|
||
let totalComponents = 0
|
||
for (const key of mergedAddonKeys) {
|
||
const pkgDir = path.join(pkgRoot, key)
|
||
let registry = null
|
||
let count = 0
|
||
|
||
if (fs.existsSync(path.join(pkgDir, 'manifest.json'))) {
|
||
const manifest = readJson(path.join(pkgDir, 'manifest.json'))
|
||
registry =
|
||
manifest.diyBridge?.registry ||
|
||
manifest.diyRegistry ||
|
||
(options.scanAllDiyComponents
|
||
? buildAddonDiyRegistryMap(options.scanAllDiyComponents, key)
|
||
: null)
|
||
count = copyAddonBridgeFromPackage(frameworkDir, pkgDir, key)
|
||
}
|
||
|
||
if (!count) {
|
||
const existingBridge = path.join(frameworkDir, getAddonBridgeRel(key))
|
||
if (fs.existsSync(existingBridge)) {
|
||
count = readAddonBridgeCount(frameworkDir, key)
|
||
}
|
||
}
|
||
|
||
if (!count) {
|
||
// 无 DIY 组件的插件不需要 bridge,跳过
|
||
if (registry && !Object.keys(registry).length) {
|
||
console.log(`[mp-diy-bridge-merge] skip "${key}" (no diy components)`)
|
||
continue
|
||
}
|
||
throw new Error(
|
||
`[mp-diy-bridge-merge] missing bridge for "${key}" (package or framework ${getAddonBridgeRel(key)})`
|
||
)
|
||
}
|
||
|
||
if (registry && Object.keys(registry).length) {
|
||
mergeRegistryEntries(frameworkDir, registry)
|
||
}
|
||
|
||
appendRouterAddonBlock(frameworkDir, key)
|
||
applyAddonBridgePlaceholders(frameworkDir, key)
|
||
totalComponents += count
|
||
|
||
const missing = validateAddonBridgePaths(frameworkDir, key)
|
||
if (missing.length) {
|
||
const sample = missing
|
||
.slice(0, 10)
|
||
.map((m) => `${m.name} -> ${m.rel}`)
|
||
.join(', ')
|
||
throw new Error(`[mp-diy-bridge-merge] bridge "${key}" missing components: ${sample}`)
|
||
}
|
||
}
|
||
|
||
applyRouterBridgePlaceholders(frameworkDir)
|
||
|
||
const mergedRegistry = readMergedRegistry(frameworkDir)
|
||
const inlineResult = patchDiyGroupInlineComponents(frameworkDir, mergedRegistry)
|
||
|
||
return {
|
||
routerAddons: listRouterAddonKeys(frameworkDir),
|
||
componentCount: totalComponents,
|
||
diyGroupInlineCount: inlineResult.count
|
||
}
|
||
}
|
||
|
||
function readAddonBridgeCount(frameworkDir, key) {
|
||
const bridgeDir = path.join(frameworkDir, getAddonBridgeRel(key))
|
||
const tag = getAddonBridgeTag(key)
|
||
const jsonPath = fs.existsSync(path.join(bridgeDir, `${tag}.json`))
|
||
? path.join(bridgeDir, `${tag}.json`)
|
||
: path.join(bridgeDir, 'index.json')
|
||
if (!fs.existsSync(jsonPath)) return 0
|
||
try {
|
||
return Object.keys(readJson(jsonPath).usingComponents || {}).length
|
||
} catch {
|
||
return 0
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
ROUTER_REL,
|
||
MERGE_MARKER,
|
||
getAddonBridgeRel,
|
||
getAddonBridgeTag,
|
||
buildAppDiyRegistryMap,
|
||
buildAddonDiyRegistryMap,
|
||
installNativeDiyBridgeRouter,
|
||
mergeRegistryEntries,
|
||
appendRouterAddonBlock,
|
||
copyAddonBridgeFromPackage,
|
||
validateAddonBridgePaths,
|
||
syncMergedDiyBridgeRouter,
|
||
listRouterAddonKeys,
|
||
applyRouterBridgePlaceholders,
|
||
applyAddonBridgePlaceholders,
|
||
patchDiyGroupInlineComponents
|
||
}
|