mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-11 13:02:22 +08:00
140 lines
4.7 KiB
JavaScript
140 lines
4.7 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
|
|
import legacy, { detectPolyfills } from '@vitejs/plugin-legacy'
|
|
|
|
const digest = (value) => createHash('sha256').update(value).digest('hex')
|
|
const inventoryPath = 'scripts/modern-polyfills.json'
|
|
const staleMessage =
|
|
'Modern polyfill inventory is stale; run pnpm polyfills:generate and commit scripts/modern-polyfills.json'
|
|
|
|
// Bind complete emitted module inputs as well as output: minification can hide APIs.
|
|
// Generation double-checks the exported detector against legacy's automatic build.
|
|
export function guardedLegacy(options) {
|
|
const capturePath = process.env.MODERN_POLYFILLS_CAPTURE
|
|
const inventory = capturePath
|
|
? null
|
|
: JSON.parse(
|
|
readFileSync(
|
|
process.env.MODERN_POLYFILLS_INVENTORY || inventoryPath,
|
|
'utf8',
|
|
),
|
|
)
|
|
const plugins = legacy({
|
|
...options,
|
|
modernPolyfills: Boolean(capturePath),
|
|
additionalModernPolyfills: [
|
|
...options.additionalModernPolyfills,
|
|
...(capturePath ? [] : inventory.imports),
|
|
],
|
|
})
|
|
const postProcess = plugins.find(
|
|
(plugin) => plugin.name === 'vite:legacy-post-process',
|
|
)
|
|
if (!postProcess?.renderChunk) {
|
|
throw new Error('plugin-legacy must provide its post-process render hook')
|
|
}
|
|
const original = postProcess.renderChunk
|
|
const render = typeof original === 'function' ? original : original.handler
|
|
const discovered = new Map()
|
|
let chunkNames
|
|
let context
|
|
if (capturePath) {
|
|
postProcess.renderChunk = async function (raw, chunk, output, meta) {
|
|
chunkNames ??= Object.keys(meta.chunks)
|
|
const imports = new Set()
|
|
await detectPolyfills(
|
|
raw,
|
|
options.modernTargets,
|
|
options.assumptions || {},
|
|
imports,
|
|
)
|
|
discovered.set(chunk.fileName, imports)
|
|
return render.call(this, raw, chunk, output, meta)
|
|
}
|
|
}
|
|
return [
|
|
...plugins,
|
|
{
|
|
name: 'guard-modern-polyfills',
|
|
enforce: 'post',
|
|
apply: 'build',
|
|
configResolved(config) {
|
|
if (config.build.ssr || options.renderLegacyChunks !== false) {
|
|
throw new Error(
|
|
'Modern polyfill guard requires a modern-only app build',
|
|
)
|
|
}
|
|
// Custom import paths must not bind the inventory to a checkout location.
|
|
context = [
|
|
{
|
|
...options,
|
|
additionalModernPolyfills: options.additionalModernPolyfills.map(
|
|
(file) =>
|
|
path.relative(process.cwd(), file).split(path.sep).join('/'),
|
|
),
|
|
},
|
|
config.build.target,
|
|
config.mode,
|
|
...[
|
|
'package.json',
|
|
'pnpm-lock.yaml',
|
|
'vite.config.mts',
|
|
'scripts/modern-polyfills.mjs',
|
|
'scripts/generate-modern-polyfills.mjs',
|
|
].map((file) => digest(readFileSync(file))),
|
|
...[
|
|
'.browserslistrc',
|
|
'browserslist',
|
|
'src/.browserslistrc',
|
|
'src/browserslist',
|
|
].map((file) =>
|
|
existsSync(file) ? digest(readFileSync(file)) : null,
|
|
),
|
|
digest(
|
|
readFileSync(new URL(import.meta.resolve('@vitejs/plugin-legacy'))),
|
|
),
|
|
]
|
|
},
|
|
generateBundle(_output, bundle) {
|
|
const imports = capturePath
|
|
? [
|
|
...new Set(
|
|
chunkNames.flatMap((name) => [...discovered.get(name)]),
|
|
),
|
|
]
|
|
: inventory.imports
|
|
const chunks = Object.values(bundle).filter(
|
|
(file) => file.type === 'chunk',
|
|
)
|
|
const moduleIds = new Set(
|
|
chunks.flatMap((chunk) => Object.keys(chunk.modules)),
|
|
)
|
|
const inputs = [...moduleIds].map((id) => {
|
|
// Rolldown's runtime is generated, not a transformed input module.
|
|
if (id === '\0rolldown/runtime.js') return null
|
|
// Vite's HTML entry code embeds an absolute import to the main module.
|
|
if (id.endsWith('.html')) return digest(readFileSync(id))
|
|
const code = this.getModuleInfo(id)?.code
|
|
if (code == null) this.error(`Missing emitted module input: ${id}`)
|
|
return digest(code)
|
|
})
|
|
const fingerprint = digest(
|
|
JSON.stringify([
|
|
context,
|
|
inputs.sort(),
|
|
chunks.map((chunk) => digest(chunk.code)).sort(),
|
|
imports,
|
|
]),
|
|
)
|
|
if (capturePath) {
|
|
writeFileSync(capturePath, JSON.stringify({ fingerprint, imports }))
|
|
} else if (!inventory.fingerprints.includes(fingerprint)) {
|
|
this.error(staleMessage)
|
|
}
|
|
},
|
|
},
|
|
]
|
|
}
|