Appearance
前端老项目发版更新缓存解决方案
概述
在前端老项目中,由于浏览器缓存机制,用户可能无法及时获取到最新的JavaScript文件,导致功能异常或显示旧版本内容。本文档提供了三种主要的解决方案,帮助开发者在不使用现代打包工具的情况下有效解决缓存问题。
目录
1. 配置Nginx不缓存JS文件
1.1 基本配置
通过修改Nginx配置文件,可以设置特定文件类型的缓存策略。以下是针对JavaScript文件的缓存控制配置:
nginx
server {
listen 80;
server_name your-domain.com;
root /var/www/html;
# 针对JS文件设置不缓存
location ~* \.js$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
# 可选:添加CORS头部
add_header Access-Control-Allow-Origin "*";
}
# 其他静态资源可以设置较长的缓存时间
location ~* \.(css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML文件设置短缓存或不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache, must-revalidate";
expires 0;
}
}
1.2 高级配置选项
1.2.1 基于环境的缓存策略
nginx
# 开发环境:完全不缓存
location ~* \.js$ {
if ($http_host ~* "dev\.|localhost") {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
}
# 生产环境:短时间缓存
location ~* \.js$ {
if ($http_host !~* "dev\.|localhost") {
add_header Cache-Control "public, max-age=300"; # 5分钟缓存
}
}
1.2.2 基于文件路径的精细控制
nginx
# 应用核心文件不缓存
location ~* ^/js/(app|main|core)\.js$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# 第三方库可以长时间缓存
location ~* ^/js/vendor/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
1.3 配置验证
配置完成后,可以通过以下方式验证:
bash
# 检查Nginx配置语法
nginx -t
# 重新加载配置
nginx -s reload
# 使用curl测试缓存头部
curl -I http://your-domain.com/js/app.js
预期响应头部应包含:
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
1.4 优缺点
优点:
- 配置简单,无需修改代码
- 立即生效,无需重新部署
- 适合临时解决缓存问题
缺点:
- 每次请求都重新下载JS/CSS,影响性能
- 增加服务器带宽消耗
- 不是最佳实践
2. 动态给JS文件添加版本号
2.1 add-version.js 脚本介绍
add-version.js
是一个Node.js脚本,用于自动为HTML文件中的JavaScript引用添加版本号参数,有效解决浏览器缓存问题。
2.2 脚本功能特性
- ✅ 自动识别HTML中的本地JS文件引用
- ✅ 支持时间戳和自定义版本号
- ✅ 智能跳过外部CDN链接
- ✅ 自动处理已有版本号的更新
- ✅ 创建备份文件保护原始内容
- ✅ 详细的操作日志输出
2.3 安装和使用
2.3.1 基本使用方法
bash
# 使用时间戳版本号(默认)
node add-version.js index.html
# 使用自定义版本号
node add-version.js -v 1.2.3 index.html
node add-version.js --version v2.0.1 index.html
# 处理多个文件
node add-version.js -v 1.0.0 *.html
2.3.2 命令行参数说明
参数 | 简写 | 说明 | 示例 |
---|---|---|---|
--version | -v | 指定自定义版本号 | -v 1.2.3 |
--help | -h | 显示帮助信息 | -h |
文件路径 | - | HTML文件路径(可选,默认index.html) | dist/index.html |
2.4 脚本工作原理
2.4.1 处理前后对比
处理前:
html
<script src="js/app.js"></script>
<script src="js/utils.js?v=old123"></script>
<script src="https://cdn.example.com/lib.js"></script>
处理后:
html
<script src="js/app.js?v=1757267890123"></script>
<script src="js/utils.js?v=1757267890123"></script>
<script src="https://cdn.example.com/lib.js"></script>
2.4.2 版本号生成策略
时间戳版本号(默认)
- 格式:
Date.now()
生成的13位时间戳 - 优点:确保每次执行都是唯一值
- 适用:开发和测试环境
- 格式:
自定义版本号
- 格式:用户指定的任意字符串
- 优点:语义化版本控制
- 适用:生产环境发布
2.5 集成到构建流程
2.5.1 npm scripts 集成
在 package.json
中添加脚本:
json
{
"scripts": {
"build": "node add-version.js",
"build:prod": "node add-version.js -v $(date +%Y%m%d%H%M%S)",
"version:update": "node add-version.js -v",
"deploy": "npm run build:prod && rsync -av ./ user@server:/var/www/html/"
}
}
2.5.2 Git Hooks 集成
创建 .git/hooks/pre-commit
文件:
bash
#!/bin/sh
# 在提交前自动更新版本号
node add-version.js -v $(git rev-parse --short HEAD)
git add *.html
2.5.3 CI/CD 集成示例
GitHub Actions:
yaml
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Add version to JS files
run: node add-version.js -v ${{ github.sha }}
- name: Deploy to server
run: |
# 部署脚本
2.6 高级配置
2.6.1 批量处理脚本
创建 batch-version.js
:
javascript
const fs = require('fs');
const path = require('path');
const VersionAdder = require('./add-version.js');
// 批量处理多个HTML文件
const htmlFiles = [
'index.html',
'admin.html',
'mobile.html'
];
const version = process.argv[2] || Date.now().toString();
htmlFiles.forEach(file => {
if (fs.existsSync(file)) {
console.log(`\n处理文件: ${file}`);
const versionAdder = new VersionAdder(file, version);
versionAdder.run();
}
});
2.6.2 配置文件支持
创建 version.config.js
:
javascript
module.exports = {
// 要处理的HTML文件
htmlFiles: ['index.html', 'admin.html'],
// 版本号生成策略
versionStrategy: 'timestamp', // 'timestamp' | 'git-hash' | 'custom'
// 自定义版本号
customVersion: '1.0.0',
// 排除的文件模式
excludePatterns: [
'https://',
'http://',
'//cdn.',
'/vendor/'
],
// 备份设置
backup: {
enabled: true,
suffix: '.backup'
}
};
2.7 优缺点
优点:
- 保持缓存优势,只有更新时才重新下载
- 不影响现有代码结构
- 可以精确控制版本号
- 适合渐进式升级
缺点:
- 需要手动或半自动执行
- 需要修改部署流程
- 版本号管理需要额外考虑
3. 构建工具升级方案
3.1 从Gulp升级到现代构建工具
3.1.1 现状分析
Gulp的局限性:
- 配置复杂,需要手动管理依赖
- 缺乏内置的模块化支持
- 热更新功能有限
- 缓存处理需要额外插件
升级的必要性:
- 更好的开发体验
- 自动化的缓存处理
- 现代JavaScript特性支持
- 更快的构建速度
3.2 Vite 迁移方案
3.2.1 Vite 优势
- ⚡ 极快的冷启动
- 🔥 即时热模块替换(HMR)
- 📦 内置文件指纹(hash)
- 🎯 按需编译
- 🛠️ 丰富的插件生态
3.2.2 迁移步骤
1. 安装Vite
bash
npm install --save-dev vite
2. 创建 vite.config.js
javascript
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
// 多页面应用配置
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html')
}
},
// 文件名包含hash
assetsDir: 'assets',
// 自定义文件名格式
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.');
const ext = info[info.length - 1];
if (/\.(css)$/.test(assetInfo.name)) {
return `css/[name]-[hash].${ext}`;
}
return `assets/[name]-[hash].${ext}`;
}
},
// 开发服务器配置
server: {
port: 3000,
open: true,
// 代理API请求
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
});
3. 更新 package.json
json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
4. 项目结构调整
project/
├── index.html # 入口HTML文件
├── admin.html # 管理后台入口
├── src/
│ ├── js/
│ │ ├── main.js # 主入口文件
│ │ └── admin.js # 管理后台入口
│ ├── css/
│ └── assets/
├── public/ # 静态资源
└── vite.config.js
3.2.3 HTML文件调整
调整前(传统方式):
html
<script src="js/app.js"></script>
<script src="js/utils.js"></script>
调整后(Vite方式):
html
<script type="module" src="/src/js/main.js"></script>
3.3 Webpack 迁移方案
3.3.1 Webpack 优势
- 🔧 高度可配置
- 📦 强大的代码分割
- 🎯 精确的缓存控制
- 🔌 丰富的loader和plugin生态
3.3.2 基础配置
1. 安装依赖
bash
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev html-webpack-plugin mini-css-extract-plugin
2. 创建 webpack.config.js
javascript
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/js/main.js',
admin: './src/js/admin.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
clean: true
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash:8][ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'index.html',
chunks: ['main']
}),
new HtmlWebpackPlugin({
template: './admin.html',
filename: 'admin.html',
chunks: ['admin']
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
devServer: {
static: './dist',
port: 3000,
open: true,
hot: true
}
};
3.4 构建工具对比
特性 | Gulp | Vite | Webpack |
---|---|---|---|
学习曲线 | 中等 | 简单 | 复杂 |
构建速度 | 中等 | 极快 | 较慢 |
热更新 | 需配置 | 内置 | 需配置 |
文件指纹 | 需插件 | 内置 | 内置 |
代码分割 | 手动 | 自动 | 强大 |
配置复杂度 | 高 | 低 | 高 |
生态系统 | 成熟 | 快速发展 | 最丰富 |
适用场景 | 传统项目 | 现代开发 | 复杂应用 |
3.5 缓存处理对比
3.5.1 文件指纹生成
Vite:
javascript
// 自动生成,无需配置
// 输出:app.a1b2c3d4.js
Webpack:
javascript
// webpack.config.js
output: {
filename: '[name].[contenthash:8].js'
}
// 输出:app.a1b2c3d4.js
Gulp:
javascript
// 需要额外插件
const rev = require('gulp-rev');
const revReplace = require('gulp-rev-replace');
gulp.task('js', () => {
return gulp.src('src/js/**/*.js')
.pipe(uglify())
.pipe(rev())
.pipe(gulp.dest('dist/js'))
.pipe(rev.manifest())
.pipe(gulp.dest('dist'));
});
3.5.2 缓存策略
现代构建工具的优势:
内容哈希(Content Hash)
- 文件内容改变时,哈希值自动更新
- 未改变的文件保持相同哈希,利用浏览器缓存
代码分割(Code Splitting)
- 将第三方库单独打包
- 业务代码变更不影响库文件缓存
长期缓存(Long-term Caching)
- 配合CDN实现最佳缓存策略
- 自动处理模块依赖关系
4. 最佳实践建议
4.1 缓存策略选择
4.1.1 根据项目阶段选择
开发阶段:
- 使用
add-version.js
脚本 + 时间戳 - Nginx配置完全不缓存
- 快速迭代,及时看到更改
测试阶段:
- 使用
add-version.js
脚本 + 版本号 - Nginx配置短时间缓存(5-10分钟)
- 便于测试缓存相关问题
生产阶段:
- 优先使用现代构建工具(Vite/Webpack)
- 配置长期缓存策略
- 结合CDN优化性能
4.1.2 混合策略
nginx
# 生产环境配置示例
server {
# 应用文件短缓存
location ~* ^/js/(app|main)\.[a-f0-9]+\.js$ {
expires 1h;
add_header Cache-Control "public, max-age=3600";
}
# 第三方库长缓存
location ~* ^/js/vendor\.[a-f0-9]+\.js$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 无版本号的文件不缓存
location ~* \.js$ {
add_header Cache-Control "no-cache, must-revalidate";
}
}
4.2 监控和调试
4.2.1 缓存状态检查
浏览器开发者工具:
- Network面板查看缓存状态
- Application面板清除缓存测试
- 使用硬刷新(Ctrl+F5)验证
命令行工具:
bash
# 检查响应头
curl -I http://your-domain.com/js/app.js
# 检查缓存状态
curl -H "If-None-Match: \"abc123\"" http://your-domain.com/js/app.js
4.2.2 自动化测试
创建缓存测试脚本 test-cache.js
:
javascript
const axios = require('axios');
async function testCache(url) {
try {
const response = await axios.get(url);
console.log(`URL: ${url}`);
console.log(`Cache-Control: ${response.headers['cache-control']}`);
console.log(`ETag: ${response.headers.etag}`);
console.log(`Last-Modified: ${response.headers['last-modified']}`);
console.log('---');
} catch (error) {
console.error(`Error testing ${url}:`, error.message);
}
}
// 测试多个文件
const testUrls = [
'http://localhost/js/app.js',
'http://localhost/js/vendor.js',
'http://localhost/css/style.css'
];
testUrls.forEach(testCache);
4.3 性能优化建议
4.3.1 资源优先级
html
<!-- 关键资源预加载 -->
<link rel="preload" href="/js/app.js?v=123" as="script">
<link rel="preload" href="/css/critical.css?v=123" as="style">
<!-- 非关键资源延迟加载 -->
<script src="/js/analytics.js?v=123" defer></script>
<script src="/js/widgets.js?v=123" async></script>
4.3.2 CDN配置
javascript
// CDN缓存配置示例
const cdnConfig = {
// 静态资源CDN
staticCDN: 'https://static.example.com',
// 缓存策略
cacheRules: {
// JS文件:1小时缓存
js: 'max-age=3600, s-maxage=86400',
// CSS文件:1天缓存
css: 'max-age=86400, s-maxage=604800',
// 图片:1周缓存
images: 'max-age=604800, s-maxage=2592000'
}
};
4.4 故障排除
4.4.1 常见问题
问题1:版本号添加后仍然缓存
bash
# 检查Nginx配置是否生效
nginx -t && nginx -s reload
# 检查浏览器缓存
# 使用无痕模式或清除缓存测试
问题2:脚本执行失败
bash
# 检查Node.js版本
node --version
# 检查文件权限
ls -la add-version.js
# 查看详细错误信息
node add-version.js --verbose
问题3:构建工具迁移问题
bash
# 清除node_modules重新安装
rm -rf node_modules package-lock.json
npm install
# 检查配置文件语法
npm run build -- --dry-run
4.4.2 调试技巧
- 逐步验证:从简单配置开始,逐步添加复杂功能
- 日志记录:在关键步骤添加日志输出
- 版本对比:保留工作版本的配置作为参考
- 环境隔离:在开发环境充分测试后再部署生产
总结
本文档提供了三种主要的前端缓存解决方案:
- Nginx配置:适合快速解决缓存问题,配置简单但不够精细
- 版本号脚本:平衡了简单性和有效性,适合传统项目
- 现代构建工具:提供最佳的长期解决方案,适合新项目或重构
选择合适的方案需要考虑项目规模、团队技术栈、维护成本等因素。建议从简单方案开始,逐步向现代化工具迁移,以获得更好的开发体验和性能表现。
附录:add-version.js 完整代码
A.1 脚本源码
以下是 add-version.js
的完整源代码,可以直接复制使用:
javascript
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
/**
* 为HTML文件中的JS文件引用添加版本号
* 防止浏览器缓存导致更新后无法加载新内容
*/
class VersionAdder {
constructor(htmlFilePath, customVersion = null) {
this.htmlFilePath = htmlFilePath;
this.version = customVersion || Date.now().toString();
this.isCustomVersion = customVersion !== null;
}
/**
* 读取HTML文件内容
*/
readHtmlFile() {
try {
const content = fs.readFileSync(this.htmlFilePath, 'utf8');
console.log(`✓ 成功读取文件: ${this.htmlFilePath}`);
return content;
} catch (error) {
console.error(`✗ 读取文件失败: ${error.message}`);
process.exit(1);
}
}
/**
* 为JS文件添加版本号
* @param {string} htmlContent - HTML文件内容
* @returns {string} - 修改后的HTML内容
*/
addVersionToJsFiles(htmlContent) {
let modifiedContent = htmlContent;
let modifiedCount = 0;
// 匹配所有script标签的src属性(包括带查询参数的)
const scriptRegex = /<script\s+[^>]*src=["']([^"']*\.js[^"']*)["'][^>]*>/gi;
modifiedContent = modifiedContent.replace(scriptRegex, (match, src) => {
// 跳过外部CDN链接
if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//')) {
console.log(`- 跳过外部链接: ${src}`);
return match;
}
let cleanSrc = src;
let action = '添加版本号';
// 如果已经有版本号,先移除旧版本号
if (src.includes('?v=') || src.includes('&v=')) {
// 移除版本号参数,处理各种情况
cleanSrc = src.replace(/[?&]v=[^&]*/, '').replace(/^&/, '?').replace(/&&/g, '&');
action = '更新版本号';
}
// 添加新版本号
const versionedSrc = `${cleanSrc}?v=${this.version}`;
const newMatch = match.replace(src, versionedSrc);
console.log(`✓ ${action}: ${src} -> ${versionedSrc}`);
modifiedCount++;
return newMatch;
});
console.log(`\n总共为 ${modifiedCount} 个JS文件添加了版本号`);
return modifiedContent;
}
/**
* 保存修改后的HTML文件
* @param {string} content - 修改后的内容
*/
saveHtmlFile(content) {
try {
// 创建备份文件
const backupPath = this.htmlFilePath + '.backup';
fs.copyFileSync(this.htmlFilePath, backupPath);
console.log(`✓ 创建备份文件: ${backupPath}`);
// 保存修改后的文件
fs.writeFileSync(this.htmlFilePath, content, 'utf8');
console.log(`✓ 保存修改后的文件: ${this.htmlFilePath}`);
} catch (error) {
console.error(`✗ 保存文件失败: ${error.message}`);
process.exit(1);
}
}
/**
* 执行版本号添加流程
*/
run() {
console.log('开始为JS文件添加版本号...');
if (this.isCustomVersion) {
console.log(`自定义版本号: ${this.version}\n`);
} else {
console.log(`时间戳版本号: ${this.version}\n`);
}
// 1. 读取HTML文件
const htmlContent = this.readHtmlFile();
// 2. 添加版本号
const modifiedContent = this.addVersionToJsFiles(htmlContent);
// 3. 保存文件
this.saveHtmlFile(modifiedContent);
console.log('\n✓ 版本号添加完成!');
}
}
// 主程序入口
function main() {
const args = process.argv.slice(2);
// 解析命令行参数
let htmlFilePath = 'index.html';
let customVersion = null;
// 处理帮助信息
if (args.includes('-h') || args.includes('--help')) {
showHelp();
return;
}
// 解析参数
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '-v' || arg === '--version') {
customVersion = args[i + 1];
if (!customVersion) {
console.error('✗ 错误: -v/--version 参数需要指定版本号值');
showHelp();
process.exit(1);
}
i++; // 跳过版本号值
} else if (!arg.startsWith('-')) {
htmlFilePath = arg;
}
}
// 如果没有指定HTML文件,使用默认值
if (!htmlFilePath || htmlFilePath.startsWith('-')) {
htmlFilePath = 'index.html';
}
// 如果是相对路径,转换为绝对路径
if (!path.isAbsolute(htmlFilePath)) {
htmlFilePath = path.resolve(process.cwd(), htmlFilePath);
}
// 检查文件是否存在
if (!fs.existsSync(htmlFilePath)) {
console.error(`✗ 文件不存在: ${htmlFilePath}`);
showHelp();
process.exit(1);
}
// 创建版本添加器并执行
const versionAdder = new VersionAdder(htmlFilePath, customVersion);
versionAdder.run();
}
// 显示帮助信息
function showHelp() {
console.log('为HTML文件中的JS文件引用添加版本号');
console.log('\n使用方法:');
console.log(' node add-version.js [选项] [html文件路径]');
console.log('\n选项:');
console.log(' -v, --version <版本号> 指定自定义版本号(如果不指定,默认使用时间戳)');
console.log(' -h, --help 显示帮助信息');
console.log('\n示例:');
console.log(' node add-version.js index.html # 使用时间戳版本号');
console.log(' node add-version.js -v 1.2.3 index.html # 使用自定义版本号 1.2.3');
console.log(' node add-version.js --version v2.0 ./dist/index.html # 使用自定义版本号 v2.0');
console.log(' node add-version.js -v abc123 # 使用自定义版本号 abc123,默认处理 index.html');
console.log('\n功能:');
console.log(' - 为本地JS文件添加版本号参数(?v=版本号)');
console.log(' - 跳过外部CDN链接和已有版本号的文件');
console.log(' - 自动创建备份文件(.backup后缀)');
console.log(' - 提供详细的操作日志');
}
// 如果直接运行此脚本
if (require.main === module) {
main();
}
module.exports = VersionAdder;
2.1.2 核心功能说明
VersionAdder 类的主要方法:
readHtmlFile()
- 安全读取HTML文件,包含完整的错误处理addVersionToJsFiles()
- 核心版本号添加逻辑,使用正则表达式匹配script标签saveHtmlFile()
- 保存文件并自动创建备份run()
- 执行完整的版本号添加流程
智能识别功能:
- ✅ 自动识别本地JS文件(跳过外部CDN链接)
- ✅ 处理已有版本号的情况(更新而非重复添加)
- ✅ 支持各种script标签格式
- ✅ 详细的日志输出和统计信息
安全机制:
- ✅ 自动创建备份文件(.backup后缀)
- ✅ 完整的错误处理和退出机制
- ✅ 文件存在性检查
- ✅ 路径处理(相对路径转绝对路径)
2.1.3 使用示例
处理前的HTML文件:
html
<!DOCTYPE html>
<html>
<head>
<title>示例页面</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h1>Hello World</h1>
<script src="js/main.js"></script>
<script src="js/utils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script src="js/old.js?v=123"></script>
</body>
</html>
执行命令:
bash
node add-version.js -v v1.2.3 index.html
处理后的HTML文件:
html
<!DOCTYPE html>
<html>
<head>
<title>示例页面</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h1>Hello World</h1>
<script src="js/main.js?v=v1.2.3"></script>
<script src="js/utils.js?v=v1.2.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script src="js/old.js?v=v1.2.3"></script>
</body>
</html>
控制台输出:
开始为JS文件添加版本号...
自定义版本号: v1.2.3
✓ 成功读取文件: /path/to/index.html
✓ 添加版本号: js/main.js -> js/main.js?v=v1.2.3
✓ 添加版本号: js/utils.js -> js/utils.js?v=v1.2.3
- 跳过外部链接: https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js
✓ 更新版本号: js/old.js?v=123 -> js/old.js?v=v1.2.3
✓ 创建备份文件: /path/to/index.html.backup
✓ 保存修改后的文件: /path/to/index.html
总共为 3 个JS文件添加了版本号
✓ 版本号添加完成!
2.1.4 模块化使用
除了命令行使用,还可以作为Node.js模块导入使用:
javascript
// 导入模块
const VersionAdder = require('./add-version.js');
// 创建实例
const versionAdder = new VersionAdder('index.html', 'v1.2.3');
// 执行版本号添加
versionAdder.run();
// 或者分步执行
const htmlContent = versionAdder.readHtmlFile();
const modifiedContent = versionAdder.addVersionToJsFiles(htmlContent);
versionAdder.saveHtmlFile(modifiedContent);
在构建脚本中使用:
javascript
// build.js
const VersionAdder = require('./add-version.js');
const { execSync } = require('child_process');
async function build() {
try {
// 1. 执行构建
console.log('开始构建...');
execSync('npm run build', { stdio: 'inherit' });
// 2. 添加版本号
console.log('添加版本号...');
const version = process.env.BUILD_VERSION || Date.now().toString();
const versionAdder = new VersionAdder('./dist/index.html', version);
versionAdder.run();
console.log('构建完成!');
} catch (error) {
console.error('构建失败:', error.message);
process.exit(1);
}
}
build();
2.2 使用方法
bash
# 基本用法 - 使用时间戳版本号
node add-version.js index.html
# 使用自定义版本号
node add-version.js -v 1.2.3 index.html
# 处理其他HTML文件
node add-version.js -v v2.0 ./dist/index.html
2.3 脚本功能特性
- ✅ 自动识别本地JS文件
- ✅ 跳过外部CDN链接
- ✅ 支持更新已有版本号
- ✅ 自动创建备份文件
- ✅ 详细的操作日志
- ✅ 支持自定义版本号
2.4 集成到部署流程
2.4.1 手动部署脚本
bash
#!/bin/bash
# deploy.sh
echo "开始部署流程..."
# 1. 构建项目(如果有构建步骤)
# npm run build
# 2. 为JS文件添加版本号
echo "为JS文件添加版本号..."
node add-version.js -v $(date +%Y%m%d%H%M%S) index.html
# 3. 上传到服务器
echo "上传文件到服务器..."
# rsync -avz ./dist/ user@server:/var/www/html/
echo "部署完成!"
2.4.2 自动化部署脚本
javascript
// auto-deploy.js
const { execSync } = require('child_process');
const VersionAdder = require('./add-version.js');
class AutoDeployer {
constructor(htmlFile, version) {
this.htmlFile = htmlFile;
this.version = version || Date.now().toString();
}
async deploy() {
try {
console.log('🚀 开始自动化部署...');
// 1. 添加版本号
console.log('📝 添加版本号...');
const versionAdder = new VersionAdder(this.htmlFile, this.version);
versionAdder.run();
// 2. 执行构建(如果需要)
console.log('🔨 执行构建...');
// execSync('npm run build', { stdio: 'inherit' });
// 3. 上传文件(根据实际情况修改)
console.log('📤 上传文件...');
// execSync('rsync -avz ./dist/ user@server:/var/www/html/', { stdio: 'inherit' });
console.log('✅ 部署完成!');
} catch (error) {
console.error('❌ 部署失败:', error.message);
process.exit(1);
}
}
}
// 使用示例
const deployer = new AutoDeployer('index.html', 'v1.0.0');
deployer.deploy();
2.5 优缺点
优点:
- 保持缓存优势,只有更新时才重新下载
- 不影响现有代码结构
- 可以精确控制版本号
- 适合渐进式升级
缺点:
- 需要手动或半自动执行
- 需要修改部署流程
- 版本号管理需要额外考虑
方案三:升级构建工具
3.1 升级到Gulp
3.1.1 安装Gulp
bash
# 全局安装gulp-cli
npm install -g gulp-cli
# 项目安装gulp
npm install --save-dev gulp gulp-uglify gulp-clean-css gulp-rename gulp-rev
3.1.2 Gulpfile.js配置
javascript
// gulpfile.js
const gulp = require('gulp');
const uglify = require('gulp-uglify');
const cleanCSS = require('gulp-clean-css');
const rev = require('gulp-rev');
const revReplace = require('gulp-rev-replace');
const del = require('del');
// 清理输出目录
function clean() {
return del(['dist/**/*']);
}
// 处理JS文件
function scripts() {
return gulp.src('src/js/**/*.js')
.pipe(uglify())
.pipe(rev())
.pipe(gulp.dest('dist/js'))
.pipe(rev.manifest())
.pipe(gulp.dest('dist'));
}
// 处理CSS文件
function styles() {
return gulp.src('src/css/**/*.css')
.pipe(cleanCSS())
.pipe(rev())
.pipe(gulp.dest('dist/css'))
.pipe(rev.manifest())
.pipe(gulp.dest('dist'));
}
// 处理HTML文件,替换资源引用
function html() {
const manifest = gulp.src('dist/rev-manifest.json');
return gulp.src('src/*.html')
.pipe(revReplace({ manifest: manifest }))
.pipe(gulp.dest('dist'));
}
// 复制其他文件
function copy() {
return gulp.src(['src/**/*', '!src/js/**/*', '!src/css/**/*', '!src/*.html'])
.pipe(gulp.dest('dist'));
}
// 构建任务
const build = gulp.series(clean, gulp.parallel(scripts, styles), html, copy);
// 监听文件变化
function watch() {
gulp.watch('src/js/**/*.js', scripts);
gulp.watch('src/css/**/*.css', styles);
gulp.watch('src/*.html', html);
}
exports.clean = clean;
exports.scripts = scripts;
exports.styles = styles;
exports.html = html;
exports.copy = copy;
exports.build = build;
exports.watch = watch;
exports.default = build;
3.1.3 package.json脚本
json
{
"scripts": {
"build": "gulp build",
"dev": "gulp watch",
"clean": "gulp clean"
}
}
3.2 升级到Webpack
3.2.1 安装Webpack
bash
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev html-webpack-plugin mini-css-extract-plugin
npm install --save-dev css-loader style-loader file-loader
3.2.2 webpack.config.js配置
javascript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
clean: true,
},
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash:8][ext]'
}
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
}),
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
3.3 升级到Vite(推荐)
3.3.1 安装Vite
bash
npm install --save-dev vite
3.3.2 vite.config.js配置
javascript
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
outDir: 'dist',
assetsDir: 'assets',
rollupOptions: {
output: {
// 自定义chunk文件名
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.');
const ext = info[info.length - 1];
if (/\.(css)$/.test(assetInfo.name)) {
return `css/[name]-[hash].${ext}`;
}
if (/\.(png|jpe?g|gif|svg)$/.test(assetInfo.name)) {
return `images/[name]-[hash].${ext}`;
}
return `assets/[name]-[hash].${ext}`;
},
},
},
},
server: {
port: 3000,
open: true,
},
});
3.4 优缺点
优点:
- 现代化的构建流程
- 自动处理文件版本号
- 支持代码分割和优化
- 更好的开发体验
- 长期维护成本低
缺点:
- 需要重构项目结构
- 学习成本较高
- 可能影响现有功能
方案选择建议
4.1 紧急情况
如果项目急需解决缓存问题,建议使用方案一(Nginx配置),可以立即生效。
4.2 短期解决
如果需要保持性能的同时解决缓存问题,建议使用方案二(动态版本号),配合自动化脚本。
4.3 长期规划
如果项目有长期维护计划,强烈建议使用方案三(升级构建工具),特别是升级到Vite。
最佳实践
5.1 缓存策略
nginx
# 推荐的Nginx缓存配置
location ~* \.(js|css)$ {
# 设置较长的缓存时间,但使用版本号控制
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
# 静态资源长期缓存
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.html$ {
# HTML文件不缓存
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
5.2 版本号管理
javascript
// 版本号生成策略
const version = process.env.NODE_ENV === 'production'
? require('./package.json').version
: Date.now().toString();
// 在构建时使用
const versionAdder = new VersionAdder('index.html', version);
5.3 自动化部署
yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Add version to JS files
run: node add-version.js -v ${{ github.sha }} index.html
- name: Deploy to server
run: |
# 部署命令
总结
前端老项目的缓存优化是一个系统工程,需要根据项目实际情况选择合适的方案。建议:
- 立即解决:使用Nginx配置
- 短期优化:使用动态版本号
- 长期规划:升级到现代构建工具
无论选择哪种方案,都要确保:
- 不影响现有功能
- 保持或提升性能
- 便于后续维护
- 有完整的测试验证
通过合理的缓存策略,可以显著提升用户体验,减少服务器负载,提高应用性能。