niucloud/uni-app/scripts/scan-mp-requires.cjs
wangchen14709853322 ef3f9b2c15 v2.0.0
2026-07-02 17:47:17 +08:00

109 lines
3.4 KiB
JavaScript

/**
* 扫描小程序分包 JS 中的 require / require.async 依赖
*/
const fs = require('fs')
const path = require('path')
const REQUIRE_RE = /\brequire(?:\.async)?\(\s*['"]([^'"]+)['"]\s*\)/g
/**
* @param {string} mpRoot dist/build/mp-weixin 根目录
* @param {string[]} subPackageRoots 如 ['addon/shop']
*/
function scanSubPackageRequires(mpRoot, subPackageRoots, addonKey) {
const requirePaths = new Set()
const forbiddenPaths = new Set()
const scannedFiles = []
for (const root of subPackageRoots) {
const absRoot = path.join(mpRoot, root)
if (!fs.existsSync(absRoot)) continue
for (const rel of walkJs(absRoot)) {
const absFile = path.join(absRoot, rel)
const relFromMp = path.posix.join(root, rel.replace(/\\/g, '/'))
scannedFiles.push(relFromMp)
const content = fs.readFileSync(absFile, 'utf-8')
let m
REQUIRE_RE.lastIndex = 0
while ((m = REQUIRE_RE.exec(content)) !== null) {
const spec = m[1]
const resolved = resolveRequire(mpRoot, absFile, spec)
if (!resolved) continue
if (isOtherAddonPath(resolved, subPackageRoots, addonKey)) {
forbiddenPaths.add(resolved)
continue
}
requirePaths.add(resolved)
}
}
}
return {
requirePaths: [...requirePaths].sort(),
forbiddenPaths: [...forbiddenPaths].sort(),
scannedFiles: scannedFiles.sort()
}
}
function walkJs(dir, base = dir) {
const out = []
for (const name of fs.readdirSync(dir)) {
const full = path.join(dir, name)
if (fs.statSync(full).isDirectory()) {
out.push(...walkJs(full, base))
} else if (name.endsWith('.js')) {
out.push(path.relative(base, full))
}
}
return out
}
function resolveRequire(mpRoot, fromFile, spec) {
if (spec.startsWith('http://') || spec.startsWith('https://')) return null
if (!spec.startsWith('.')) {
return spec.replace(/\\/g, '/')
}
const dir = path.dirname(fromFile)
let resolved = path.normalize(path.join(dir, spec))
if (!resolved.endsWith('.js') && !resolved.endsWith('.json')) {
if (fs.existsSync(resolved + '.js')) resolved += '.js'
else if (fs.existsSync(resolved + '.json')) resolved += '.json'
}
if (!resolved.startsWith(mpRoot)) return null
return path.relative(mpRoot, resolved).replace(/\\/g, '/')
}
function isOtherAddonPath(resolvedPath, allowedRoots, addonKey) {
if (!resolvedPath.startsWith('addon/')) return false
// 同插件目录下的文件(如 api/、hooks/ 等不属于子包的公共目录)不视为跨插件
if (addonKey && resolvedPath.startsWith(`addon/${addonKey}/`)) return false
for (const root of allowedRoots) {
if (resolvedPath === root || resolvedPath.startsWith(`${root}/`)) {
return false
}
}
return true
}
function validateRequires(mpRoot, requirePaths, forbiddenPaths) {
const missing = []
for (const p of requirePaths) {
const abs = path.join(mpRoot, p)
if (!fs.existsSync(abs)) {
missing.push(p)
}
}
return {
ok: missing.length === 0 && forbiddenPaths.length === 0,
missing,
forbiddenPaths
}
}
module.exports = {
scanSubPackageRequires,
validateRequires
}