Webpack 是一个模块打包器(module bundler),在 webpack 中模块(module)是最基本的概念。

概述

举个例子方便说明,一个应用有如下目录结构:

— src
  — app.js
  — file-a.js
  — file-b.js
1
2
3
4

应用的入口文件是 app.js, 内容如下:

import moduleA from './file-a';
import moduleB from './file-b';

/*
 * 一些应用逻辑
 */
1
2
3
4
5
6

在这个项目中,我们有三个模块,分别是 app.jsfile-a.jsfile-b.js。 Webpack 在执行打包的时候,会从应用入口(app.js)开始解析,通过 import 语句解析到 app.js 依赖了 file-a.jsfile-b.js 这两个模块,构成一个依赖图(dependency graph)。

依赖图是一个描述各节点之间关系的有向图,在这里,这个图是由各个文件之间的 importrequire 来定义的。Webpack 会单纯静态的扫描而不执行这些文件,来生成这个图。

之后,webpack 会根据用户的配置生成输出文件(output)。此外,用户还可以在代码中定义分割点(split points),将应用的代码分割打包到不同的 bundle 中,实现按需加载。

所谓 bundle,就是一组代码的集合,可以将一个应用的所有代码都打包到一个 bundle 中,也可以分成多个 bundle,按需加载。在 Webpack 中,还有一个常见的名词 chunk,chunk 就是代码块,多个 chunk 组成了 bundle。

Webpack 默认支持 ES2015,CommonJS,MJS 和 AMD 模块规范。同时,通过 loader 机制,借助于 css-loader 提供的 @importurl() 语法,webpack 还可以支持对 css 文件的打包。

Webpack 的执行过程(execution process)

Webpack

上图清晰地展示了 Webpack 的执行过程。

首先 Webpack 从 entry 文件开始处理,通常这些 entiry 文件是 JavaScript 文件。在执行过程中,webpack 会根据 loader 的配置来决定如何处理每一个匹配到的文件。最终,在生成依赖图以后,再输出打包后的文件。

Webpack 的解析过程(resolution process)

Webpack 通过 resolve 配置项来在文件系统中查找遇到的每一个模块,比如我们可以通过配置来让 webpack 避开 node_modules 目录。

如果解析失败,webpack 会抛出一个运行时错误,如果解析成功,webpack 则会根据 loader 的配置来处理这个文件。每个 loader 都定义了一套处理对应模块内容的方法。

可以通过多种方式给某一类型的文件指定 loader,比如文件类型或者文件在文件系统中的位置。Webpack 支持通过同样的方式来指定 loaderloader 本身就有自己的 resolve 配置。如果 webpack 查找 loader 失败,则会抛出一个运行时错误。

Webpack 会解析它碰到的任何一个模块,如果一个模块依赖了其他模块,那么 Webpack 会递归的解析每一个依赖模块,直到解析完所有的模块。

同时,Webpack 允许用户自定义不同资源的处理方式。比如用户可以选择将某些资源内联在 JavaScript 文件中来减少网络请求,可以通过 CSS Modules 等技术来将样式与组件关联起来,避免全局冲突。

尽管主要被用来打包 JavaScript,Webpack 还可以被用来处理图片、字体文件这样的资源文件,并将其单独输出文件。这一切都取决于用户如何配置。

Webpack 的评估过程(Evaluation process)

假设用户配置的所有 loader 都成功解析,Webpack 在处理模块的时候,会从下到上、从右到左(styleLoader(cssLoader('./main.css')))按序依次用匹配的 loader 来对模块进行处理。如果一个 loader 执行成功,没有任何运行时错误,那么 Webpack 就将模块的源代码包含到最终生成的 bundle 中。

尽管 loader 处理模块时功能非常强大,但是 loader 却不能处理一些更高级的任务。 Webpack 插件弥补了这一不足。插件可以截取 Webpack 的运行时事件(runtime events)来做一些额外的任务。

MiniCssExtractPlugin 就是一个很好的例子,在和 loader 配合使用的时候,MiniCssExtractPlugin 可以将 css 文件从 bundle 中抽取出来,放到独立的文件中去。否则 css 文件将会和 JavaScript 文件打包在一起。

Webpack 的输出

在处理完所有的模块以后,Webpack 开始输出文件。在输出文件中有一个非常小的运行时(runtime)文件,该运行时文件负责在浏览器中执行 Webpack 输出的结果。另外,在输出文件中还有一个 manifest 文件,记录了所有需要被加载的 bundle 。

Webpack 是配置驱动的

Webpack 是配置驱动的,下面的配置样例清楚的展示如何配置 Webpack 进行打包:

const path = require('path');
const webpack = require('webpack');
module.exports = {
  entry: { app: './entry.js' }, // 开始打包
  output: {
    path: path.join(__dirname, 'dist'), // 输出到 dist 目录
    filename: '[name].js', // 输出 app.js ,因为 entry 的名称为 app
  },
  // 解析所有遇到的模块
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.js$/,
        use: 'swc-loader',
        exclude: /node_modules/,
      },
    ],
  },
  // 进行额外的处理
  plugins: [new webpack.DefinePlugin({ HELLO: 'hello' })],
  // 调整模块解析方式
  resolve: {
    alias: {
      react: 'preact-compat',
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Webpack 的配置非常复杂,而且经常会慢慢变得难以理解。本系列的目的就是为了将这些背后的概念和设计讲清楚,方便更好的理解 Webpack .

总结

Webpack 是一个模块打包器,通过遍历所有模块来生成依赖图,并基于依赖图和用户配置来输出打包结果。

Webpack 依赖于 loaderpluginloader 负责处理模块,而 plugin 则借助于 Webpack 提供的钩子能更好的与执行过程配合执行一些其他处理任务。

Webpack 的配置文件描述了 Webpack 如何来处理各种资源文件,并决定最终以何种形式输出。

关注微信公众号,获取最新推送~