【前端必会】tapable、hook,webpack的灵魂
背景
- 什么是tapable、hook,平时做vue开发时的webpack 配置一直都没弄懂,你也有这种情况吗?
- 还是看源码,闲来无聊又看一下webpack的源码,看看能否找到一些宝藏
- tapable和webpack没有特定关系,可以先看下这篇文章,了解下这个小型库
https://webpack.docschina.org/api/plugins/#tapable
https://blog.csdn.net/mafan121/article/details/113120081
4.下面记录下寻宝过程
开始
执行一次webpack经历了什么,先看一下代码
我们分析一下4点
- 引用了webpack
- 我们使用的配置文件
- 调用webpack函数,传入配置,返回一个compiler(编译器)
- 执行编译器的run方法
分析
引用webpack,先把这个函数找出来
https://github.com/webpack/webpack/blob/main/package.json
"main": "lib/index.js",
https://github.com/webpack/webpack/blob/main/lib/index.js
module.exports = mergeExports(fn, {
get webpack() {
return require("./webpack");
},
https://github.com/webpack/webpack/blob/main/lib/webpack.js
const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ (
/**
* @param {WebpackOptions | (ReadonlyArray<WebpackOptions> & MultiCompilerOptions)} options options
* @param {Callback<Stats> & Callback<MultiStats>=} callback callback
* @returns {Compiler | MultiCompiler}
*/
(options, callback) => {
const create = () => {
if (!asArray(options).every(webpackOptionsSchemaCheck)) {
getValidateSchema()(webpackOptionsSchema, options);
util.deprecate(
() => {},
"webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
"DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
)();
}
/** @type {MultiCompiler|Compiler} */
let compiler;
let watch = false;
/** @type {WatchOptions|WatchOptions[]} */
let watchOptions;
if (Array.isArray(options)) {
/** @type {MultiCompiler} */
compiler = createMultiCompiler(
options,
/** @type {MultiCompilerOptions} */ (options)
);
watch = options.some(options => options.watch);
watchOptions = options.map(options => options.watchOptions || {});
} else {
const webpackOptions = /** @type {WebpackOptions} */ (options);
/** @type {Compiler} */
compiler = createCompiler(webpackOptions);
watch = webpackOptions.watch;
watchOptions = webpackOptions.watchOptions || {};
}
return { compiler, watch, watchOptions };
};
if (callback) {
try {
const { compiler, watch, watchOptions } = create();
if (watch) {
compiler.watch(watchOptions, callback);
} else {
compiler.run((err, stats) => {
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
return compiler;
} catch (err) {
process.nextTick(() => callback(err));
return null;
}
} else {
const { compiler, watch } = create();
if (watch) {
util.deprecate(
() => {},
"A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
"DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
)();
}
return compiler;
}
}
);
module.exports = webpack;
这里主要就是调用create创建一个Compiler(先不理watch)
在看一下create,这里是调用的createCompiler或者createMultiCompiler
在看一下createCompiler,这里主要就是new一个Compiler。这个时候已经开始了webpack编译的生命周期。
/**
* @param {WebpackOptions} rawOptions options object
* @returns {Compiler} a compiler
*/
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context, options);
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
我们简单看一下Compiler类的一些hooks
const {
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
......
class Compiler {
/**
* @param {string} context the compilation path
* @param {WebpackOptions} options options
*/
constructor(context, options = /** @type {WebpackOptions} */ ({})) {
this.hooks = Object.freeze({
/** @type {SyncHook<[]>} */
initialize: new SyncHook([]),
/** @type {SyncBailHook<[Compilation], boolean>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<[Stats]>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {SyncHook<[Stats]>} */
afterDone: new SyncHook(["stats"]),
/** @type {AsyncSeriesHook<[]>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[Compiler]>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<[Compiler]>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
assetEmitted: new AsyncSeriesHook(["file", "info"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
每个hook都是一个 tapable包里对应后hook的实例
在回到创建编译器那里,这时创建一个插件的实例,并且执行apply方法,插件就会向自己关系的hook添加事件处理函数(其实还是一个事件监听),NodeEnvironmentPlugin代码可以自行在源码中查看
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
一切都准备好了之后,我们再看一下编译器的run方法
/**
* @param {Callback<Stats>} callback signals when the call finishes
* @returns {void}
*/
run(callback) {
if (this.running) {
return callback(new ConcurrentCompilationError());
}
let logger;
const finalCallback = (err, stats) => {
if (logger) logger.time("beginIdle");
this.idle = true;
this.cache.beginIdle();
this.idle = true;
if (logger) logger.timeEnd("beginIdle");
this.running = false;
if (err) {
this.hooks.failed.call(err);
}
if (callback !== undefined) callback(err, stats);
this.hooks.afterDone.call(stats);
};
const startTime = Date.now();
this.running = true;
const onCompiled = (err, compilation) => {
if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) {
compilation.startTime = startTime;
compilation.endTime = Date.now();
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
return;
}
process.nextTick(() => {
logger = compilation.getLogger("webpack.Compiler");
logger.time("emitAssets");
this.emitAssets(compilation, err => {
logger.timeEnd("emitAssets");
if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
compilation.startTime = startTime;
compilation.endTime = Date.now();
logger.time("done hook");
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
logger.timeEnd("done hook");
if (err) return finalCallback(err);
this.hooks.additionalPass.callAsync(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
return;
}
logger.time("emitRecords");
this.emitRecords(err => {
logger.timeEnd("emitRecords");
if (err) return finalCallback(err);
compilation.startTime = startTime;
compilation.endTime = Date.now();
logger.time("done hook");
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
logger.timeEnd("done hook");
if (err) return finalCallback(err);
this.cache.storeBuildDependencies(
compilation.buildDependencies,
err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
}
);
});
});
});
});
};
const run = () => {
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
};
if (this.idle) {
this.cache.endIdle(err => {
if (err) return finalCallback(err);
this.idle = false;
run();
});
} else {
run();
}
}
https://github.com/webpack/webpack/blob/main/lib/Compiler.js
这里简单分析一下,主要就是执行run相关生命周期,以及编译。并且编译完成后传入回调函数onCompiled
const run = () => {
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
};
整体逻辑不是很复杂,我们主要可以感受到webpack启动后对hook的一些使用方式。整体的逻辑差不多都是一样的。是不是很简单。
总结
- 想了解一个框架,一定要找到入口函数,一点一点向前探索。
- tapable 是个好东西,
- 关于webpack生命周期,有疑问的时候,除了看文档意外,还可以结合源码去理解,去感受
你学会了吗?欢迎留下你的感受!
【前端必会】tapable、hook,webpack的灵魂的更多相关文章
- 【前端必会】不知道webpack插件? webpack插件源码分析BannerPlugin
背景 不知道webpack插件是怎么回事,除了官方的文档外,还有一个很直观的方式,就是看源码. 看源码是一个挖宝的行动,也是一次冒险,我们可以找一些代码量不是很大的源码 比如webpack插件,我们就 ...
- 【前端必会】走进webpack生命周期,另类的学习方法
背景 webpack构建过程中的hooks都有什么呢?除了在网上看一些文章,还可以通过更直接的办法,结合官方文档快速让你进入webpack的hook世界 写一个入口文件 //index.js cons ...
- 前端必学内容:webpack3快速入门 1-23节内容参考
前端必学内容:webpack(模块打包器) webpack3 学习内容,点击即可到达 (1).webpack快速入门——如何安装webpack及注意事项 (2).webpack快速入门——webpac ...
- 前端工程化(二)---webpack配置
导航 前端工程化(一)---工程基础目录搭建 前端工程化(二)---webpack配置 前端工程化(三)---Vue的开发模式 前端工程化(四)---helloWord 继续上一遍的配置,本节主要记录 ...
- [Linux] 一个前端必会的 Nginx 免费教程-在虚拟机中用deepin测试
原文技术胖的 nginx 技术胖 专注于前端开发 deepin Linux Deepin 是一个基于 DEB 包管理的一个独立操作系统,和那些 Ubuntu(下个大版本是基于 debian 开发) 的 ...
- 2018 BAT最新《前端必考面试题》
2018 BAT最新<前端必考面试题> 1.Doctype作用? 严格模式与混杂模式如何区分?它们有何意义? (1). 声明位于文档中的最前面,处于 标签之前.告知浏览器的解析器,用什么文 ...
- input屏蔽历史记录 ;function($,undefined) 前面的分号是什么用处 JSON 和 JSONP 两兄弟 document.body.scrollTop与document.documentElement.scrollTop兼容 URL中的# 网站性能优化 前端必知的ajax 简单理解同步与异步 那些年,我们被耍过的bug——has
input屏蔽历史记录 设置input的扩展属性autocomplete 为off即可 ;function($,undefined) 前面的分号是什么用处 ;(function($){$.ex ...
- 前端必学---JavaScript数据结构与算法---简介
前端必学---JavaScript数据结构与算法---简介 1. 数据结构: 数据结构是相互之间存在一种或者多种特定关系的数据元素的集合.---<大话数据结构> 1.1 数据结构的分类 1 ...
- 【前端必会】webpack 插件,前进路绕不过的障碍
背景 webpack的使用中我们会遇到各种各样的插件.loader. webpack的功力主要体现在能理解各个插件.loader的数量上.理解的越多功力越深 开始 https://webpack.do ...
随机推荐
- 清北学堂 2020 国庆J2考前综合强化 Day4
目录 1. 题目 T1 写字符串 题目描述 Sol T2 神奇的数 题目描述 Sol T3 珠子染色 题目描述 Sol T4 病毒扩散 题目描述 Sol 算法 -- 图论 1. 题目 T1 写字符串 ...
- tcp协议传输中的粘包问题
什么是粘包问题 tcp是流体协议. 其nagle算法会将数据量较小. 并且发送间隔时间较短的多个数据包合并为一个发送. 网络传输的时候是一段一段字节流的发送. 在接收方看来根本不知道字节流从何开始. ...
- js实现两种99乘法表
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- GP查询表状态常用SQL
- 技术分享 | MySQL Group Replication集群对IP地址的限制导致的一些问题与解决办法
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 1. 遇到问题 测试人员小玲准备在docker环境中部署MGR集群进行一些测试,她有三个容器,容器IP分别是: 172.3 ...
- 推荐几款最好用的MySQL开源客户端,建议收藏!
一.摘要 众所周知,MYSQL 是目前使得最广泛.最流行的数据库技术之一,为了更方便的管理数据库,市场上出现了大量软件公司和个人开发者研发的客户端工具,比如我们所熟知的比较知名的客户端: Navica ...
- Angular 新建项目错误:The Schematic workflow failed. See above
记录踩坑填坑,有不正之处请指出 错误 解决方法1 npm config set registry https://registry.npmjs.org/ 也可使用淘宝镜像 npm config set ...
- 听,引擎的声音「GitHub 热点速览 v.22.33」
这期的热点速览异常 Cool,因为有呜呜声内燃机引擎加成的 engine-simengine-sim 坐镇,听到如此曼妙的引擎声,相比你的人生也在高速上升吧.还有,自己搭建个服务就能在本地用上 AI ...
- 【ARK UI】HarmonyOS 从相册选择图片并显示到Image组件上
参考资料 [Harmony OS][ARK UI]ETS 上下文基本操作 [Harmony OS][ARK UI]ets使用startAbility或startAbilityForResult方式 ...
- 面试题:Java序列化与反序列化
目录 序列化和反序列化的概念 应用场景? 序列化实现的方式 继承Serializable接口,普通序列化 继承Externalizable接口,强制自定义序列化 serialVersionUID的作用 ...