二、Webpack5 入门到原理(高级优化)

系列文章

一、Webpack5 入门到原理(基础)

1、提升开发体验

1、sourceMap

1、为什么?

为什么要有sourceMap

因为我们在开发环境用webpack编译生成的代码通常情况下是这个样子的,如果在开发环境下面报错了,我们在定位问题的时候是非常困难的,所以我们就需要一个文件来映射源代码与编译后代码之间的关系。

2、是什么?

sourceMap顾名思义,就是对源代码的一个映射,里面包含了源代码和构建后代码的每一行、每一列的映射关系。当构建后的代码运行报错的时候,就可以通过.map文件,从构建后代码出错的位置找到映射后源代码出错的位置,这样就可以让浏览器提示出源代码出错的位置,帮助我们更快的找到错误的位置。

3、怎么用?

通过查看Webpack DevTool 文档open in new window可知,SourceMap 的值有很多种情况.

但实际开发时我们只需要关注两种情况即可:

  • 开发模式:cheap-module-source-map

    • 优点:打包编译速度快,只包含行映射
    • 缺点:没有列映射
js 复制代码
module.exports = {
  mode: "development",
  devtool: "cheap-module-source-map",
};
  • 生产模式:source-map

    • 优点:包含行/列映射
    • 缺点:打包编译速度更慢

生产模式也有可能不开启sourceMap,会部生成专门的sourceMap文件

js 复制代码
module.exports = {
  // 其他省略
  mode: "production",
  devtool: "source-map",
};

2、提升打包构建速度

1、HotModuleReplacement

1、为什么?

在没有使用热模块替换功能的时候,当我们修改了项目中一个模块的代码,webpack就会将所有模块重新编译打包,这样速度回很,还会刷新整个页面。

所以我们就需要一个机制,就是当我们修改了某一模块,只需要重新打包编译当前模块,其他模块不变,这样就能大大提升开发环境编译的速度。

2、是什么?

HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而不需要重新加载整个页面。

3、怎么用?

css 已经经过 style-loader 处理过了,已经有HMR的功能了,此时 js 还不行。

基本配置

js 复制代码
module.exports = {
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

js 配置

js 复制代码
// 判断是否支持 HMR 功能
if (module.hot) {
  module.hot.accept("./js/count.js", function (count) {
    const result1 = count(2, 1);
    console.log(result1);
  });

  module.hot.accept("./js/sum.js", function (sum) {
    const result2 = sum(1, 2, 3, 4);
    console.log(result2);
  });
}

2、oneOf

1、为什么?

webpack打包时,每个文件都会经过所有的loader处理,就算有test进行匹配,没有进行实际上的处理,但是都需要过一遍,比较慢。

2、是什么?

就是只能匹配上一个loader,剩下的就不进行匹配了。

3、怎么用?

js 复制代码
  // 加载器
  module: {
    rules: [
      {
        oneOf: [
          ... 原有的 rule 规则
        ],
      },
    ],
  },

生产环境也这样配置

3、include/exclude

1、为什么?

开发的时候我们安装的第三方库或者插件,都下载到了node_modules中了,这些依赖包在内部已经生成好了编译后的代码了,所以不在需要webpack对其进行编译。所以我们在对 js 文件进行处理的时候,就需要排除这些node_modules下面的依赖包。

2、是什么?

  • include 包含,只处理 包含的文件
  • exclude 排除,处理 排除文件之外的文件

3、怎么用?

开发环境配置

js 复制代码
  {
    test: /\.js$/,
    // exclude: /node_modules/, // 排除 node_modules 下面的代码不进行编译
    include: path.resolve(__dirname, "../src"),
    loader: "babel-loader",
  },
  
  
  plugins: [
    new ESlintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
    })
  ],

生产环境也是同样的配置

3、Cache

1、为什么?

每次打包的时候 js 文件都要经过 eslint 检查和 babel 编译,比较慢。

我们可以缓存之前 eslint 检查和 babel 编译的结果,这样在第二次构建的时候速度就会更快了。

2、是什么?

eslint 检查和 babel 编译结果进行缓存。

3、怎么用?

js 复制代码

可以观察一下第一次耗时与开启后的耗时

第一次

第二次

还有更多配置参见 babel-loader 文档

4、Thread

1、为什么?

当我们的项目越来越大的时候,打包构建的速度越来越慢的时候,这个时候我们就要想办法提高打包的效率,对于我们来说其实就是提高 js 打包的效率,因为其他的文件数量相对较少。

而处理 js 文件的主要就是 eslint babel Terser 这三个工具,因此我们要提高他们的运行速度。

我们可以采取开启多进程同时处理 js 文件,这样的速度就会比之前的单进程打包更快。

2、是什么?

多进程打包:开启电脑的多个进程同时干一件事情,速度更快。

注意:仅适合在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右的开销。

3、怎么用?

启动的进程数就是我们电脑 cpu 的核心数

js 复制代码
const os = require("os");

// 获取 cpu 核数
const threads = os.cpus().length;

安装 thread-loader

shell 复制代码
yarn add thread-loader

webpack 配置

js 复制代码
const TerserPlugin = require("terser-webpack-plugin");

  // babel-loader
  {
    test: /\.js$/,
    exclude: /node_modules/, // 排除 node_modules 下面的代码不进行编译
    use: [
      {
        loader: "thread-loader",
        options: {
          workers: threads,
        },
      },
      {
        loader: "babel-loader",
        options: {
          cacheDirectory: true,
          cacheCompression: false,
        },
      },
    ],
  },
  
  // eslint
  new ESlintWebpackPlugin({
    ...
    threads,
  }),
  
  // 压缩
  optimization: {
    minimize: true,
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,如果我们有其他的配置就要在这里进行重写
      new TerserPlugin({
        parallel: threads, // 开启多进程
      }),
    ],
  },

更多信息参见 优化(Optimization)

因为当前我们的代码内容很少,又因为开启了进程的原因,所以实际上我们的打包时间会变长。所以大项目才使用多进程打包。

开启多进程以后的

可以与之前的进行对比

3、减少代码体积

1、tree shaking

1、为什么?

开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。

如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。

这样将整个库都打包进来,体积就太大了。

2、是什么?

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 importexport。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。

注意:它依赖 Es Module

3、怎么用?

我们新建src/js/math.js

js 复制代码
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

在 main.js 中使用

js 复制代码
import { cube } from "./js/math";

console.log(cube(5));

然后我们先给webpack.prod.js 配置文件中的 mode 改成 development 然后进行打包

再改回 production再看打包结果,是不是很清晰,只会把用到的代码打包进结果里面。

更多用法请参见 Tree Shaking 文档

webpack production 模式已经默认开启了这个功能。

2、babel

1、为什么?

Babel 在每个文件都插入了辅助代码,使代码体积过大!

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

你可以引入 Babel runtime 作为一个独立模块,来避免重复引入。

注意 :你必须执行 npm install -D @babel/plugin-transform-runtime 来把它包含到你的项目中,然后使用 npm install @babel/runtime@babel/runtime 安装为一个依赖。

2、是什么?

@babel/plugin-transform-runtime配置禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

更多信息请查看 文档

3、怎么用?

下载包

js 复制代码
yarn add @babel/plugin-transform-runtime -D

配置

js 复制代码
module: {
  rules: [
    {
        test: /\.js$/,
        exclude: /node_modules/, // 排除 node_modules 下面的代码不进行编译
        use: [
         ...
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true,
              cacheCompression: false,
              presets: ["@babel/preset-env"],
              plugins: ["@babel/plugin-transform-runtime"],
            },
          },
        ],
      },
  ];
}

更多信息请参见文档 babel-loader

这里说的 babel 产生的辅助代码,目前没有测试出来,什么情况下会产生辅助代码,有了解的小伙伴可以评论一波哦!!!

4、优化代码运行性能

1、code split

1、为什么?

打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

2、是什么?

代码分割(Code Split)主要做了两件事:

  1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
  2. 按需加载:需要哪个文件就加载哪个文件

3、怎么用?

代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示

  • 多入口配置

文件目录

arduino 复制代码
├── public
├── src
|   ├── app.js
|   └── main.js
├── package.json
└── webpack.config.js

app.js

js 复制代码
console.log("this is app");

main.js

js 复制代码
console.log("this is main");

运行打包命令

shell 复制代码
yarn build
  • 提取重复代码

如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。

我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。

代码如下,两个入口文件都引用了 math.js

新手可以跟着 视频课程 学习,有基础的直接跟着文章敲一遍感受一下。

相关推荐
90后小陈老师1 小时前
3D个人简历网站 5.天空、鸟、飞机
前端·javascript·3d
不爱吃糖的程序媛4 小时前
浅谈前端架构设计与工程化
前端·前端架构设计
BillKu6 小时前
Vue3 Element Plus 对话框加载实现
javascript·vue.js·elementui
郝YH是人间理想6 小时前
系统架构设计师案例分析题——web篇
前端·软件工程
Evaporator Core6 小时前
深入探索:Core Web Vitals 进阶优化与新兴指标
前端·windows
初遇你时动了情7 小时前
html js 原生实现web组件、web公共组件、template模版插槽
前端·javascript·html
QQ2740287567 小时前
Soundness Gitpod 部署教程
linux·运维·服务器·前端·chrome·web3
前端小崔7 小时前
从零开始学习three.js(18):一文详解three.js中的着色器Shader
前端·javascript·学习·3d·webgl·数据可视化·着色器
哎呦你好7 小时前
HTML 表格与div深度解析区别及常见误区
前端·html
运维@小兵7 小时前
vue配置子路由,实现点击左侧菜单,内容区域显示不同的内容
前端·javascript·vue.js