通常我们使用 webpack 都是通过命令行,其实 webpack 也可是 nodejs 的一个模块引入并调用。命令行也是在这基础上封装了一层。
const webpack = require('webpack');
const options = require('./webpack.config');
const compiler = webpack(options);
compiler.run((err, stats) => {});
从上面的代码可以看出 webpack 是个函数并返回一个 Compiler 实例,接着运行 run 方法由此打开了潘多拉的盒子。
webpack 函数
- 在调用 webpack 函数时传入我们的配置文件中导出的配置项
- 合并传入的配置项和命令行传入的配置项(写过nodejs命令行工具应该知道如何接收命令行参数)
- 实例化 Compiler 并传入合并后的配置项
- 注册配置项传入的所有插件
plugin
,这里只是遍历执行plugin
上的 apply 方法,plugin 的真正意义是在 webpack 编译过程中每个阶段hooks
做扩展,调用 apply 方法就是注册监听 webpack 中钩子函数 - 返回 Compiler 实例
compiler 类
compiler 的定义是个 class
-
构造函数内定义了一些属性,包括传入的配置项也被赋值给了私有属性,另外还初始化了一些 hooks 实例
-
重点是 run 方法,在执行 run 方法时就开始执行编译了
- 在传入的配置项中 entry 获取到入口文件
- 从入口文件出发开始编译打包
- 通过路径 fs 到源码
- 获取到原始源码后首先经过 loader 的洗礼,loader 的具体操作后面的文章会详情解释
- 经过 loader 返回的源码在给到 webpack 的 ast 遍历找到语法树中的 require 节点,从新拼接出 require 引入模块的绝对路径添加到当前模块的 dependencies 中作为当前模块的依赖项
- 在此引出 webpack 比较重要的概念 moduleId 和 dependencies ,首先会创建一个私有变量 module 对象,module 中会有 moduleId,dependencies, _source 等属性,当前模块的相对路径就是 moduleId ,当前模块引入的其他模块的绝对路径会 push 到 dependencies中,_source 就是当前模块抽象语法树返回的新代码
- 编译模块是递归操作
- 接着会遍历当前模块的 dependencies 数组,递归的操作编译模块这步,也就是在从上面的第三步开始
- 根据入口和模块之间的依赖关系,组成一个 Chunk ,Chunk 也是一个对象,包含 name, modules 等属性,比如多入口的应用或者异步加载模块都产生不同的 Chunk
- 最后通过依赖关系组成一个 Chunk 被 push 到 Chunks 中,最后的最后就是遍历 Chunks 通过 fs 写入到磁盘
总结
- 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置对象
- 用上一步得到参数初始化 Compiler 对象
- 加载注册所以配置的插件
- 执行 run 方法开始编译
- 根据配置中的 entry 找到入口文件,也可能是多入口
- 从入口文件出发,调用所用配置的 Loader 对模块进行编译
- 再找出该模块的依赖模块,再递归模块编译这一步骤
- 根据入口文件和模块直接的依赖关系,组装成一个 Chunk
- 再把每个 Chunk 转换成一个单独文件添加到输出列表
- 根据配置确定输出的路径和文件名,把文件写入到磁盘