feat(performance): 优化性能并添加离线支持

- 新增 .babelrc 配置文件,使用 Babel 进行代码转换和压缩
- 添加 PERFORMANCE.md 文件,详细说明性能优化措施
- 在 README.md 中增加性能优化相关说明
- 更新 index.html,添加 Service Worker 注册和性能优化脚本
This commit is contained in:
Abner
2025-08-05 22:32:19 +08:00
parent 82bfe5805c
commit ac0744c4d5
17 changed files with 28986 additions and 49 deletions

159
scripts/compress.js Normal file
View File

@@ -0,0 +1,159 @@
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const { promisify } = require('util');
const gzip = promisify(zlib.gzip);
const brotliCompress = promisify(zlib.brotliCompress);
// 压缩配置
const compressionOptions = {
gzip: {
level: 9,
memLevel: 9
},
brotli: {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 11,
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_GENERIC,
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0
}
}
};
// 需要压缩的文件类型
const compressibleExtensions = ['.js', '.css', '.html', '.json', '.xml', '.svg'];
// 压缩单个文件
async function compressFile(filePath) {
try {
const content = fs.readFileSync(filePath);
const ext = path.extname(filePath);
// 只压缩特定类型的文件
if (!compressibleExtensions.includes(ext)) {
return null;
}
const fileName = path.basename(filePath);
const dir = path.dirname(filePath);
// Gzip压缩
const gzipped = await gzip(content, compressionOptions.gzip);
const gzipPath = path.join(dir, `${fileName}.gz`);
fs.writeFileSync(gzipPath, gzipped);
// Brotli压缩如果支持
try {
const brotlied = await brotliCompress(content, compressionOptions.brotli);
const brotliPath = path.join(dir, `${fileName}.br`);
fs.writeFileSync(brotliPath, brotlied);
} catch (error) {
console.log(`Brotli压缩失败 ${fileName}:`, error.message);
}
const originalSize = content.length;
const gzipSize = gzipped.length;
const compressionRatio = ((originalSize - gzipSize) / originalSize * 100).toFixed(1);
return {
file: fileName,
original: originalSize,
gzip: gzipSize,
ratio: compressionRatio
};
} catch (error) {
console.error(`压缩文件失败 ${filePath}:`, error.message);
return null;
}
}
// 递归压缩目录
async function compressDirectory(dirPath) {
const results = [];
async function scanDirectory(dir) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
await scanDirectory(fullPath);
} else if (stat.isFile()) {
const result = await compressFile(fullPath);
if (result) {
results.push(result);
}
}
}
}
await scanDirectory(dirPath);
return results;
}
// 主压缩函数
async function compressBuild() {
const distDir = path.join(__dirname, '../dist');
if (!fs.existsSync(distDir)) {
console.error('dist目录不存在请先运行构建命令');
return;
}
console.log('开始压缩构建文件...');
try {
const results = await compressDirectory(distDir);
if (results.length === 0) {
console.log('没有找到需要压缩的文件');
return;
}
// 显示压缩结果
console.log('\n压缩结果:');
console.log('文件名'.padEnd(30) + '原始大小'.padEnd(12) + 'Gzip大小'.padEnd(12) + '压缩率');
console.log('-'.repeat(70));
let totalOriginal = 0;
let totalGzip = 0;
results.forEach(result => {
totalOriginal += result.original;
totalGzip += result.gzip;
const originalKB = (result.original / 1024).toFixed(1);
const gzipKB = (result.gzip / 1024).toFixed(1);
console.log(
result.file.padEnd(30) +
`${originalKB}KB`.padEnd(12) +
`${gzipKB}KB`.padEnd(12) +
`${result.ratio}%`
);
});
const totalRatio = ((totalOriginal - totalGzip) / totalOriginal * 100).toFixed(1);
console.log('-'.repeat(70));
console.log(
'总计'.padEnd(30) +
`${(totalOriginal / 1024).toFixed(1)}KB`.padEnd(12) +
`${(totalGzip / 1024).toFixed(1)}KB`.padEnd(12) +
`${totalRatio}%`
);
console.log('\n压缩完成');
} catch (error) {
console.error('压缩失败:', error);
}
}
// 如果直接运行此脚本
if (require.main === module) {
compressBuild();
}
module.exports = { compressBuild, compressFile };

122
scripts/optimize-images.js Normal file
View File

@@ -0,0 +1,122 @@
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');
const fs = require('fs');
const path = require('path');
// 图片优化配置
const webpOptions = {
quality: 85,
method: 6,
autoFilter: true,
filter: 0.8
};
const jpegOptions = {
quality: 85,
progressive: true,
smooth: 1
};
const pngOptions = {
quality: [0.6, 0.8],
speed: 4
};
// 优化函数
async function optimizeImages() {
const srcDir = path.join(__dirname, '../src/images');
const distDir = path.join(__dirname, '../dist/images');
// 确保目标目录存在
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
try {
console.log('开始优化图片...');
// 优化为WebP格式
const webpFiles = await imagemin([`${srcDir}/*.{jpg,jpeg,png}`], {
destination: distDir,
plugins: [
imageminWebp(webpOptions)
]
});
console.log(`WebP优化完成: ${webpFiles.length} 个文件`);
// 优化JPEG文件
const jpegFiles = await imagemin([`${srcDir}/*.{jpg,jpeg}`], {
destination: distDir,
plugins: [
imageminMozjpeg(jpegOptions)
]
});
console.log(`JPEG优化完成: ${jpegFiles.length} 个文件`);
// 优化PNG文件
const pngFiles = await imagemin([`${srcDir}/*.png`], {
destination: distDir,
plugins: [
imageminPngquant(pngOptions)
]
});
console.log(`PNG优化完成: ${pngFiles.length} 个文件`);
// 生成图片清单
generateImageManifest(distDir);
console.log('图片优化完成!');
} catch (error) {
console.error('图片优化失败:', error);
}
}
// 生成图片清单
function generateImageManifest(distDir) {
const manifest = {
images: [],
generated: new Date().toISOString()
};
function scanDirectory(dir, relativePath = '') {
const files = fs.readdirSync(dir);
files.forEach(file => {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDirectory(fullPath, path.join(relativePath, file));
} else if (stat.isFile()) {
const ext = path.extname(file).toLowerCase();
if (['.jpg', '.jpeg', '.png', '.webp'].includes(ext)) {
manifest.images.push({
name: file,
path: path.join(relativePath, file),
size: stat.size,
type: ext.substring(1)
});
}
}
});
}
scanDirectory(distDir);
const manifestPath = path.join(distDir, 'manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log(`图片清单已生成: ${manifestPath}`);
}
// 如果直接运行此脚本
if (require.main === module) {
optimizeImages();
}
module.exports = { optimizeImages };