webpack4插件及工做流程

基础篇能够参考webpack4基础node

webpack做者是c#出身,不少代码是OOP模式,能够借鉴下webpack

Tapable

webpack4重写了Tapable, 是webpack的插件组织的核心。它提供给各个插件钩子,在事件触发时执行这些挂载的方法。webapck的插件里必须有apply()方法,当其被调用的时候webpack将钩子上的方法挂载到各个事件下面有点像nodejsEventEmitter$ongit

class Car {
	constructor() {
		this.hooks = {
			accelerate: new SyncHook(["newSpeed"]),
			brake: new SyncHook(),
			calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
		};
	},
    setSpeed(newSpeed) {
		this.hooks.accelerate.call(newSpeed);
	}
}
复制代码

如上代码所示先是在实例化的过程当中注册了三个钩子函数,在实例上调用方法时触发钩子函数。 下面介绍webpack里主要的6个Tapable的实例, 它们都继承了Tapable,定义了一些本身的hookgithub

Compiler

最高层的实例,初始化配置,提供全局性的钩子好比done, compilation。其余的Tapable实例须要经过其访问,如web

compiler.hooks.compilation.tap(
      "myFirstWebpackPlugin",
      (compilation, params) => {
        compilation.hooks.seal.tap()
      }
    );
复制代码

Compilation

Compiler建立,整个构建就在这里完成,进行依赖图构建,优化资源,渲染出runtime时的代码等。下面的4个实例都是发生在这个阶段。json

Resolver

当你请求一个模块的时候,你将模块名或者相对地址发给模块解析器,它会去解析出绝对地址去寻找那个模块,看是否存在,若是存在则返回相应的模块信息,包括上下文等。这里的请求能够相似网络请求同样携带上查询参数之类的,Resolver将会返回额外信息。webpack4里将Resolver这个实例抽出来单独发了一个包enhanced-resolve, 抽象出来能够便于用户实现本身的Resolverc#

ModuleFactory

模块工厂就是负责构造模块的实例,介绍两种NormalModuleFactoryContextModuleFactory。二者不一样的地方在于后者用于解析动态import(). 模块工厂主要是用于将Resolver解析成功的请求里的源码从文件中拿出,在内存中建立一个模块对象(NormalModule)数组

Parser

Parser主要用于将代码解析成AST抽象语法🌲.能够在ast查看代码转换成AST后的样子。webpack默认采用acorn解析器,babel是babylonParserModuleFactory返回的对象里的代码字符串转换成AST后进行解析,发现import或者require或者define相似模块引用时会将这些引用信息也就是依赖添加到当前模块的对象里,这样每一个模块对象里不但有本身模块的信息还包含它的依赖信息。webpack会在不单单会在模块声明处触发事件,它甚至会在解析到变量时也触发事件。以下在webpack/lib/Parser.js里能够看到以下三个钩子函数浏览器

varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
    varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
    varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
复制代码

Template

负责生成运行时的代码babel

// 源码
// index.js
    var multiply  = require('./multiply')
    var sum = (a,b)=>{
        return a+b;
    }
    module.exports = sum;
// multiply.js
    module.exports = (a, b) => a*b

// 生成的runtime
[
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
        var multiply  = __webpack_require__(1)
        var sum = (a,b)=>{
            return a+b;
        }
        module.exports = sum;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
        module.exports = (a, b) => a*b
/***/ })
];
复制代码

如上面代码所示,里面包含三个模板,分别负责chunk, module, dependency. chunk是包含多个模块的数组,就是外面数组的形式;module就是里面用当即执行函数包围的部分;dependency就是将原先import, require等引用模块部分转换成 __webpack_require__.

工做流程

目前只看到模块构建那部分,后续再补充╮(╯_╰)╭。。。心得就是利用好vscode的调试工具多打断点~~

介绍完了这六个实例,下面大体讲下webpack的工做流程,webpack作的工做很是多,这里只挑主要的讲下。括号里的是源码所在的文件位置,上下文是node_modules/webpack. 本流程基于webpack4.30.0版本。

  1. 首先是将配置文件读入,webpack4有默认配置options = new WebpackOptionsDefaulter().process(options);会以用户的配置为先。Compiler进行建立compiler = new Compiler(options.context);将配置里的plugin部分进行绑定调用
for (const plugin of options.plugins) {
        if (typeof plugin === "function") {
            plugin.call(compiler, compiler);
        } else {
            plugin.apply(compiler);
        }
    }
复制代码

进行其余配置设置compiler.options = new WebpackOptionsApply().process(options, compiler); (lib/webpack.js) 2. 接着根据打包的目标(web, node, electron等)生成不一样的打包模板

switch (options.target) {
        case "web":
            JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
            ...
            break;
        case "webworker":
            ...
复制代码

由于浏览器端请求异步加载的模块会相似jsonp插入dom中<script>标签,而好比node端是没有dom结构的。

new EntryOptionPlugin().apply(compiler);
    compiler.hooks.entryOption.call(options.context, options.entry);
复制代码

这部分是将入口处配置添加调用entryOption钩子。 (lib/WebpackOptionsApply.js) 3. 根据不一样接口类型调用不一样的类,webpack里处处都是类

// lib/EntryOptionPlugin.js
    if (typeof entry === "string" || Array.isArray(entry)) {
        itemToPlugin(context, entry, "main").apply(compiler);
    } else if (typeof entry === "object") {
        for (const name of Object.keys(entry)) {
            itemToPlugin(context, entry[name], name).apply(compiler);
        }
    } else if (typeof entry === "function") {
        new DynamicEntryPlugin(context, entry).apply(compiler);
    }
复制代码

这里举例是单文件入口, 在compilation钩子上绑定(即Compiler建立compilation后调用)回调,指定当前依赖的模块生成方法。

compiler.hooks.compilation.tap(
        "SingleEntryPlugin",
        (compilation, { normalModuleFactory }) => {
            compilation.dependencyFactories.set(
                SingleEntryDependency,
                normalModuleFactory
            );
        }
    );
复制代码

(lib/SingleEntryPlugin.js) 4. 建立compilation (lib/Compiler.js).

const compilation = this.createCompilation();
复制代码
// lib/SingleEntryPlugin.js
    compiler.hooks.make.tapAsync(
        "SingleEntryPlugin",
        (compilation, callback) => {
            const { entry, name, context } = this;
            const dep = SingleEntryPlugin.createDependency(entry, name);
            compilation.addEntry(context, dep, name, callback);
        }
    );
复制代码

上面这段是以前注册的,可是会在compilation建立完成前调用,是个异步钩子。compilation建立好后传入,它会将入口建立一个依赖。 5. 开始执行addEntry()方法,在addEntry方法里调用_addModuleChain,将当前入口文件建立模块moduleFactory.create,模块建立好后处理当前模块的依赖项this.processModuleDependencies. 将依赖建立模块后再依次解析模块的依赖。(lib/Compilation.js)

参考资料

主要仍是Sean的课程 webpack-plugins

contribute-to-webpack

相关文章
相关标签/搜索