diff --git a/package.json b/package.json index 1566264..a1ce947 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "unplugin-vue-components": "31.0.0", "unplugin-vue-setup-extend-plus": "1.0.1", "vite": "7.3.1", - "vite-plugin-compression": "0.5.1", "vite-plugin-svg-icons-ng": "^1.5.2", "vite-plugin-vue-devtools": "8.0.7", "vitest": "4.0.18", diff --git a/vite/plugins/compression.ts b/vite/plugins/compression.ts index aa8c779..4660ab8 100644 --- a/vite/plugins/compression.ts +++ b/vite/plugins/compression.ts @@ -1,28 +1,110 @@ -import compression from 'vite-plugin-compression'; +import { promises as fs } from 'fs'; +import path from 'path'; +import zlib from 'zlib'; +import { promisify } from 'util'; +import type { Plugin, ResolvedConfig } from 'vite'; -export default (env: any) => { - const { VITE_BUILD_COMPRESS } = env; - const plugin: any[] = []; - if (VITE_BUILD_COMPRESS) { - const compressList = VITE_BUILD_COMPRESS.split(','); - if (compressList.includes('gzip')) { - // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 - plugin.push( - compression({ - ext: '.gz', - deleteOriginFile: false - }) - ); - } - if (compressList.includes('brotli')) { - plugin.push( - compression({ - ext: '.br', - algorithm: 'brotliCompress', - deleteOriginFile: false - }) - ); - } +const gzip = promisify(zlib.gzip); +const brotliCompress = promisify(zlib.brotliCompress); +const compressibleFileRE = /\.(js|mjs|json|css|html)$/i; +const defaultThreshold = 1025; + +type CompressionKind = 'gzip' | 'brotli'; + +const compressionHandlers: Record Promise }> = { + gzip: { + ext: '.gz', + compress: (content) => gzip(content, { level: zlib.constants.Z_BEST_COMPRESSION }) + }, + brotli: { + ext: '.br', + compress: (content) => + brotliCompress(content, { + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY, + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT + } + }) } - return plugin; +}; + +async function collectFiles(rootDir: string): Promise { + const entries = await fs.readdir(rootDir, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async (entry) => { + const fullPath = path.join(rootDir, entry.name); + if (entry.isDirectory()) { + return collectFiles(fullPath); + } + return compressibleFileRE.test(entry.name) ? [fullPath] : []; + }) + ); + return files.flat(); +} + +function createCompressionPlugin(kind: CompressionKind): Plugin { + const handler = compressionHandlers[kind]; + let config: ResolvedConfig | undefined; + + return { + name: `local:compression:${kind}`, + apply: 'build', + enforce: 'post', + configResolved(resolvedConfig) { + config = resolvedConfig; + }, + async closeBundle() { + const outputDir = path.resolve(process.cwd(), config?.build.outDir ?? 'dist'); + const files = await collectFiles(outputDir); + const compressedEntries: Array<{ file: string; originalKb: string; compressedKb: string }> = []; + + await Promise.all( + files.map(async (filePath) => { + const stat = await fs.stat(filePath); + if (stat.size < defaultThreshold) { + return; + } + + const content = await fs.readFile(filePath); + const compressed = await handler.compress(content); + const outputFile = `${filePath}${handler.ext}`; + + await fs.writeFile(outputFile, compressed); + compressedEntries.push({ + file: path.relative(outputDir, outputFile).replaceAll('\\', '/'), + originalKb: (stat.size / 1024).toFixed(2), + compressedKb: (compressed.byteLength / 1024).toFixed(2) + }); + }) + ); + + if (!compressedEntries.length) { + return; + } + + compressedEntries.sort((a, b) => a.file.localeCompare(b.file)); + config?.logger.info(`\n[compression:${kind}] generated ${compressedEntries.length} files`); + for (const entry of compressedEntries) { + config?.logger.info(`${path.basename(outputDir)}/${entry.file} ${entry.originalKb}kb -> ${entry.compressedKb}kb`); + } + config?.logger.info(''); + } + }; +} + +export default (env: Record) => { + const { VITE_BUILD_COMPRESS } = env; + const plugins: Plugin[] = []; + if (!VITE_BUILD_COMPRESS) { + return plugins; + } + + const compressionList = VITE_BUILD_COMPRESS.split(',').map((item) => item.trim()) as CompressionKind[]; + if (compressionList.includes('gzip')) { + plugins.push(createCompressionPlugin('gzip')); + } + if (compressionList.includes('brotli')) { + plugins.push(createCompressionPlugin('brotli')); + } + return plugins; };