mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2026-07-17 09:47:42 +00:00
276 lines
8.6 KiB
JavaScript
276 lines
8.6 KiB
JavaScript
/**
|
||
* Core 构建会把 @/locale 打进 index.js(uni-app 忽略 rollup external)。
|
||
* 剥离内联 createI18n + Language,改为 import shared/wap-locale.js 单例。
|
||
*/
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
const { WAP_LOCALE_URL } = require('./shared-external.cjs')
|
||
|
||
const WAP_LOCALE_IMPORT =
|
||
`import{t as __wapT,language as __wapLanguage,i18n as __wapI18n,default as __wapLocaleDefault}from"${WAP_LOCALE_URL}";`
|
||
|
||
const CREATE_I18N_IMPORT_RE =
|
||
/import\{createI18n as [\w$]+\}from"\/wap\/assets\/shared\/vue-i18n\.js";/
|
||
|
||
const MINIFIED_IDENT = '[\\w$]+'
|
||
|
||
function escapeRe(value) {
|
||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||
}
|
||
|
||
function findLocaleBlock(code) {
|
||
const startMatch = code.match(new RegExp(`let (${MINIFIED_IDENT})=[\\w$]+\\(\\{locale:`))
|
||
if (!startMatch) return null
|
||
|
||
const i18nVar = startMatch[1]
|
||
const start = startMatch.index
|
||
const head = code.slice(start, start + 1200)
|
||
if (!head.includes('globalInjection:!0')) return null
|
||
|
||
const classMatch = code.slice(start).match(new RegExp(`const (${MINIFIED_IDENT})=new class\\{`))
|
||
if (!classMatch) return null
|
||
|
||
const langVar = classMatch[1]
|
||
const classStart = start + classMatch.index
|
||
|
||
const endToken = `}(${i18nVar});`
|
||
let endPos = -1
|
||
let pos = classStart
|
||
while (true) {
|
||
const next = code.indexOf(endToken, pos)
|
||
if (next < 0) break
|
||
endPos = next
|
||
pos = next + endToken.length
|
||
}
|
||
if (endPos < 0) return null
|
||
|
||
const block = code.slice(start, endPos + endToken.length)
|
||
if (!block.includes('loadLocaleMessages') || !block.includes('mergeLocaleMessage')) {
|
||
return null
|
||
}
|
||
|
||
return {
|
||
start,
|
||
end: endPos + endToken.length,
|
||
i18nVar,
|
||
langVar
|
||
}
|
||
}
|
||
|
||
function findInlineLocaleSymbols(code, i18nVar, langVar) {
|
||
const i18n = escapeRe(i18nVar)
|
||
const lang = escapeRe(langVar)
|
||
const tRe = new RegExp(
|
||
'(' +
|
||
MINIFIED_IDENT +
|
||
')=' +
|
||
MINIFIED_IDENT +
|
||
'=>\\{let ' +
|
||
MINIFIED_IDENT +
|
||
'=' +
|
||
MINIFIED_IDENT +
|
||
'\\(\\);const ' +
|
||
MINIFIED_IDENT +
|
||
'=`\\$\\{' +
|
||
lang +
|
||
'\\.getFileKey\\(' +
|
||
MINIFIED_IDENT +
|
||
'\\)\\.fileKey\\}\\.\\$\\{' +
|
||
MINIFIED_IDENT +
|
||
'\\}`;return ' +
|
||
i18n +
|
||
'\\.global\\.t\\(' +
|
||
MINIFIED_IDENT +
|
||
'\\)!=' +
|
||
MINIFIED_IDENT +
|
||
'\\?' +
|
||
i18n +
|
||
'\\.global\\.t\\(' +
|
||
MINIFIED_IDENT +
|
||
'\\):' +
|
||
i18n +
|
||
'\\.global\\.t\\(' +
|
||
MINIFIED_IDENT +
|
||
'\\)!=' +
|
||
MINIFIED_IDENT +
|
||
'\\?' +
|
||
i18n +
|
||
'\\.global\\.t\\(' +
|
||
MINIFIED_IDENT +
|
||
'\\):""\\}'
|
||
)
|
||
const tMatch = code.match(tRe)
|
||
|
||
const pluginRe = new RegExp(
|
||
`(${MINIFIED_IDENT})=\\{install\\(${MINIFIED_IDENT}\\)\\{${MINIFIED_IDENT}\\.use\\(${i18n}\\)\\}\\}`
|
||
)
|
||
const pluginMatch = code.match(pluginRe)
|
||
|
||
return { tMatch, pluginMatch }
|
||
}
|
||
|
||
function patchReferences(next, i18nVar, langVar) {
|
||
const lang = escapeRe(langVar)
|
||
const i18n = escapeRe(i18nVar)
|
||
next = next.replace(new RegExp(`${lang}\\.loadLocaleMessages`, 'g'), '__wapLanguage.loadLocaleMessages')
|
||
next = next.replace(new RegExp(`${lang}\\.getFileKey`, 'g'), '__wapLanguage.getFileKey')
|
||
next = next.replace(new RegExp(`${lang}\\.loadAllLocaleMessages`, 'g'), '__wapLanguage.loadAllLocaleMessages')
|
||
next = next.replace(new RegExp(`${i18n}\\.global`, 'g'), '__wapI18n.global')
|
||
return next
|
||
}
|
||
|
||
function fixExportAliases(next, i18nVar, langVar, tVar, pluginVar) {
|
||
const pairs = [
|
||
[langVar, '__wapLanguage'],
|
||
[i18nVar, '__wapI18n'],
|
||
[tVar, '__wapT'],
|
||
[pluginVar, '__wapLocaleDefault']
|
||
]
|
||
for (const [from, to] of pairs) {
|
||
if (!from || from === to) continue
|
||
next = next.replace(new RegExp(`${escapeRe(from)} as `, 'g'), `${to} as `)
|
||
}
|
||
return next
|
||
}
|
||
|
||
function fixLocaleExportLeaks(code) {
|
||
if (!code.includes(WAP_LOCALE_URL)) return null
|
||
const exportIdx = code.lastIndexOf('export{')
|
||
if (exportIdx < 0) return null
|
||
|
||
const head = code.slice(0, exportIdx)
|
||
let next = code
|
||
let changed = false
|
||
|
||
for (const [bad, good] of [
|
||
['QL', '__wapLanguage'],
|
||
['UL', '__wapI18n'],
|
||
['m$', '__wapLanguage'],
|
||
['g$', '__wapI18n']
|
||
]) {
|
||
const exportRe = new RegExp(`\\b${escapeRe(bad)} as `, 'g')
|
||
if (!exportRe.test(code.slice(exportIdx))) continue
|
||
const defRe = new RegExp(`\\b(?:const|let|var|function) ${escapeRe(bad)}\\b|(?:^|[=,;(])${escapeRe(bad)}=`)
|
||
if (defRe.test(head)) continue
|
||
next = next.replace(exportRe, `${good} as `)
|
||
changed = true
|
||
}
|
||
|
||
return changed ? next : null
|
||
}
|
||
|
||
function validatePatchedSyntax(code, langVar, i18nVar) {
|
||
if (!code.includes(WAP_LOCALE_URL) || !code.includes('__wapI18n')) return false
|
||
if (
|
||
new RegExp(`let ${MINIFIED_IDENT}=[\\w$]+\\(\\{locale:[\\s\\S]{0,1200}?globalInjection:!0`).test(
|
||
code
|
||
)
|
||
) {
|
||
return false
|
||
}
|
||
if (/[\w$]+=__wapT[.`]/.test(code)) return false
|
||
if (/async loadSplitAddonLocale/.test(code)) return false
|
||
if (/async loadSplitAppLocale/.test(code)) return false
|
||
if (
|
||
new RegExp(`\\}\\(${MINIFIED_IDENT}\\);function ${MINIFIED_IDENT}\\(e\\)\\{return uni\\.getStorageSync\\("pid"\\)`).test(
|
||
code
|
||
)
|
||
) {
|
||
return false
|
||
}
|
||
if (/new class\{constructor\(e\)\{[^}]*loadLocale/.test(code)) return false
|
||
|
||
const exportPart = code.slice(code.lastIndexOf('export{'))
|
||
if (langVar && new RegExp(`${escapeRe(langVar)} as `).test(exportPart)) return false
|
||
if (i18nVar && new RegExp(`${escapeRe(i18nVar)} as `).test(exportPart)) return false
|
||
return true
|
||
}
|
||
|
||
function patchCoreIndex(code) {
|
||
if (code.includes(WAP_LOCALE_URL)) return null
|
||
if (!CREATE_I18N_IMPORT_RE.test(code)) return null
|
||
|
||
const block = findLocaleBlock(code)
|
||
if (!block) return null
|
||
|
||
const { start, end, i18nVar, langVar } = block
|
||
const { tMatch, pluginMatch } = findInlineLocaleSymbols(code, i18nVar, langVar)
|
||
|
||
let next = code.slice(0, start) + code.slice(end)
|
||
next = next.replace(CREATE_I18N_IMPORT_RE, WAP_LOCALE_IMPORT)
|
||
|
||
if (tMatch) {
|
||
next = next.replace(tMatch[0], `${tMatch[1]}=__wapT`)
|
||
}
|
||
|
||
if (pluginMatch) {
|
||
next = next.replace(pluginMatch[0], `${pluginMatch[1]}=__wapLocaleDefault`)
|
||
}
|
||
|
||
next = patchReferences(next, i18nVar, langVar)
|
||
next = fixExportAliases(
|
||
next,
|
||
i18nVar,
|
||
langVar,
|
||
tMatch ? tMatch[1] : null,
|
||
pluginMatch ? pluginMatch[1] : null
|
||
)
|
||
|
||
if (!validatePatchedSyntax(next, langVar, i18nVar)) return null
|
||
return next
|
||
}
|
||
|
||
function patchCoreH5Dir(h5Dir) {
|
||
const assetsDir = path.join(h5Dir, 'assets')
|
||
if (!fs.existsSync(assetsDir)) return { patched: 0, failed: 0 }
|
||
|
||
let patched = 0
|
||
let failed = 0
|
||
for (const name of fs.readdirSync(assetsDir)) {
|
||
if (!name.startsWith('index-') || !name.endsWith('.js')) continue
|
||
const filePath = path.join(assetsDir, name)
|
||
const code = fs.readFileSync(filePath, 'utf-8')
|
||
let next = patchCoreIndex(code)
|
||
if (!next) {
|
||
next = fixLocaleExportLeaks(code)
|
||
if (next) {
|
||
fs.writeFileSync(filePath, next, 'utf-8')
|
||
patched++
|
||
console.log(`[patch-core-locale-external] export-leak ${name}`)
|
||
continue
|
||
}
|
||
if (findLocaleBlock(code) && !code.includes(WAP_LOCALE_URL)) {
|
||
failed++
|
||
console.error(`[patch-core-locale-external] inline locale not patched: ${name}`)
|
||
} else if (code.includes(WAP_LOCALE_URL) && !validatePatchedSyntax(code)) {
|
||
failed++
|
||
console.error(`[patch-core-locale-external] invalid patched file (rebuild core): ${name}`)
|
||
}
|
||
continue
|
||
}
|
||
fs.writeFileSync(filePath, next, 'utf-8')
|
||
patched++
|
||
console.log(`[patch-core-locale-external] ${name}`)
|
||
}
|
||
return { patched, failed }
|
||
}
|
||
|
||
module.exports = {
|
||
patchCoreIndex,
|
||
patchCoreH5Dir,
|
||
findLocaleBlock,
|
||
validatePatchedSyntax,
|
||
fixLocaleExportLeaks
|
||
}
|
||
|
||
if (require.main === module) {
|
||
const dir = process.argv[2]
|
||
if (!dir) {
|
||
console.error('Usage: node patch-core-locale-external.cjs <h5-dir>')
|
||
process.exit(1)
|
||
}
|
||
const r = patchCoreH5Dir(path.resolve(dir))
|
||
console.log('[patch-core-locale-external]', r)
|
||
process.exit(r.failed > 0 ? 1 : 0)
|
||
}
|