为什么要使用缓存呢?单就功能来讲,不用缓存也能达到同样的效果,使用缓存的目的其实是为了 提高性能
为此我们必须要确保 webpack 编译生成的文件能被客户端缓存,并且当文件发生变化后,客户端能得到新的文件
1、哈希命名
下面,我们先来搭建一个小小的项目,一步一步说明如何在 webpack 中配置使用缓存
创建一个空文件夹 Demo
,作为项目的根目录,然后在该目录中运行以下命令安装依赖
> # 创建 package.json 文件 > npm init -y > # 安装 webpack > npm install --save-dev webpack > npm install --save-dev webpack-cli > # 安装 lodash 模块 > npm install --save lodash
在根目录下创建 src
和 dist
文件夹,分别用于存放资源文件和打包之后的输出文件
并在 src
文件夹下创建 index.js
文件,该文件的作用是添加一个 div
元素作为 body
的子节点
import _ from 'lodash'; function component() { var element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } let elem = component(); document.body.appendChild(elem);
之后,我们在根目录下创建一个 webpack.config.js
文件,用于指定 webpack 的一些配置
通过 hash,我们可以使得每次打包生成的文件命名都是唯一的
const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') } };
2、使用 HtmlWebpackPlugin 插件
这里有一个问题,如果每次打包之后生成的文件命名都不一样,那么我们要怎么引用文件呢?
答案是使用 HtmlWebpackPlugin,该插件会自动生成一个 HTML5 文件,动态添加每次编译后引用的资源
首先安装插件
> npm install --save-dev html-webpack-plugin
然后在 webpack.config.js
文件中进行配置
const path = require('path'); // 引入 HtmlWebpackPlugin 插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') }, plugins: [ // 使用 HtmlWebpackPlugin 插件 new HtmlWebpackPlugin() ] };
3、使用 CleanWebpackPlugin 插件
另外一个问题,如果经过多次修改和打包,由于之前打包生成的文件也会残留下来,所以文件会越堆越多
要怎么解决呢?答案是使用 CleanWebpackPlugin,该插件可以在重新打包时,把没有用的文件自动清除
还是先安装插件
> npm install --save-dev clean-webpack-plugin
然后在 webpack.config.js
文件中进行配置
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入 CleanWebpackPlugin 插件 const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') }, plugins: [ new HtmlWebpackPlugin(), // 使用 CleanWebpackPlugin 插件 new CleanWebpackPlugin() ] };
4、提取模板
有的时候,将第三方库提取到独立的 chunk 文件是比较推荐的做法
因为它们很少被修改,所以利用客户端的长效缓存机制,可以最大限度减少客户端向服务器请求资源的次数
修改 webpack.config.js
文件如下
const path = require('path'); // 引入 webpack 内置插件 const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { entry: { main: './src/index.js', // 新增 vender 入口,指定第三方库 vender: [ 'lodash' ] }, output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') }, plugins: [ // 将指定的模块分离到单独的文件 new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new HtmlWebpackPlugin(), new CleanWebpackPlugin() ] };
5、使用 NamedModulesPlugin / HashedModuleIdsPlugin 插件
至此,还有最后一个问题,假如我们在 src
目录下添加一个文件 hello.js
,文件内容如下
export function SayHello() { console.log('Hello World') }
然后,我们在 src
目录下的 index.js
文件中引用新建的文件,修改 index.js
文件如下
import _ from 'lodash'; import { SayHello } from './hello.js'; function component() { SayHello() var element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } let elem = component(); document.body.appendChild(elem);
但是当我们重新打包的时候,却发现之前打包 vender 生成的 bundle 文件的 hash 也发生了改变
这是因为 module.id
会基于默认的解析顺序进行增量,所以 vender 生成的 bundle 文件的 hash 也会因此变化
对于这种情况,webpack 提供两种解决方法
一是使用 NamedModulesPlugin
(适合开发环境使,二是使用 HashedModuleIdsPlugin
(适合生产环境)
修改 webpack.config.js
文件如下
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { entry: { main: './src/index.js', vender: [ 'lodash' ] }, output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), // 使用 HashedModuleIdsPlugin 插件 new webpack.HashedModuleIdsPlugin(), new HtmlWebpackPlugin(), new CleanWebpackPlugin() ] };
【 更新:webpack.optimize.CommonsChunkPlugin 已弃用,请使用 config.optimization.splitChunks 】
对于报错信息,不针对上面的例子,下面是一个比较通用的写法
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: '[name].[chunkhash].js', path: path.resolve(__dirname, 'dist') }, plugins: [ new webpack.HashedModuleIdsPlugin(), new HtmlWebpackPlugin(), new CleanWebpackPlugin() ], optimization: { splitChunks: { chunks: 'all', // 可选值有三个,initial(入口)、async(异步)和 all minSize: 30000, // 最小尺寸 minChunks: 1, // 最小引用次数 maxAsyncRequests: 5, // 最大请求异步 chunk 的次数 maxInitialRequests: 3, // 最大请求入口 chunk 的次数 cacheGroups: { vendor: { // 提取第三方库 test: /[\\/]node_modules[\\/]/, // 根据需求修改 name: 'vendor', }, common: { // 提取公共文件 test: /[\\/]src[\\/]js[\\/]/, // 根据需求修改 name: 'common' } } } } };