webpack梳理

核心概念

本质上,webpack 是一个 JavaScipt 应用程序的静态模块打包器,它递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块根据配置打包成一个或多个包(bundle).

  • 模块(modules)
    在模块化编程中,将程序分解成离散功能的块,称之为模块.每个模块比完整程序更小的接触面,使得校验,调测,测试更容易.在 webpack 中,模块可是是 js 文件,npm 包,图片,css/sass/less 文件.以如下方式方式表达它们的依赖关系:

    • ES2015 import 语句
    • CommonJS require() 语句
    • AMD define 和 require 语句
    • css/sass/less 文件中的 @import 语句
    • css 样式中url()或 HTML 中<img src="">中的图片链接
  • 包(bundle):包由许多不同的模块生成,是已经加载和编译过的源文件的最终版本,用于代码合并和分隔。

  • 依赖关系图(dependency graph)
    当一个文件依赖另一个文件时,webpack 就认为该文件之间有依赖关系.这使得 webpack 可以接收非代码资源(如图像或自定义字体),并把它们当作依赖提供给应用程序.
    webpack 从入口起点开始,递归的构建一个依赖图,其包含着所需的每个模块,然后将这些模块打包为 bundle,可由浏览器加载.

  • 配置(configuration)
    webpack 配置文件,是导出一个对象的 JavaScript 文件。由 webpack 根据该对象定义的属性进行解析。
    因为 webpack 配置是标准的 Node.js CommonJS 模块,所以:

    • 通过 require() 导入其他文件
    • 通过 require() 使用 npm 的工具函数
    • 使用 JavaScript 控制流表达式
    • 对常用值使用常量或变量
    • 编写并执行函数来生成部分配置
  • 块 (chunk):是 webpack 中特定术语,在内部用于管理打包过程。包由块组成,块有几种类型(如 entry 和 child),通常块直接输出对应的包,但是有些配置不会产生一对一的关系。

  • 入口起点(entry points)
    在 wepack 配置中定义的 entry 属性。

    • 简单语法 entry: string|Array<string>
    • 对象语法 entry:{[entryChunkNume:string]: string|Array<string>}
  • 输出(output)
    在配置中定义的 output 属性,它控制 webpack 像硬盘写入编译文件。多个入口起点,可以只指定一个输出配置。
    output 属性的最低要求,将它设置为一个对象,包含filename,输出文件名path,输出目录,绝对路径
    如果配置了多个入口起点,创建了多个单独的“chunk”,则应该使用占位符来确保每个文件具有唯一的名称.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    entry: {
    app: './src/app.js',
    search: './src/search.js'
    },
    output: {
    filename: '[name].js',
    path: _dirname + '/dist'
    }
    }
    // 输入: ./dist/app.js ./dist/search.js
  • 模块转换器(loader)
    loader 可以将所有类型的文件转化为 webpack 能够处理的有效模块(JavaScript),然后打包处理。本质上,就是将所有类型的文件,转化为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。
    配置中 loader 有两个目标:

    1. test 属性,标识出某个或某些文件要被 loader 进行转换
    2. use 属性,进行转换时,应该使用哪个 loader
      loader 要定义在module.rules中,而不是rules中。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      {
      entry:{},
      output:{},
      module: {
      rules: [
      {test:/\.txt$/, use:'raw-loader'}
      ]
      }
      }

    webpack 编译器,当碰到[require(),import 语句中被解析为’.txt’的路径]时,在打包之前,先用raw-loader转换。

  • 扩展插件(plugin)
    webpack 插件是一个具有apply属性的 JavaScript 对象,apply 属性会被 webpack commpiler 调用,并且 compiler 对象可在整个编译生命周期访问。
    在 webpack 运行时的生命周期中会广播许多事件,plugin 可以监听这些事件,并在合适的时机通过 webpack 提供的 API 改变输出结果。

  • 模式(mode)
    webpack4 默认不需要配置文件,可以通过 mode 选项为 webpack 指定一些默认的配置。mode 分为development/production

  • compiler/compilation

    • compiler 对象,包含了 webpack 环境所有的配置信息(options,loader,plugins),这个对象在 webpack 启动时被实例化,且是全局唯一的,可简单立理解为 webpack 实例。代表整个 webpack 从启动到关闭的生命周期
    • compliation 对象包含了当前的模块资源、编译生成资源、变化的文件等,当 webpack 以开发模式运行时,每当检测到一个文件变化,就有一次新的 compilation 被创建。compilation 对象也提供了许多事件回调供插件做扩展,通过 compilation 也能读取到 compiler 对象。只代表一次新的编译

webpack 构建流程

webpack 的构建流程可分三大阶段:

  • 初始化: 启动构建,读取于合并配置参数,加载 plugin,实例化 compiler。
  • 编译:从 entry 出发,针对每一个 module串行调用对应的 loader 取转换文件,再找到该 module 依赖的 modular,递归地进行编译处理
  • 输出:对编译后的 module 组合成 chunk,把 chunk 转换成文件,输出到文件系统

具体流程概况

webpack 的运行流程是一个串行过程

  1. 初始化参数:从配置文件或者命令行中读取、合并参数,得出最终配置的参数。
  2. 开始编译:用最终的配置参数初始化 compiler 对象,加载所有配置的插件,执行对象的run方法开始编译。
  3. 确定入口:根据配置中的 entry 找出所有的入口文件。
  4. 模块编译:从入口文件出发,调用所有配置的 loader 模块进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过处理。
  5. 完成模块编译:在经过第四步使用 loader 转换完成所有模块后,得到每个模块转换后的最终内容和它们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,webpack 会在特定的事件点广播出特定的事件,plugin 在监听到事件后会执行特定的逻辑,且可以调用 webpack 提供的 api 改变 webpack 运行结果

参考文章

webpack 原理解读
webpack 原理,实现 loader、plugin