webpack实践指南(优化篇)

390 阅读4分钟

引言

webpack工程化的优化是十分经典且重要的问题,文章将从加快构建速度,减少打包体积两个角度介绍如何进行项目优化。

一:项目分析

进行webpack优化,首先要做的肯定是分析问题,而不是直接一顿321连招上去,如下是常用的项目分析插件。

1. 编译进度条

随着项目工程的复杂度提升,在编译构建项目时通常需要耗费几分钟,这个时候可以添加打包进度条为我们提供更好的视觉体验。

安装

npm i -D progress-bar-webpack-plugin

使用

webpack.common.js

const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin')
const config = {
    plugins:[
        new ProgressBarWebpackPlugin()
    ]
}

2. 编译速度分析

编译速度分析插件可以在打包构建时列出每个loader耗费的时间,开发者可以对耗时较长的loader进行针对性优化。

webpack.dev.js

安装

 npm i -D speed-measure-webpack-plugin

使用

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = smp.wrap({
  mode: 'development'
})

3. 打包体积分析

打包体积插件通过可视化的方式展示每个bundle具体的体积大小和所占的空间比例,开发者可对较大体积的bundle进行优化。

webpack.prod.js

安装

 npm i -D webpack-bundle-analyzer

使用

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const config = {
  mode: 'development',
  plugins:[
      new BundleAnalyzerPlugin()
  ]
}

image.png

二:提升构建速度

在开发阶段如果项目比较复杂,通常构建等待时间会比较长,这势必会影响开发效率,当然掌握了构建速度分析,你也可以悄悄的反向优化构建速度,提高摸鱼质量。

1. 升级webpack

webpack的每次升级都是对上一版本的优化,通常新版的构建效率都会被提高一个档次。

2. 添加cache缓存

添加cache缓存,在webpack第二次构建时可以大幅提高构建速度。

module.exports = { 
    cache: { 
        type: 'filesystem', // 使用文件缓存
    }, 
}

3. 使用include资源匹配范围

    module.exports = {
    rules: [
        {
            test: /\.(js|ts|jsx|tsx)$/,
            include: paths.appSrc,
            use: [
              {
                loader: 'esbuild-loader',
                options: {
                  loader: 'tsx',
                  target: 'es2015',
                },
              }
            ]
         }
    ]
}




4.优化resolve

通过对resolve的alias,extensions,modules进行设置,加快文件匹配检索

 module.exports = {
     resolve: {
        extensions: ['.tsx', '.ts', '.js'],
        alias:{
          '@':rootPath, // @ 代表 src 路径
        },
        modules: [ 'node_modules', paths.appSrc, ]
  },
 }
 
 

5.多进程打包

安装

npm install --save-dev thread-loader

使用

```
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        include: path.resolve('src'),
        use: [
          "thread-loader",
          // 耗时的 loader (例如 babel-loader)
        ],
      },
    ],
  },
};
```

6.代码映射

开发阶段通常使用eval-cheap-module-source-map

export.module = { 
    devtool: 'eval-cheap-module-source-map',
}

三:项目体积优化

1.js压缩

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
              parallel: 4,
              terserOptions: {
                parse: {
                  ecma: 8,
                },
                compress: {
                  ecma: 5,
                  warnings: false,
                  comparisons: false,
                  inline: 2,
                },
                mangle: {
                  safari10: true,
                },
                output: {
                  ecma: 5,
                  comments: false,
                  ascii_only: true,
                },
              },
            }),
        ]
    }
}

2.css压缩

安装

npm install -D css-minimizer-webpack-plugin

使用

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
    optimization: {
        minimizer: [
            new CssMinimizerPlugin()
        ]
    }
}

3.js tree-shaking

webpack默认在production模式下开启了tree-shaking,但是通常我们需要自定义哪些文件不被tree-shaking掉。

例子1

 //tool.js
 export function add(a,b) {
     return a+b;
 }
 export function del(a,b){
     return a-b;
 }
 
 //index.js
 import {add,del} from "./tool.js"
 console.log(add(1,2));
 

上面的例子在打包index.js时del方法会被过滤掉。

例子2

import "./styles.css"
import "./tool.js"

上面两种导入方式在webpack打包时都会被过滤掉,但是通常在项目中经常使用这种导入css的方式,因此我们必须自定义配置哪些文件不会被tree-shaing掉。

设置方式

修改package.json

第一种方式表示所有的模块都放心的执行tree-shaking规则(导入没有使用就过滤),第二种方式表示当前数组匹配的文件不会被过滤掉。这也就解决了css直接导入被过滤的问题了。

"sideEffects": false
"sideEffects": ["*.css"]

4.css tree-shaking

安装

npm i purgecss-webpack-plugin -D

使用

const PurgeCSSPlugin = require('purgecss-webpack-plugin');
const config = {
    plugins:[
        new PurgeCSSPlugin({//css tree-shaking
          paths: glob.sync(`${rootPath}/**/*`,  { nodir: true }),
        }),
    ]
}

5. css代码分离

在打包时通常css体积比较大,因此需要将项目中所有的css单独抽离打包

安装

  npm install -D mini-css-extract-plugin
  

使用

webpack.common.js

      const MiniCssExtractPlugin = require("mini-css-extract-plugin");
      plugins:[
          new MiniCssExtractPlugin({
              filename: "[name].css",
              chunkFilename: "[id].css",
          })
      ]
      module:{
          rules:[
              {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader, "css-loader"],
                include: rootPath,
              }, 
          ]
      }
 

7. cdn加载

对于第三方模块,例如lodash,jquery我们可以通过cdn并行加载,大幅减少打包体积。通过cdn加载的模块不会打包到最终bundle中。

  1. html引入cdn

 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>webpack You hua</title>
   <script src="https://code.jquery.com/jquery-3.1.0.js"></script>
 </head>
 <body>

 </body>
 </html>

  1. 使用externals

    externals: {//将jQuery通过cdn引入,不会打包到bundle文件中
       jquery: 'jQuery',
    },
    
  2. 使用

      import $ from "jquery"
    

8. 删除第三方文件多余依赖

例如moment.js插件,其包体积包含需要无用的语言包,我们可以借助第三方插件删除无用依赖

  const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
  plugins:[
      new MomentLocalesPlugin({//momnet删除无用的语言包
          localesToKeep: ['zh-cn'],
        }),
  ]

9. 第三方工具的筛选

  1. 优先选择支持esm模块的工具,esm模块支持tree-shaking
  2. 优先选择体积小,且稳定的工具。例如尽量选择day.js而不是moment.js

10. splitChunks分包处理

module.exports = {
    optimization:{
         splitChunks: {
          // include all types of chunks
          chunks: 'all',
          // 重复打包问题
          cacheGroups:{
            vendors:{ // node_modules里的代码
              test: /[\\/]node_modules[\\/]/,
              chunks: "all",
              // name: 'vendors', 一定不要定义固定的name
              priority: 10, // 优先级
              enforce: true 
            }
          }
        },
    }

}