Webpack编译原理
garinghu opened this issue · 0 comments
从bundle文件分析webpack都做了什么
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ console.log(module)
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
let world = __webpack_require__(1);
function sayHello(){
console.log('hello')
}
world();
// world.sayWorld();
/***/ }),
/* 1 */
/***/ (function(module, exports) {
function world(){
console.log('world');
}
module.exports = world;
/***/ })
/******/ ]);
webpack构建的构建流程
webpack构建构建流程可以分为以下三个阶段
- 初始化:启动构建,读取与合并配置参数,加载plugin,实例化compiler
- 编译:从entry出发,针对每个module串行调用对应的loader去翻译文件的内容,再找到该module依赖的module,递归地进行编译处理(与其说module是文件,不如说是“依赖”比较准确)
- 输出:将编译后的module组合成chunk,将chunk转换成文件,输出到文件系统中(一个chunk包含多个module)
如果只执行一次构建,则以上阶段将会按照顺序各执行一次,但在开启监听模式下,流程将如图所示
这里解释一下几个重要概念
- module:每个文件(打包前)为一个module,例如上面hello.js引用world.js,这两个文件都为module,只不过hello.js是入口文件而已
- chunk:正常情况下,一个入口会对应一个出口,最后生成的文件即为一个chunk,当然代码分割产生对的文件也为一个chunk,可以通过引入webpack-bundle-analyzer插件分析最终产生的bundle的结构
编译阶段发生的事件
webpack整体是一个插件架构,所有的功能都以插件的方式集成在构建流程中,通过发布订阅事件来触发各个插件执行。webpack核心使用Tapable 来实现插件(plugins)的binding和applying.其中每一个事件都可被plugin所接收
- run:启动一次新的编译
- compile:该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上compiler对象
- compilation:当webpack以开发模式运行时,每当检测到文件的变化,便有一次compilcation被创建,一个compilation对象包含了当前的模块资源,编译生成资源,变化的文件等。compilation提供了很多事件回掉给插件进行扩展
- make:一个新的compilation创建完毕,即将从entry开始读取文件,根据文件的类型和配置的loader对文件进行编译,编译完后再找出该文件依赖的文件,递归地编译和解析
在编译阶段中,最重要的事件是compilation,因为在compilation阶段调用了loader,完成了每个模块的转换操作。在compilation阶段又会发生很多子事件
-
build-module:使用对应的loader去转换一个模块
-
normal-module-loader:用loader转换完一个模块后,使用acorn解析转换后的内容,输出对应的抽象语法树(AST),以方便webpack在后面对代码进行分析
-
seal:依赖模块通过loader转换完成,根据依赖关系生成chunk
由此可以看出webpack在编译的阶段的任务是根据打包前的代码生成module并且捋顺依赖关系,其中生成module用工厂模式实现
编译阶段的具体实现
这里引入了依赖(Dependency)的概念,每一个依赖都包含一个module,指向被依赖的module,这里可以理解成类似链表或有向图
从make事件开始
在创建 module 之前,Compiler 会触发 make,并调用 Compilation.addEntry 方法,通过 options 对象的 entry 字段找到我们的入口js文件。之后,在 addEntry 中调用私有方法 _addModuleChain ,这个方法主要做了两件事情。一是根据模块的类型获取对应的模块工厂并创建模块,二是构建模块。
- 根据依赖模块的类型获取对应的模块工厂,用于后边创建模块。
var moduleFactory = this.dependencyFactories.get(dependency.constructor);
if(!moduleFactory) {
throw new Error("No dependency factory available for this dependency type: " + dependency.constructor.name);
}
- 使用模块工厂创建模块,并将创建出来的module作为参数传给回调方法
moduleFactory.create(context, dependency, function(err, module) {
if(err) {
return errorAndCallback(new EntryModuleNotFoundError(err));
}
if(this.profile) {
if(!module.profile) {
module.profile = {};
}
var afterFactory = +new Date();
module.profile.factory = afterFactory - start;
}
var result = this.addModule(module);
//result表示该module是否第一次创建
if(!result) {
//不是第一次创建
module = this.getModule(module);
onModule(module);
if(this.profile) {
var afterBuilding = +new Date();
module.profile.building = afterBuilding - afterFactory;
}
return callback(null, module);
}
//如果module已缓存过,且不需要rebuild。result是一个Module对象,直接返回该缓存的module
if(result instanceof Module) {
if(this.profile) {
result.profile = module.profile;
}
module = result;
onModule(module);
moduleReady.call(this);
return;
}
- 对module进行build了。包括调用loader处理源文件,使用acorn生成AST
this.buildModule(module, function(err) {
if(err) {
return errorAndCallback(err);
}
if(this.profile) {
var afterBuilding = +new Date();
module.profile.building = afterBuilding - afterFactory;
}
//这里module已经build完了,依赖也收集好了,开始处理依赖的module
moduleReady.call(this);
}.bind(this));
webpack分析依赖是基于ast(抽象语法树)的,其中通过调用acorn解析经loader处理后的源文件http://esprima.org/demo/parse.html#
通过遍历ast即可找到下一个需要构建的模块,形成依赖
Parser.prototype.parse = function parse(source, initialState) {
var ast;
if(!ast) {
// acorn以es6的语法进行解析
ast = acorn.parse(source, {
ranges: true,
locations: true,
ecmaVersion: 6,
sourceType: "module"
});
}
...
};
- 对于当前模块,或许存在着多个依赖模块。当前模块会开辟一个依赖模块的数组,在遍历 AST 时,将 require() 中的模块通过addDependency() 添加到数组中。当前模块构建完成后,webpack 调用 processModuleDependencies 开始递归处理依赖的 module,接着就会重复之前的构建步骤。
Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {
// 根据依赖数组(dependencies)创建依赖模块对象
var factories = [];
for(var i = 0; i < dependencies.length; i++) {
var factory = _this.dependencyFactories.get(dependencies[i][0].constructor);
factories[i] = [factory, dependencies[i]];
}
...
// 与当前模块构建步骤相同
}