Skip to content
On this page

Webpack 缓存策略

缓存策略是 Webpack 性能优化的重要组成部分,合理的缓存配置可以显著提升构建速度和用户体验。本章将详细介绍各种缓存策略的配置和使用。

构建缓存

文件系统缓存

javascript
// Webpack 5 的文件系统缓存
module.exports = {
  cache: {
    type: 'filesystem',                    // 启用文件系统缓存
    buildDependencies: {
      config: [__filename]                 // 配置文件变更时清除缓存
    },
    cacheDirectory: path.resolve(__dirname, '.webpack-cache'), // 缓存目录
    name: 'production',                    // 缓存名称,用于区分不同环境
    version: '1.0.0'                      // 缓存版本,变更时清除缓存
  }
};

// 开发环境配置
module.exports = {
  mode: 'development',
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    name: 'development'
  }
};

缓存优化配置

javascript
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    // 缓存压缩
    compression: 'brotli',                 // 'gzip' 或 'brotli'
    
    // 缓存限制
    maxMemoryGenerations: 10,              // 内存中保留的最大生成次数
    maxAge: 1000 * 60 * 60 * 24 * 30      // 缓存有效期(30天)
  },
  
  // 快照配置
  snapshot: {
    managedPaths: [                        // Webpack 监控的路径
      path.resolve(__dirname, 'node_modules')
    ],
    immutablePaths: [                      // 不变路径
      path.resolve(__dirname, 'immutable-deps')
    ],
    resolveBuildDependencies: {
      hash: true,                          // 启用构建依赖哈希
      timestamp: true                      // 启用时间戳
    },
    resolve: {
      hash: true,
      timestamp: true
    },
    module: {
      hash: true,
      timestamp: true
    }
  }
};

模块标识符优化

确定性模块 ID

javascript
module.exports = {
  optimization: {
    moduleIds: 'deterministic',            // 确定性模块 ID
    chunkIds: 'deterministic',             // 确定性 chunk ID
    runtimeChunk: 'single'                 // 单独的运行时 chunk
  }
};

// 不同的模块 ID 策略
module.exports = {
  optimization: {
    // moduleIds: 'named',                 // 开发环境推荐
    // moduleIds: 'size',                  // 按大小优化
    // moduleIds: 'total-size',            // 按总大小优化
    moduleIds: 'deterministic'             // 生产环境推荐
  }
};

长期缓存优化

javascript
module.exports = {
  optimization: {
    moduleIds: 'deterministic',
    chunkIds: 'deterministic',
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
          chunks: 'all'
        }
      }
    }
  },
  output: {
    filename: '[name].[contenthash].js',   // 使用内容哈希
    chunkFilename: '[name].[contenthash].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  }
};

资源缓存

HTTP 缓存配置

javascript
// 输出配置支持长期缓存
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js', // 固定长度的哈希
    chunkFilename: '[name].[contenthash:8].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
    clean: true
  },
  
  // 设置资源提示
  plugins: [
    new webpack.HashedModuleIdsPlugin()   // Webpack 4 使用
  ]
};

服务端缓存配置

javascript
// 结合服务器配置实现最佳缓存
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: 'https://cdn.example.com/assets/'
  },
  
  plugins: [
    // 生成资源映射
    new WebpackManifestPlugin({
      fileName: 'manifest.json'
    }),
    
    // 生成子资源完整性 (SRI)
    new SriPlugin({
      hashFuncNames: ['sha256', 'sha384']
    })
  ]
};

开发环境缓存

开发服务器缓存

javascript
module.exports = {
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
      watch: {
        ignored: [
          path.join(__dirname, 'node_modules'),
          path.join(__dirname, '.git'),
          path.join(__dirname, 'dist')
        ]
      }
    },
    hot: true,                            // 启用热模块替换
    client: {
      progress: true,                     // 显示进度
      overlay: {
        errors: true,                     // 显示错误覆盖
        warnings: false                   // 不显示警告覆盖
      }
    }
  },
  
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    name: 'dev-server'
  }
};

开发构建缓存

javascript
// 开发环境构建缓存配置
module.exports = {
  mode: 'development',
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    name: 'development-build',
    version: process.env.NODE_ENV + (process.env.ASSET_VERSION || '')
  },
  
  optimization: {
    moduleIds: 'named',                   // 开发环境使用命名 ID 便于调试
    chunkIds: 'named'
  },
  
  devtool: 'eval-cheap-module-source-map' // 快速源映射
};

Babel 缓存

Babel 缓存配置

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,          // 启用 Babel 缓存
            cacheCompression: false,       // 禁用缓存压缩(加快读取)
            compact: true                  // 压缩输出
          }
        },
        exclude: /node_modules/
      }
    ]
  }
};

// 高级 Babel 缓存配置
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: path.resolve(__dirname, '.babel-cache'),
            cacheIdentifier: JSON.stringify({
              env: process.env.NODE_ENV,
              babel: require('@babel/core/package.json').version
            }),
            cacheCompression: false
          }
        },
        exclude: /node_modules/
      }
    ]
  }
};

TypeScript 缓存

TypeScript 缓存配置

javascript
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,         // 只转译,不进行类型检查
              happyPackMode: true,         // 启用 happypack 模式
              compilerOptions: {
                noEmit: false
              }
            }
          }
        ],
        exclude: /node_modules/
      }
    ]
  },
  
  plugins: [
    // 使用 ForkTsCheckerWebpackPlugin 进行类型检查
    new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: path.resolve(__dirname, 'tsconfig.json'),
        build: true,                      // 启用增量编译
        mode: 'write-references'          // 写入引用文件以加速类型检查
      }
    })
  ]
};

缓存失效策略

智能缓存失效

javascript
// 自定义缓存失效逻辑
class CustomCachePlugin {
  apply(compiler) {
    compiler.hooks.beforeCompile.tapPromise('CustomCachePlugin', async (params) => {
      // 检查是否有需要清除缓存的条件
      const shouldClearCache = await this.checkCacheInvalidation();
      
      if (shouldClearCache) {
        await this.clearCache();
      }
    });
  }
  
  async checkCacheInvalidation() {
    // 检查特定条件来决定是否清除缓存
    // 例如:检查依赖版本、环境变量等
    return process.env.CLEAR_CACHE === 'true';
  }
  
  async clearCache() {
    // 清除缓存的逻辑
    const cacheDir = path.resolve(__dirname, '.webpack-cache');
    if (fs.existsSync(cacheDir)) {
      await fs.promises.rm(cacheDir, { recursive: true, force: true });
    }
  }
}

module.exports = {
  plugins: [
    new CustomCachePlugin()
  ]
};

环境感知缓存

javascript
// 根据环境使用不同的缓存策略
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';

const cacheConfig = isDevelopment 
  ? {
      type: 'filesystem',
      buildDependencies: { config: [__filename] },
      name: 'development',
      version: `${process.env.NODE_ENV}-${Date.now()}`
    }
  : {
      type: 'filesystem',
      buildDependencies: { config: [__filename] },
      name: 'production',
      version: process.env.BUILD_VERSION || '1.0.0',
      compression: 'brotli'
    };

module.exports = {
  cache: cacheConfig,
  optimization: {
    moduleIds: isProduction ? 'deterministic' : 'named',
    chunkIds: isProduction ? 'deterministic' : 'named'
  }
};

缓存监控和分析

缓存性能监控

javascript
class CacheStatsPlugin {
  apply(compiler) {
    let startTime = 0;
    
    compiler.hooks.beforeCompile.tap('CacheStatsPlugin', () => {
      startTime = Date.now();
    });
    
    compiler.hooks.done.tap('CacheStatsPlugin', (stats) => {
      const buildTime = Date.now() - startTime;
      const cacheHits = stats.compilation.fileDependencies.size;
      
      console.log(`Build time: ${buildTime}ms`);
      console.log(`Cache hits: ${cacheHits}`);
      
      // 输出缓存统计信息
      if (compiler.cache && compiler.cache.hooks) {
        // Webpack 5 缓存统计
        console.log('Using filesystem cache');
      }
    });
  }
}

module.exports = {
  plugins: [
    new CacheStatsPlugin()
  ]
};

缓存大小管理

javascript
// 管理缓存大小
const fs = require('fs').promises;
const path = require('path');

class CacheSizePlugin {
  constructor(options = {}) {
    this.cacheDir = options.cacheDir || '.webpack-cache';
    this.maxSize = options.maxSize || 500 * 1024 * 1024; // 500MB
  }
  
  async apply(compiler) {
    compiler.hooks.done.tapPromise('CacheSizePlugin', async () => {
      await this.manageCacheSize();
    });
  }
  
  async manageCacheSize() {
    const cachePath = path.resolve(this.cacheDir);
    
    try {
      const stats = await fs.stat(cachePath);
      if (stats.size > this.maxSize) {
        // 清理旧缓存或执行其他管理操作
        console.log(`Cache size (${stats.size}) exceeds limit (${this.maxSize})`);
        // 可以在这里实现缓存清理逻辑
      }
    } catch (error) {
      // 缓存目录不存在
    }
  }
}

高级缓存策略

多级缓存

javascript
// 实现多级缓存策略
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    name: 'primary-cache',
    store: 'pack',                        // 'pack' 或 'json'
    compression: 'brotli'
  },
  
  // 结合其他缓存策略
  optimization: {
    moduleIds: 'deterministic',
    chunkIds: 'deterministic',
    sideEffects: false,
    usedExports: true
  }
};

CI/CD 缓存集成

javascript
// CI/CD 环境下的缓存配置
const isCI = process.env.CI === 'true' || process.env.CONTINUOUS_INTEGRATION === 'true';

module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    name: isCI ? 'ci-cache' : 'local-cache',
    version: process.env.GIT_COMMIT || 'local',
    store: 'pack'
  },
  
  // CI 环境可能需要不同的优化策略
  optimization: isCI ? {
    minimize: false,                      // CI 环境可能不需要压缩
    moduleIds: 'named',
    chunkIds: 'named'
  } : {
    minimize: true,
    moduleIds: 'deterministic',
    chunkIds: 'deterministic'
  }
};

缓存配置最佳实践

完整的缓存配置示例

javascript
const path = require('path');

module.exports = {
  mode: process.env.NODE_ENV,
  
  // 文件系统缓存
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    },
    name: process.env.NODE_ENV,
    version: process.env.BUILD_VERSION || '1.0.0',
    cacheDirectory: path.resolve(__dirname, '.webpack-cache'),
    maxMemoryGenerations: 10,
    maxAge: 1000 * 60 * 60 * 24 * 7      // 7天
  },
  
  // 优化配置
  optimization: {
    moduleIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named',
    chunkIds: process.env.NODE_ENV === 'production' ? 'deterministic' : 'named',
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
          chunks: 'all'
        }
      }
    }
  },
  
  // 输出配置(支持长期缓存)
  output: {
    filename: process.env.NODE_ENV === 'production' 
      ? '[name].[contenthash:8].js' 
      : '[name].js',
    chunkFilename: process.env.NODE_ENV === 'production'
      ? '[name].[contenthash:8].chunk.js'
      : '[name].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
    clean: true
  },
  
  // 模块配置
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            cacheCompression: false
          }
        },
        exclude: /node_modules/
      }
    ]
  },
  
  // 快照配置
  snapshot: {
    managedPaths: [path.resolve(__dirname, 'node_modules')],
    immutablePaths: [path.resolve(__dirname, 'immutable-deps')]
  }
};

小结

Webpack 缓存策略是提升构建性能的关键。通过合理配置文件系统缓存、模块标识符、资源缓存等,可以显著减少构建时间。在生产环境中,使用确定性模块 ID 和内容哈希可以实现长期缓存,提升用户体验。选择合适的缓存策略需要根据项目特点和部署环境来决定。