Webpack从手把手配置到原理浅析(二):高级配置

854 阅读6分钟

上一篇文章介绍了最基本的Webpack配置,可以满足一个前端项目最基本的构建和打包需求。 本文在基本配置上,增加更高级的配置,包括多入口处理、压缩CSS、公共代码和第三方库打包优化和懒加载。

多入口处理

上一节介绍的配置都是单一入口文件,最终打包产出单一HTML文件。 如果想要最终产出多个HTML文件,那么就需要配置多入口。

1. 配置多个entry

之前在基本配置里,入口配置是放在webpack.base.conf.js下:

entry: path.join(srcPath, 'index')

这里的意思是把src/index.js作为打包构建的唯一入口文件。 如果要配置多入口,此时的entry不再是一个字符串,而是一个对象,每一对键值对就是一个入口,其中的key是每个入口文件形成chunk后的唯一标识,下面配置pluginsoutput时会利用到这些标识:

entry: {
  index: path.join(srcPath, 'index.js'),
  other: path.join(srcPath, 'other.js')
}

2. 配置HtmlWebpackPlugin

上一节说到,HtmlWebpackPlugin是用来生成最终的HTML文件,所有打包后的bundle文件都会以script标签的形式存在于这个HTML文件内加载出来。 一个HtmlWebpackPlugin配置就会生成一个HTML文件,所以想要最终有多个HTML文件,就需要配置多个HtmlWebpackPlugin

  • template:模板html文件
  • filename:打包后的文件名
  • chunks:当前页面需要引用的chunks标识,默认全部引用
plugins: [
  // 多入口 - 生成 index.html
  new HtmlWebpackPlugin({
      template: path.join(srcPath, 'index.html'),
      filename: 'index.html',
      // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
      chunks: ['index']  // 只引用 index.js
  }),
  // 多入口 - 生成 other.html
  new HtmlWebpackPlugin({
      template: path.join(srcPath, 'other.html'),
      filename: 'other.html',
      chunks: ['other']  // 只引用 other.js
  })
]

3. 配置输出output

上一节的基本配置我们是这么配置output的:

output: {
  filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
  path: distPath,
 },

这样的配置会导致最终所有打包后的bundle文件都叫bundle.xxx.js文件,不好区分是哪个入口。 所以输出配置这里要利用入口文件的chunk标识,对输出的bundle文件进行命名:

output: {
  filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
  path: distPath,
},

压缩CSS

在上一节基础配置里,我们是通过css-loaderstyle-loader等将CSS文件最终处理为style样式,最终以style的方式注入到最终打包形成的JS文件。 但是这样的做法会导致最终打包出来的JS文件比较大,初次加载的时间比较长。 所以对于CSS文件,考虑有两点优化的地方:

  • 要从JS文件中抽离出来,JS文件和样式文件分别引入,加快加载速度
  • 对CSS代码进行产出优化,对它们进行压缩,减小打包后的体积

1. 使用mini-css-extract-plugin.loader解析,抽离CSS文件

我们可以使用mini-css-extract-plugin实现抽离CSS目标,只要将之前使用style-loader,改为使用MiniCssExtractPlugin.loader即可。

使用mini-css-extract-plugin好处:

  • CSS不再通过style的方式引入,打包过后的js文件也不会有css相关的内容
  • 所有的CSS都通过link标签加载,速度更快
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// ...
module: {
  rules: [
    // 抽离 css
    {
      test: /\.css$/,
      loader: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
      ]
    },
    // 抽离 less --> css
    {
      test: /\.less$/,
      loader: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader',
          'postcss-loader'
      ]
    }
  ]
},

2. 利用缓存的CSS文件

利用hash给打包后的CSS文件进行命名,利用浏览器缓存策略,命中名字的CSS文件不会重新加载,加快加载速度。

plugins: [
  new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
  new webpack.DefinePlugin({
      // window.ENV = 'production'
      ENV: JSON.stringify('production')
  }),

  // 设置打包后的CSS文件名,利用缓存
  new MiniCssExtractPlugin({
      filename: 'css/main.[contentHash:8].css'
  })
],

3.optimize-css-assets-webpack-plugin对CSS压缩

optimize-css-assets-webpack-plugin主要的压缩策略是删除CSS文件里的空格和注释。

optimization: {
  minimizer: [
    new TerserJSPlugin({}),// 压缩JS
    new OptimizeCSSAssetsPlugin({})// 压缩 css
  ],
}

抽离公共代码和第三方代码

我们在开发中,经常会抽取复用的公共代码,以及第三方库,通过import的方式引入,减少重复代码,提升开发效率。 但是在打包过程中,Webpack会解析文件的引入,会把引入的文件也打包,这样就造成了公共的代码的重复打包,给打包后的文件增加了不必要的体积。 所以对于公共代码,我们可以考虑将其分割抽离出来,形成一个个个单独的chunk,然后在入口配置时决定每个入口使用哪些chunk,这样可以减少重复打包,实现按需加载。

1. 分割代码块

通过在optimization设置splitChunks去分割代码块,可以将第三方库代码和公共代码分别分割成一个chunk。 配置要点:

  • cacheGroups里去定义分割标准,其中的key就是这个chunk的名字
  • test字段配置chunk的文件范围
  • minSize字段决定体积规则,minChunks字段决定复用次数规则,即只有超过minSize的体积和复用超过minChunks次才的代码,才会被抽离成相应的chunk
  • priority字段决定当前抽离规则的优先级,数值越大,优先级越高
optimization: {
  // 分割代码块
  splitChunks: {
    chunks: 'all',
    // 缓存分组
    cacheGroups: {
      // 第三方模块
      vendor: {
        name: 'vendor', // chunk 名称
        priority: 1, // 权限更高,优先抽离
        test: /node_modules/,
        minSize: 0,  // 大小限制
        minChunks: 1  // 最少复用过几次
      },

      // 公共的模块
      common: {
        name: 'common', // chunk 名称
        priority: 0, // 优先级
        minSize: 0,  // 公共模块的大小限制
        minChunks: 2  // 公共模块最少复用过几次
      }
    }
  }
}

2. 配置入口

在设置完代码分割规则后,需要在入口配置,决定每个入口分别使用哪些chunk,实现按需加载。

 plugins: [
  // 多入口 - 生成 index.html
  new HtmlWebpackPlugin({
    template: path.join(srcPath, 'index.html'),
    filename: 'index.html',
    // chunks 表示该页面要引用哪些 chunk,默认全部引用
    chunks: ['index', 'vendor', 'common']  // 要考虑代码分割
  }),
  // 多入口 - 生成 other.html
  new HtmlWebpackPlugin({
    template: path.join(srcPath, 'other.html'),
    filename: 'other.html',
    chunks: ['other', 'common']  // 考虑代码分割
  })
]

上面配置的chunks数组中, vendorcommon是我们在optimization.splitChunks分割的chunk,那么剩下的indexother是哪儿来的? 别忘了,我们在多入口配置里讲过,配置入口entry也会形成chunk,所以这里的indexother,就是在配置多入口时的两个chunk

总结一下会形成chunk的地方:

  • entry
  • optimization.splitChunks

懒加载

通过使用webpack的'dynamic import', 可以将打包的代码分割成不同的chunk,加快首次加载的速度。 React和Vue都提供了异步加载组件的方式,异步加载的组件不会阻塞主页面的渲染,首屏加载速度更快,所以我们可以考虑将一些次要的、较为复杂的组件通过异步的方式引入,实现'dynamic import'。

React懒加载

React可以使用React.lazy()实现懒加载:

const Chart = React.lazy(() => {
  import("./Chart");// Webpack打包之后的chunk名字就是Chart
})

Vue懒加载

Vue实现懒加载有两种方式:一是异步组件声明,二是通过路由懒加载。

异步组件声明

Vue.component('chart', () => import('./chart'));

路由懒加载

const chart = () => import('./chart');

// 路由引入
const routes = [
  {
   name: "Chart",
   path: 'chart',
   component: chart
  }
]