Skip to content

6.1 认识构建工具

如果你是一名有经验的前端开发者,一定听说过构建工具(如:Webpack)。那么什么是构建工具?它有什么作用?本节我们来探索这个问题。

在传统的前端三驾马车中并没有“构建”这么一说,因为开发者编写的 HTML、CSS、JavaScript 代码扔给浏览器可以直接运行。但是随着前端技术快速发展,出现了各种各样的新技术(如 ES6、Sass、TypeScript、ES Modules 等),这些新技术多数处于浏览器不认识的尴尬地位,因此必须经过“新代码变旧代码”的转换才能被浏览器使用,这个转换的过程就被称为“构建”,也就是打包。

然而在一个现代前端项目中,几乎每一处代码都要经过层层构建才能变成最终的产物,这个过程非常复杂。比如我要修改一个组件,当这部分修改最终在页面上生效时,中间需要经过以下一系列的构建步骤:

  1. TypeScript 转换:TypeScript 编译成 JavaScript。
  2. JavaScript 转换:将 ES6+ 代码编译成 ES5。
  3. CSS 转换:将 Sass、Less 等编译成普通 CSS。
  4. Vue 转换:解析并拆分 .vue 文件,生成标准代码。
  5. ESLint 校验:检测代码是否有语法错误、不符合规范等。
  6. 其他的构建任务....

如果每一个构建任务都要手动执行的话,那简直太低效了,而且在大型项目中根本不现实。所以大家希望有一个工具,可以让这一套构建任务自动执行,我们只需要提供一个触发时机,这样效率就会大大提高。这个“自动化执行构建任务”的工具就是我们说的构建工具。

一句话概括构建工具的作用,就是简单快速高效地把源代码转换成可执行的 HTML、CSS、JavaScript 代码。

构建工具其实是工程化、自动化思想在前端开发中的实践。把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程,而我们只需要配置它就好。构建工具给前端开发注入了巨大的活力,推动了前端工程化的进程,当然这些都有 Node.js 的功劳,因为大多数构建流程都是在 Node.js 环境下执行的。

以 Webpack 为主的构建工具流行了很长时间,也逐渐暴露出很多问题,速度方面是主要诟病。一些优秀的开发者尝试突破 Node.js 的瓶颈,使用更高效的开发语言实现构建,这是构建工具的一次突破性升级。

6.1.1 Webpack

Webpack 是一个前端静态资源的打包工具,一切文件资源包括 JavaScript、CSS、图片、模板等,Webpack 统统将其视为模块,因此它是一个模块打包工具。Webpack 几乎支持市面上所有的模块系统(包括 ESM、CommonJS、AMD 等),可以在绝大部分场景下使用。Webpack 专注于构建模块化的项目。

其官网的首页图很形象的画出了 Webpack 的构建原理,如下:

Webpack 会从一个或多个入口文件开始,依次查找依赖关系,最终构建一个依赖图,这个依赖图中包含着应用程序中所需的所有模块。在 Webpack 处理时会将一个入口文件的依赖图视为一个 chunk,处理后可能会输出一个或多个 bundle。通常只有一个 bundle 会被浏览器直接加载,其他的 bundle 可以按需加载。

当遇到不认识的模块时,Webpack 会通过 Loader 转换文件。比如使用 Vue 时会遇到 .vue 为后缀的单文件组件,Webpack 默认不认识该文件,但是可以通过 vue-loader 来解析这个组件,将其拆解为普通的 JavaScript、CSS 代码,最后输出为可运行的 bundle 文件。

当你想要自定义 Webpack 的构建过程时,使用 Plugins 插件可实现该需求。比如我要设置一组环境变量,可以通过 DefinePlugin 插件定义,然后就可以在代码中使用该变量。当 Webpack 执行构建时,代码中的环境变量会被替换为插件中定义的环境变量值。

Webpack 具有很大的灵活性,通过丰富的配置来定义如何处理文件,大致使用如下:

js
module.exports = {
  entry: "./app.js", // 模块入口文件
  output: {
    filename: "bundle.js", // 打包后生成一个文件 bundle.js 输出
  },
  module: {
    rules: [], // 定义 Loader
  },
  plugins: [], // 定义 Plugin
};

6.1.2 Rollup

Rollup 是在 Webpack 流行之后出现的另一个打包工具。相比于 Webpack 的大而全,Rollup 只专注于 JavaScript 的模块打包,并且它只针对 ESM 进行打包,不像 Webpack 兼容多种模块化方案,因此它做到了“小而轻”。Rollup 的亮点是 Tree Shaking,该方案可以去除那些已被定义但没被使用的代码,减小打包后的体积。Rollup 还拥有丰富且强大的插件系统,在各种场景中都能找到合适的解决方案。

将 Rollup 与 Webpack 对比,它们的异同之处如下:

  • 两者都是模块打包工具。Webpack 支持所有主流模块化方案,Rollup 仅支持 ESM。
  • Webpack 中一切皆模块,通过 Loader 处理任何文件;Rollup 只专注于 JavaScript。
  • Webpack 适用于项目打包,Rollup 主要适用于库文件打包。
  • Rollup 不支持 Code Spliting,但打包出的代码更小更精简。
  • Webpack 优势在于全面,全面的兼容性;Rollup 优势在于精简,面向未来。

Rollup 的配置文件格式如下:

js
module.exports = {
  // 入口
  input: "./lib/index.js",
  // 打包不同格式
  output: [
    {
      format: "umd",
      file: "./build/bundle.umd.js",
    },
    {
      format: "cjs",
      file: "./build/bundle.cjs.js",
    },
  ],
  plugins: [], // 插件目录
};

Rollup 允许使用新的 ESM 编写代码,然后将其编译回现有支持的格式(例如 CommonJS 模块、AMD 模块和 IIFE 风格的脚本)。这意味着可以全面使用 ESM 进行开发并在任何地方使用编译后的库文件。

6.1.3 Vite

在 Webpack 逐渐成为前端工程的统一构建工具之后,它变的越来越大,尝试解决所有问题,于是弊端也随之暴露出来,最明显的有亮点:

  1. 复杂项目下,打包速度太慢。
  2. 配置太复杂,插件加载器眼花缭乱。

打包速度慢的根源是 Webpack 基于 Node.js 实现文件操作,性能瓶颈在语言上面,因此优化方案也只能是微小的改进,无法显著提升。而它的配置复杂是因为需要构建的内容实在太多了,模块化的逻辑非常复杂,并且它不支持 TypeScript,一些复杂的配置无法得到类型提示,所以配置的门槛越来越高,以至于人们把高级前端工程师戏称为 Webpack 配置工程师。

Vite 则与 Webpack 的完全不同,它被称为下一代的前端工具链。何为下一代?必然是有根本上的改进。Vite 官网提供了 6 大功能。

  1. 极速的服务启动。
  2. 轻量快速的热重载。
  3. TypeScript、JSX、CSS 等开箱即用。
  4. 支持使用 Rollup 构建。
  5. 高拓展性,与 Rollup 通用的插件。
  6. 完全类型化的 API。

如果你使用过 Vite 构建项目,一个非常明显的直观感受就是快!在控制台运行项目后,几乎感受不到编译的过程,项目就已经运行起来了,速度真是块到飞起。

为什么会这么快呢?这是因为 Vite 另辟蹊径,不再使用 Node.js 作为执行编译的语言,而是使用 Go 语言编写的 esbuild 执行构建,速度比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

除了编译语言上的升级,Vite 直接面向现代浏览器,以原生 ESM 的方式提供源码。这样虽然放弃了对老版浏览器的兼容,但好处是省去了大量的模块化转换,并且使用原生 ESM 实现按需导入和按需编译,这样在 “编译加速+减少转换” 的双重升级下,才有了如此不可思议的速度。

Vite 官方提供给了两张构建原理对比图,可以更直观的看出 Vite 于传统打包的区别。