从4个方面重新梳理webpack

2023/10/30

我们始终被外在的东西所折磨,但只要活在当下,专注此时此刻做的每一件事情,把它做到极致,则就是那滴蜜糖。抓住这一点,我们就不再焦虑。

# 背景

本文内容来自于小册webpack5核心原理与应用实现 (opens new window), 总结一下对自己有帮助的点,这本小册总体写的不错,比较大的收获有:

  1. 对webpack的配置文件的理解
  2. js、css、图片 常用loader配置以及比较
  3. webpack优化方案
  4. webpack原理(后续单独总结)

写文章不易,写了几天,辛苦大家多多支持和指正~

# 一、重新理解webpack配置项

由于webpack的配置项众多,之前在脑海里都是一个一个的点,根本没有串起来,而且也记不住,但是换一种理解方法的话,就容易理解了。如果我们按照webpack的构建流程可以将配置项划分为两类来理解:

  1. 流程类:主要是干预打包编译规则,直接影响打包结果。
  2. 工具类:打包流程外,提供工程化的配置项。

image.png

# 1. 流程类

根据webpack编译流程来看构建项,这样的话就明白为什么会有这个配置项了。所以先来回顾下webpack的构建流程:

webpack编译流程

  • 输入:webpack从文件系统的读取入口
  • 模块递归处理:读取分析entry模块,调用Loader转译Module内容,并转换为AST,找到模块依赖,递归处理依赖,直到所有模块都处理完毕。
  • 后处理:所有模块处理完成后,根据Entry配置将模块封装进不同的Chunk对象,经过模块合并、注入运行时、产物优化,将模块按照chunk合并成最终产物,写入文件系统。

按照以上流程划分配置项有:

image.png

总结起来:

  1. webpack首先根据输入配置entry找到项目入口文件
  2. 按照模块配置相关的内容(module/resolve/externals)配置的规则处理模块,包括对各种类型文件利用loader转换为js类型、依赖分析等。
  3. 最后根据后处理相关的配置(optimization/target)合并模块资源、注入运行时依赖、优化产物等。

比较重要的点:

  1. resolve:告诉webpack怎么去找模块,这一步是webpack常用的优化点(考点),
    • 可以通过设置resolve的extensions缩小文件后缀的遍历路径,
    • 或者resolve的modules设置import的路径,一般设置为当前目录下的node_modules
  2. module:可以理解为loader配置,js、css、img等的loader配置
  3. optimization:splitChunk的分包配置等在这里,属于chunk生成阶段使用的。

# 2. 工具类配置

用于解决某一工程类问题,可以划分为以下三类:

  1. 开发效率类:watch、devtool、devServer
  2. 性能优化类:cache、performance
  3. 日志类:stats、infrastructureLogging

image.png

# 二、 js、css、img常用loader梳理

这块的话js比较熟悉,css插件不少,有时候记不住,需要梳理下,还有img的,也容易迷糊😓。

注意:loader执行顺序从右到左。

# 1. js loader

js的loader最常用就是babel-loader,ts的话是ts-loader或者,还有一般会加eslint插件,eslint插件一般会加扩展包。不多说了,直接脑图总结:

image.png

配置示例

const path = require('path')
const ESLintPlugin = require('eslint-webpack-plugin')

module.exports = {
  entry: './src/index.ts',
  mode: 'development',
  devtool: false,
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: {
          loader: 'babel-loader',
          options: { presets: ['@babel/preset-typescript'] }
        }
      }
    ]
  },
  plugins: [new ESLintPlugin({ extensions: ['.js', '.ts'] })]
}

# 2. css

webpack不能处理css,所以需要借助css-loader插件将它转换为js格式使它可以处理。然后再通过style-loader或者mini-css-extract-plugin插入到页面中。

  • css-loader:将css翻译为module.exports = "${css}"的js代码
  • 插入页面,以下二选一:
    • style-loader创建一个style标签插入
    • mini-css-extract-plugin抽离成单独的css文件,通过link标签的方式插入到页面中

除去以上基础的,还有css预处理器以及post-css:

  • less-loader、sass-loader,预处理器定义了一套css之上的超集,如Ts与js的关系。
  • post-css:增强原生css的能力,没有定义一门新的语言,而是将css解析为ast结构,并传入postcss插件做处理,具体功能都由插件实现,就像babel和js。

脑图总结:

image.png

这里要注意的就是loader的顺序,实际处理顺序是从右到左,所以css的执行的流程:

less-loader=> post-css=> css-loader => style-loader,

style-loader一定要在最后面css-loader后面,因为被解析为js后才能插入页面。

示例demo如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          "style-loader", 
          {
            loader: "css-loader",            
            options: {
              importLoaders: 1
            }
          }, 
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                // 添加 autoprefixer 插件
                plugins: [require("autoprefixer")],
              },
            },
          },
          "less-loader"
        ],
      },
    ],
  }
};

# 3. 图片处理

这一块webpack4和5有区别了,因为相关的loader,webpack5基本内置了处理流程,等同于将img、文本等也升级为webpack的一等公民了。

图片分两部分,一部分是加载图片时使用的loader,一个是图片相关优化的loader。

# 图片加载相关
loader 作用 webpack5
file-loader 图片引用转换为url并生成相应图片 type = "asset/resource"'
url-loader 对于小于阈值 limit 的图像直接转化为 base64 编码,大于阈值用file-loader转换,看起来是file-loader升级版 type = "asset/inline"
raw-loader 不做任何转移,只是简单将文件内容复制到产物中,适用于svg type = "asset/source"

以上loader不仅可以处理图片,还可以加载任意类型的多媒体和文本文件。

# 图片优化相关
优化方法 loader 说明
图像压缩 image-webpack-loader (opens new window) 组件 底层依赖于 imagemin (opens new window) 及一系列的图像优化工具
响应式图片 responsive-loader (opens new window) 等 生成不同尺寸的图片
雪碧图 webpack-spritesmith (opens new window) 插件 目前使用场景减少,iconfont和base64图片处理了小图标相关的功能

脑图总结

image.png

示例代码

  module: {
    rules: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      use: [{
        loader: 'babel-loader',
        options: {
          presets: [
            ['@babel/preset-env'],
            ['@babel/preset-react']
          ]
        }
      }]
    }, {
      test: /\.less$/i,
      use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
    }, {
      test: /\.svg$/i,
      use: ['svg-url-loader']
    }, {
      test: /\.(png|jpg)$/,
      type: "asset/resource",
      use: [{
        loader: 'image-webpack-loader',

        options: {
          disable: true,
          mozjpeg: {
            quality: 10
          },
        }
      }]
    }],
  }

# 三、webpack优化方案

我们通常遇到性能分析问题,都要按照以下三个步骤来走:

  1. 度量
  2. 分析卡点
  3. 解决问题

# 性能度量工具

那有哪些性能度量工具呢?

image.png

以上这些工具可以用于webpack打包结果分析。

# 为什么会慢

为什么webpack在大型项目中性能不佳?

  • 一方面是js的单线程架构,无法并行执行,不像rust、go是支持多线程的,这也就是esbuild为啥优于webpack,因为语言层面就区别挺大。
  • 另一方面从webpack的流程看,它要完成大量的文件读写、代码转译等操作。

# 性能优化方法

关于性能优化,有没有觉得如果不实际操作,直接看那些优化方法,在面试的时候总是容易忘?为了让自己梳理清楚,可以将webpack的常用优化方法分为两个方向:

方面 目的 方法
优化构建速度 提升开发效率 1. 持久化缓存
2. 并行构建
3. 缩小编译范围
4. 使用最新版本的webpack5、node
优化运行时性能 提升网页运行时性能 1. 动态加载,减少首屏资源加载量
2. 代码压缩
3.tree-shaking、scope hoisting减少应用体积

# 一、构建速度优化

# 1. 持久化缓存

这个就是空间换时间的思路了。因为webpack5自带了持久化缓存 (opens new window)的功能,所以可以从webpack5和4区分来看。

  • webpack5的持久化缓存能够把首次构建结果保存到文件系统,下次构建就可以跳过解析、连接、编译,直接用上次编译好的对象。

  • webpack4可以借助插件实现类似的功能。例如cache-loader (opens new window)hard-source-webpack-plugin (opens new window),这一点可以通过对vue-cli的内置的配置看出来,执行命令vue inspect > output.js或者npx vue-cli-service inspect > output.js来导出cli的默认配置,可以发现里面出现了2次cache-loader的影子。分别是在vue-loaderbabel-loader前面,看吧,原来我们一直在使用cache-loader。

image.png image.png

脑图总结:

image.png

# 2. 并行构建

这一条是从单线程限制的方面优化,也是借助插件来实现。例如:

image.png

# 3. 缩小编译范围
  • 优化loader的执行范围
  • 模块解析,配置 resolve 控制资源搜索范围;
  • 针对 npm 包设置 module.noParse 跳过编译步骤;

image.png

# 4. 使用最新版本的webpack5、node

# 二、运行时性能优化

# 1. 动态加载,减少首屏资源加载量

SplitChunksPlugin (opens new window) 是 Webpack 4 之后内置实现的最新分包方案。常用分包方案

  • node_modules设置分组
  • 业务代码
    • 运行时代码单独抽离chunk
    • 设置common分组,通过minChunk设置使用多次的抽离为chunk
# 2. 代码压缩
  • terser-webpack-plugin:用于压缩 JS 代码的插件;
  • css-minimizer-webpack-plugin:用于压缩 CSS 代码的插件;
  • html-minifier-terser:用于压缩 HTML 代码的插件。

插件都支持 include/test/exclude 配置项,用于控制压缩功能的应用范围;也都支持 minify 配置项,用于切换压缩器,借助这个配置我们可以使用性能更佳的工具,如 ESBuild 执行压缩。

# 3. 利用tree-shaking、scope hoisting减少应用体积

image.png

# 四、webpack5新特性

既然是webpack5,就把webpack5的升级点简单总结下吧。

image.png