cache-loader实现构建缓存
cache-loader 的基本原理
cache-loader 是 Webpack 生态中一个专门用于缓存构建中间结果的 loader。它的工作原理相当直接:在构建过程中,它会将经过 loader 处理后的模块内容保存到本地文件系统,当下次构建时如果发现源文件没有变化,就直接从缓存中读取结果,跳过耗时的 loader 处理过程。
这个 loader 通常被放置在 loader 数组的最前面,这样它就能拦截后续 loader 的处理请求。它的典型工作流程如下:
- 计算当前模块的缓存标识(通常使用文件路径和修改时间)
- 检查缓存中是否存在有效结果
- 如果存在有效缓存,直接返回缓存结果
- 如果没有缓存或缓存失效,将请求传递给后续 loader
- 将最终处理结果写入缓存
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
'babel-loader'
]
}
]
}
}
安装与基础配置
要使用 cache-loader,首先需要通过 npm 或 yarn 进行安装:
npm install --save-dev cache-loader
# 或
yarn add --dev cache-loader
基础配置非常简单,只需要在你想缓存的 loader 前面添加 cache-loader 即可。以下是一个典型配置示例:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('.cache'),
cacheIdentifier: 'v1'
}
},
'babel-loader'
],
include: path.resolve('src')
}
]
}
}
在这个配置中,我们指定了缓存目录为项目根目录下的 .cache
文件夹,并设置了缓存标识符为 v1
。当修改 babel 配置或升级 babel 版本时,可以修改这个标识符来强制缓存失效。
高级配置选项
cache-loader 提供了多个配置选项来满足不同场景的需求:
cacheDirectory
指定缓存文件的存储位置。默认是 path.resolve('.cache-loader')
。建议将其设置为项目目录下的某个路径:
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve(__dirname, '.cache/loader')
}
}
cacheIdentifier
一个字符串标识符,当这个标识符改变时,所有缓存都会失效。这非常适合在升级 loader 或修改配置时使用:
{
loader: 'cache-loader',
options: {
cacheIdentifier: `babel-config-${JSON.stringify(babelConfig)}-${babelVersion}`
}
}
cacheKey
自定义缓存键的生成函数,可以基于更多因素来决定是否使用缓存:
{
loader: 'cache-loader',
options: {
cacheKey: (options, request) => {
return `${options.cacheIdentifier}|${request.path}|${fs.statSync(request.path).mtimeMs}`
}
}
}
read/write
自定义缓存读写逻辑的函数:
{
loader: 'cache-loader',
options: {
read: (cacheKey, callback) => {
customCacheStore.get(cacheKey, callback)
},
write: (cacheKey, data, callback) => {
customCacheStore.set(cacheKey, data, callback)
}
}
}
性能优化实践
选择性缓存
不是所有文件都适合缓存。通常,我们只缓存那些处理耗时较长的文件:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('.cache/js')
}
},
'babel-loader'
],
include: path.resolve('src')
},
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('.cache/css')
}
},
'css-loader',
'sass-loader'
]
}
]
}
}
多级缓存
对于大型项目,可以结合 memory-fs 实现内存+磁盘的多级缓存:
const MemoryFS = require('memory-fs')
const mfs = new MemoryFS()
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: {
read: (key, callback) => {
if (mfs.existsSync(key)) {
return callback(null, mfs.readFileSync(key))
}
fs.readFile(key, callback)
},
write: (key, data, callback) => {
mfs.writeFileSync(key, data)
fs.writeFile(key, data, callback)
}
}
},
'babel-loader'
]
}
]
}
}
并行处理
结合 thread-loader 实现并行处理:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
{
loader: 'thread-loader',
options: {
workers: 4
}
},
'babel-loader'
]
}
]
}
}
常见问题与解决方案
缓存失效问题
当遇到缓存不更新的情况时,可以从以下几个方面排查:
- 检查文件修改时间是否更新
- 确认 cacheIdentifier 是否正确反映了配置变更
- 检查缓存目录权限是否正确
// 调试缓存键生成
{
loader: 'cache-loader',
options: {
cacheKey: (options, request) => {
const key = customCacheKey(options, request)
console.log('Cache key:', key)
return key
}
}
}
缓存污染
当多个项目使用相同缓存目录时可能出现问题。解决方案:
- 为每个项目设置独立的缓存目录
- 在 cacheIdentifier 中加入项目特定信息
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve(`.cache/${process.env.NODE_ENV || 'development'}`),
cacheIdentifier: `${packageJson.name}-${packageJson.version}`
}
}
构建结果不一致
有时缓存可能导致构建结果不一致,可以通过以下方式解决:
// 在CI环境中禁用缓存
{
loader: process.env.CI ? 'babel-loader' : 'cache-loader'
}
// 或者强制清除缓存
if (process.env.CLEAR_CACHE) {
require('rimraf').sync(path.resolve('.cache'))
}
与其他工具集成
与 HardSourceWebpackPlugin 配合
HardSourceWebpackPlugin 提供了模块级别的缓存,可以与 cache-loader 互补:
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
plugins: [
new HardSourceWebpackPlugin({
cacheDirectory: path.resolve('.cache/hard-source/[confighash]')
})
],
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
'babel-loader'
]
}
]
}
}
与 DllPlugin 结合使用
对于稳定的第三方库,使用 DllPlugin 可以获得更好的效果:
// webpack.dll.config.js
module.exports = {
entry: {
vendor: ['react', 'react-dom', 'lodash']
},
output: {
path: path.resolve('.cache/dll'),
filename: '[name].dll.js',
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
path: path.join('.cache/dll', '[name]-manifest.json'),
name: '[name]_[hash]'
})
]
}
// webpack.config.js
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./.cache/dll/vendor-manifest.json')
})
],
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
'babel-loader'
],
exclude: /node_modules/
}
]
}
}
缓存策略进阶
基于内容的缓存
默认情况下,cache-loader 基于文件修改时间判断缓存有效性。可以改为基于文件内容:
const crypto = require('crypto')
{
loader: 'cache-loader',
options: {
cacheKey: (options, request) => {
const fileContent = fs.readFileSync(request.path)
const contentHash = crypto.createHash('md5').update(fileContent).digest('hex')
return `${options.cacheIdentifier}|${request.path}|${contentHash}`
}
}
}
分布式缓存
在团队开发中,可以共享缓存:
const AWS = require('aws-sdk')
const s3 = new AWS.S3()
{
loader: 'cache-loader',
options: {
read: (key, callback) => {
s3.getObject({ Bucket: 'our-cache-bucket', Key: key }, (err, data) => {
if (err && err.code === 'NoSuchKey') {
return fs.readFile(key, callback)
}
callback(err, err ? null : data.Body)
})
},
write: (key, data, callback) => {
fs.writeFile(key, data, (err) => {
if (err) return callback(err)
s3.putObject({ Bucket: 'our-cache-bucket', Key: key, Body: data }, callback)
})
}
}
}
监控与调优
缓存命中率统计
可以通过自定义 read/write 函数来统计缓存命中率:
let hits = 0
let misses = 0
{
loader: 'cache-loader',
options: {
read: (key, callback) => {
fs.readFile(key, (err, data) => {
if (err) {
misses++
return callback(err)
}
hits++
callback(null, data)
})
},
write: (key, data, callback) => {
fs.writeFile(key, data, callback)
}
}
}
// 在编译结束后打印统计信息
compiler.hooks.done.tap('CacheStatsPlugin', () => {
console.log(`Cache hit rate: ${hits / (hits + misses) * 100}%`)
})
缓存大小控制
实现自动清理旧缓存的机制:
const MAX_CACHE_SIZE = 100 * 1024 * 1024 // 100MB
{
loader: 'cache-loader',
options: {
write: (key, data, callback) => {
fs.writeFile(key, data, (err) => {
if (err) return callback(err)
// 检查缓存目录大小
getDirectorySize('.cache', (size) => {
if (size > MAX_CACHE_SIZE) {
cleanupOldCacheFiles('.cache', MAX_CACHE_SIZE / 2)
}
callback()
})
})
}
}
}
function getDirectorySize(dir, callback) {
// 实现获取目录大小的逻辑
}
function cleanupOldCacheFiles(dir, targetSize) {
// 实现清理旧文件的逻辑
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:自定义Loader开发指南