在使用 webpack 进行前端打包时,经常会遇到缓存问题。其中一个常见问题就是在 js 文件名后面出现 ?v=xxxxxxx 的情况,例如 chunk.js?v=1234567。这种情况通常意味着浏览器无法识别文件是否已更改,从而无法正确获取最新的文件版本。因此,本文将介绍这种问题的根本原因,并提供几种可能的解决方法。
问题根源
在 webpack 打包时,为了减少文件下载时间和减小文件体积,它会将多个 JS 文件打包成一个或多个 bundle。这些 bundle 文件的名字是由 webpack 自动生成的哈希值,而这个哈希只随着文件内容的改变而改变。同时,webpack 又会生成一个名为 manifest.json 的文件,记录每个 bundle 的哈希值。因此,当打包后我们访问页面时,浏览器会下载这些 bundle 文件和 manifest.json 文件,然后根据 manifest.json 文件的内容去匹配对应的 bundle,从而正确加载我们的页面。
然而,有些情况下我们打包出来的文件名并不带哈希值,而是带有一个类似 ?v=xxxxxxx 的查询字符串。这个字符串实际上是被添加到文件路径后面的,而 webpack 默认并不会为文件添加这个字符串。所以,这个字符串的产生肯定是由某些别的操作引起的。
解决方法
方法一:手动指定 output.filename
最简单的解决方法是手动指定 webpack 的 output.filename 配置,将哈希值添加到文件名后面。例如:
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js', // 将哈希值添加到文件名后面
},
// ... 其他配置 ...
};这样做的好处是,我们可以很方便地自定义文件名,同时还可以避免截取文件名来解析哈希值的麻烦。
方法二:使用 HtmlWebpackPlugin 插件
Webpack 也有一个叫做 HtmlWebpackPlugin 的插件,它可以自动生成 index.html 文件,并自动在其中引用打包后的 JS 和 CSS 文件。使用这个插件时,我们可以通过设置其 hash 选项来在文件路径后添加哈希值,例如:
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
hash: true, // 将哈希值添加到文件路径后
}),这样在打包后,生成的 index.html 文件中就会自动添加查询字符串,例如:
<script type="text/javascript" src="main.b383b0a1963b7e3fec75.js?v=7f3131a4c4c4f4a4"></script>
方法三:使用 AssetsPlugin 插件
另外一个可选的插件是 AssetsPlugin,它可以在打包结束后自动构建明文的资源清单,以便于维护打包后的文件。使用这个插件时,我们可以通过它的配置项来控制是否在文件名中添加哈希值,例如:
new AssetsPlugin({
path: path.resolve(__dirname, 'dist'),
filename: 'assets-manifest.json',
fullPath: false, // 不要使用完整路径(避免跨平台问题)
hash: true, // 如果需要,则将哈希值添加到文件名后
}),这样,在打包后生成的 assets-manifest.json 文件中,我们就可以很方便地查看每个文件的路径和文件名,例如:
{
"main.js": "main.b383b0a1963b7e3fec75.js?v=7f3131a4c4c4f4a4",
"vendor.js": "vendor.711dec5b5df33f7761c8.js?v=7f3131a4c4c4f4a4",
"styles.css": "styles.e04ecb2858e818dd61ec.css?v=7f3131a4c4c4f4a4"
}方法四:手动编写一个插件
最后我们也可以编写一个自己的插件,来在打包时为文件名添加哈希值。这个插件代码较为复杂,但它也是最灵活的解决方案。其具体实现可参考以下示例代码:
// www.javascriptcn.com code example
class AppendHashToFilenamePlugin {
constructor(options = {}) {
this.options = Object.assign({
files: [''],
query: 'v',
hashLength: 8,
}, options);
}
apply(compiler) {
compiler.hooks.emit.tapAsync('AppendHashToFilenamePlugin', (compilation, callback) => {
const { hashLength, query } = this.options;
const { basedir } = compiler.options;
compilation.chunks.forEach(chunk => {
chunk.files.forEach(filename => {
const ext = path.extname(filename);
const name = path.basename(filename, ext);
let hash = compilation.hash.slice(0, hashLength);
// 如果用户设置了 manifest.json 文件,则从中获取哈希值,否则使用默认值
if (chunk.name) {
const manifestPath = path.join(basedir, `${chunk.name}.manifest.json`);
if (fs.existsSync(manifestPath)) {
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
hash = manifest.hash;
}
}
// 给文件名添加哈希
let newFilename = `${name}.${hash}${ext}`;
if (query) {
newFilename += `?${query}=${hash}`;
}
// 重新命名文件
const oldPath = path.join(basedir, filename);
const newPath = path.join(basedir, newFilename);
fs.renameSync(oldPath, newPath);
console.log(`Renamed ${filename} to ${newFilename}`);
});
});
callback();
});
}
}
module.exports = AppendHashToFilenamePlugin;在使用它时,我们只需要将它加入到 webpack 的插件数组中即可:
// www.javascriptcn.com code example
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js', // 不要在这里手动添加哈希值
},
plugins: [
new AppendHashToFilenamePlugin({
query: 'v', // 将哈希值添加到文件名后面
}),
// ... 其他插件 ...
],
// ... 其他配置 ...
};结语
在前端开发中,我们经常需要使用 webpack 进行打包。然而,由于浏览器缓存的存在,打包后的代码很容易出现缓存问题。本文介绍了一个常见的缓存问题 —— 在文件名后面添加查询字符串 ?v=xxxxxxx,并提供了几种可能的解决方法。希望能够帮助到有类似问题的读者。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d64abca941bf7134bfe923