* event-name 为事件名称,注意不要和现有的事件重名
*/
compiler.apply(‘event-name’,params);
compilation.apply(‘event-name’,params);
/**
* 监听事件
*/
compiler.plugin(‘event-name’,function(params){});
compilation.plugin(‘event-name’, function(params){});
Tapable
类暴露了tap
、tapAsync
和tapPromise
方法,可以根据钩子的同步/异步方式来选择一个函数注入逻辑。
tap
同步钩子
compiler.hooks.compile.tap(‘MyPlugin’, params => {
console.log(‘以同步方式触及 compile 钩子。’)
})
tapAsync
异步钩子,通过callback
回调告诉Webpack
异步执行完毕tapPromise
异步钩子,返回一个Promise
告诉Webpack
异步执行完毕
compiler.hooks.run.tapAsync(‘MyPlugin’, (compiler, callback) => {
console.log(‘以异步方式触及 run 钩子。’)
callback()
})
compiler.hooks.run.tapPromise(‘MyPlugin’, compiler => {
return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
console.log(‘以具有延迟的异步方式触及 run 钩子’)
})
})
Tabable用法
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require(“tapable”);
tapable
简单实现一个 SyncHook
class Hook{
constructor(args){
this.taps = []
this.interceptors = [] // 这个放在后面用
this._args = args
}
tap(name,fn){
this.taps.push({name,fn})
}
}
class SyncHook extends Hook{
call(name,fn){
try {
this.taps.forEach(tap => tap.fn(name))
fn(null,name)
} catch (error) {
fn(error)
}
}
}
tapable
是如何将webapck/webpack
插件关联的?
Compiler.js
const { AsyncSeriesHook ,SyncHook } = require(“tapable”);
//创建类
class Compiler {
constructor() {
this.hooks = {
run: new AsyncSeriesHook([“compiler”]), //异步钩子
compile: new SyncHook([“params”]),//同步钩子
};
},
run(){
//执行异步钩子
this.hooks.run.callAsync(this, err => {
this.compile(onCompiled);
});
},
compile(){
//执行同步钩子 并传参
this.hooks.compile.call(params);
}
}
module.exports = Compiler
MyPlugin.js
const Compiler = require(‘./Compiler’)
class MyPlugin{
apply(compiler){//接受 compiler参数
compiler.hooks.run.tap(“MyPlugin”, () => console.log(‘开始编译…’));
compiler.hooks.compile.tapAsync(‘MyPlugin’, (name, age) => {
setTimeout(() => {
console.log(‘编译中…’)
}, 1000)
});
}
}
//这里类似于webpack.config.js的plugins配置
//向 plugins 属性传入 new 实例
const myPlugin = new MyPlugin();
const options = {
plugins: [myPlugin]
}
let compiler = new Compiler(options)
compiler.run()
想要深入了解tapable
的文章可以看看这篇文章:
webpack4
核心模块tapable
源码解析: https://www.cnblogs.com/tugenhua0707/p/11317557.html
理解Compiler(负责编译)
开发插件首先要知道compiler
和 compilation
对象是做什么的
Compiler
对象包含了当前运行Webpack
的配置,包括entry、output、loaders
等配置,这个对象在启动Webpack
时被实例化,而且是全局唯一的。Plugin
可以通过该对象获取到Webpack的配置信息进行处理。
如果看完这段话,你还是没理解compiler
是做啥的,不要怕,接着看。 运行npm run build
,把compiler
的全部信息输出到控制台上console.log(Compiler)
。
compiler
// 为了能更直观的让大家看清楚compiler的结构,里面的大量代码使用省略号(…)代替。
Compiler {
_pluginCompat: SyncBailHook {
…
},
hooks: {
shouldEmit: SyncBailHook {
…
},
done: AsyncSeriesHook {
…
},
additionalPass: AsyncSeriesHook {
…
},
beforeRun: AsyncSeriesHook {
…
},
run: AsyncSeriesHook {
…
},
emit: AsyncSeriesHook {
…
},
assetEmitted: AsyncSeriesHook {
…
},
afterEmit: AsyncSeriesHook {
…
},
thisCompilation: SyncHook {
…
},
compilation: SyncHook {
…
},
normalModuleFactory: SyncHook {
…
},
contextModuleFactory: SyncHook {
…
},
beforeCompile: AsyncSeriesHook {
…
},
compile: SyncHook {
…
},
make: AsyncParallelHook {
…
},
afterCompile: AsyncSeriesHook {
…
},
watchRun: AsyncSeriesHook {
…
},
failed: SyncHook {
…
},
invalid: SyncHook {
…
},
watchClose: SyncHook {
…
},
infrastructureLog: SyncBailHook {
…
},
environment: SyncHook {
…
},
afterEnvironment: SyncHook {
…
},
afterPlugins: SyncHook {
…
},
afterResolvers: SyncHook {
…
},
entryOption: SyncBailHook {
…
},
infrastructurelog: SyncBailHook {
…
}
},
…
outputPath: ‘’,//输出目录
outputFileSystem: NodeOutputFileSystem {
…
},
inputFileSystem: CachedInputFileSystem {
…
},
…
options: {
//Compiler对象包含了webpack的所有配置信息,entry、module、output、resolve等信息
entry: [
‘babel-polyfill’,
‘/Users/frank/Desktop/fe/fe-blog/webpack-plugin/src/index.js’
],
devServer: { port: 3000 },
output: {
…
},
module: {
…
},
plugins: [ MyWebpackPlugin {} ],
mode: ‘production’,
context: ‘/Users/frank/Desktop/fe/fe-blog/webpack-plugin’,
devtool: false,
…
performance: {
maxAssetSize: 250000,
maxEntrypointSize: 250000,
hints: ‘warning’
},
optimization: {
…
},
resolve: {
…
},
resolveLoader: {
…
},
infrastructureLogging: { level: ‘info’, debug: false }
},
context: ‘/Users/frank/Desktop/fe/fe-blog/webpack-plugin’,//上下文,文件目录
requestShortener: RequestShortener {
…
},
…
watchFileSystem: NodeWatchFileSystem {
//监听文件变化列表信息
…
}
}
Compiler源码精简版代码解析
源码地址(948行):https://github.com/webpack/webpack/blob/master/lib/Compiler.js
const { SyncHook, SyncBailHook, AsyncSeriesHook } = require(“tapable”);
class Compiler {
constructor() {
// 1. 定义生命周期钩子
this.hooks = Object.freeze({
// …只列举几个常用的常见钩子,更多hook就不列举了,有兴趣看源码
done: new AsyncSeriesHook([“stats”]),//一次编译完成后执行,回调参数:stats
beforeRun: new AsyncSeriesHook([“compiler”]),
run: new AsyncSeriesHook([“compiler”]),//在编译器开始读取记录前执行
emit: new AsyncSeriesHook([“compilation”]),//在生成文件到output目录之前执行,回调参数: compilation
afterEmit: new AsyncSeriesHook([“compilation”]),//在生成文件到output目录之后执行
compilation: new SyncHook([“compilation”, “params”]),//在一次compilation创建后执行插件
beforeCompile: new AsyncSeriesHook([“params”]),
compile: new SyncHook([“params”]),//在一个新的compilation创建之前执行
make:new AsyncParallelHook([“compilation”]),//完成一次编译之前执行
afterCompile: new AsyncSeriesHook([“compilation”]),
watchRun: new AsyncSeriesHook([“compiler”]),
failed: new SyncHook([“error”]),
watchClose: new SyncHook([]),
afterPlugins: new SyncHook([“compiler”]),
entryOption: new SyncBailHook([“context”, “entry”])
});
// …省略代码
}
newCompilation() {
// 创建Compilation对象回调compilation相关钩子
const compilation = new Compilation(this);
//…一系列操作
this.hooks.compilation.call(compilation, params); //compilation对象创建完成
return compilation
}
watch() {
//如果运行在watch模式则执行watch方法,否则执行run方法
if (this.running) {
return handler(new ConcurrentCompilationError());
}
this.running = true;
this.watchMode = true;
return new Watching(this, watchOptions, handler);
}
run(callback) {
if (this.running) {
return callback(new ConcurrentCompilationError());
}
this.running = true;
process.nextTick(() => {
this.emitAssets(compilation, err => {
if (err) {
// 在编译和输出的流程中遇到异常时,会触发 failed 事件
this.hooks.failed.call(err)
};
if (compilation.hooks.needAdditionalPass.call()) {
// …
// done:完成编译
this.hooks.done.callAsync(stats, err => {
// 创建compilation对象之前
this.compile(onCompiled);
});
}
this.emitRecords(err => {
this.hooks.done.callAsync(stats, err => {
});
});
});
});
this.hooks.beforeRun.callAsync(this, err => {
this.hooks.run.callAsync(this, err => {
this.readRecords(err => {
this.compile(onCompiled);
});
});
});
}
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
//触发make事件并调用addEntry,找到入口js,进行下一步
this.hooks.make.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
// 封装构建结果(seal),逐次对每个module和chunk进行整理,每个chunk对应一个入口文件
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
// 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,
// 不然运行流程将会一直卡在这不往下执行
return callback(null, compilation);
});
});
});
});
});
});
}
emitAssets(compilation, callback) {
const emitFiles = (err) => {
//…省略一系列代码
// afterEmit:文件已经写入磁盘完成
this.hooks.afterEmit.callAsync(compilation, err => {
if (err) return callback(err);
return callback();
});
}
// emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(这是最后一次修改最终文件的机会)
this.hooks.emit.callAsync(compilation, err => {
if (err) return callback(err);
outputPath = compilation.getPath(this.outputPath, {});
mkdirp(this.outputFileSystem, outputPath, emitFiles);
});
}
// …省略代码
}
apply
方法中插入钩子的一般形式如下:
// compiler提供了compiler.hooks,可以根据这些不同的时刻去让插件做不同的事情。
compiler.hooks.阶段.tap函数(‘插件名称’, (阶段回调参数) => {
});
compiler.run(callback)
理解Compilation
Compilation
对象代表了一次资源版本构建。当运行 webpack
开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation
,从而生成一组新的编译资源。一个 Compilation
对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息,简单来讲就是把本次打包编译的内容存到内存里。Compilation
对象也提供了插件需要自定义功能的回调,以供插件做自定义处理时选择使用拓展。
简单来说,Compilation
的职责就是构建模块和Chunk,并利用插件优化构建过程。
和 Compiler
用法相同,钩子类型不同,也可以在某些钩子上访问 tapAsync
和 tapPromise。
控制台输出console.log(compilation)
通过 Compilation
也能读取到 Compiler
对象。
源码2000多行,看不动了- -,有兴趣的可以自己看看。 https://github.com/webpack/webpack/blob/master/lib/Compilation.js
介绍几个常用的Compilation Hooks
buildModule(SyncHook)
:在模块开始编译之前触发,可以用于修改模
succeedModule(SyncHook)
:在模块开始编译之前触发,可以用于修改模块
finishModules(AsyncSeriesHook)
:当所有模块都编译成功后被调用
seal(SyncHook)
:当一次compilation
停止接收新模块时触发
optimizeDependencies(SyncBailHook)
:在依赖优化的开始执行
optimize(SyncHook)
:在优化阶段的开始执行
optimizeModules(SyncBailHook)
:在模块优化阶段开始时执行,插件可以在这个钩子里执行对模块的优化,回调参数:modules
optimizeChunks(SyncBailHook)
:在代码块优化阶段开始时执行,插件可以在这个钩子里执行对代码块的优化,回调参数:chunks
optimizeChunkAssets(AsyncSeriesHook)
:优化任何代码块资源,这些资源存放在compilation.assets
上。一个 chunk
有一个 files
属性,它指向由一个chunk
创建的所有文件。任何额外的 chunk
资源都存放在 compilation.additionalChunkAssets
上。回调参数:chunks
optimizeAssets(AsyncSeriesHook)
:优化所有存放在 compilation.assets
的所有资源。回调参数:assets
|
Compiler 和 Compilation 的区别
Compiler
代表了整个 Webpack
从启动到关闭的生命周期,而 Compilation
只是代表了一次新的编译,只要文件有改动,compilation
就会被重新创建。
常用 API
插件可以用来修改输出文件、增加输出文件、甚至可以提升 Webpack
性能、等等,总之插件通过调用Webpack
提供的 API
能完成很多事情。 由于 Webpack
提供的 API
非常多,有很多 API
很少用的上,又加上篇幅有限,下面来介绍一些常用的 API。
读取输出资源、代码块、模块及其依赖
有些插件可能需要读取 Webpack
的处理结果,例如输出资源、代码块、模块及其依赖,以便做下一步处理。 在 emit 事件发生时,代表源文件的转换和组装已经完成,在这里可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容。 插件代码如下:
class Plugin {
apply(compiler) {
compiler.plugin(‘emit’, function (compilation, callback) {
// compilation.chunks 存放所有代码块,是一个数组
compilation.chunks.forEach(function (chunk) {
// chunk 代表一个代码块
// 代码块由多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块
chunk.forEachModule(function (module) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
总结一下这三次面试下来我的经验是:
-
一定不要死记硬背,要理解原理,否则面试官一深入就会露馅!
-
代码能力一定要注重,尤其是很多原理性的代码(之前两次让我写过Node中间件,Promise.all,双向绑定原理,被虐的怀疑人生)!
-
尽量从面试官的问题中表现自己知识的深度与广度,让面试官发现你的闪光点!
-
多刷面经!
我把所有遇到的面试题都做了一个整理,并且阅读了很多大牛的博客之后写了解析,免费分享给大家,算是一个感恩回馈吧,有需要的朋友【点击我】免费获取。祝大家早日拿到自己心怡的工作!
篇幅有限,仅展示部分内容
mg-3OoPFmt9-1712367404121)]
[外链图片转存中…(img-ta1bHwsd-1712367404122)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-7ZjT3NnG-1712367404122)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
总结一下这三次面试下来我的经验是:
-
一定不要死记硬背,要理解原理,否则面试官一深入就会露馅!
-
代码能力一定要注重,尤其是很多原理性的代码(之前两次让我写过Node中间件,Promise.all,双向绑定原理,被虐的怀疑人生)!
-
尽量从面试官的问题中表现自己知识的深度与广度,让面试官发现你的闪光点!
-
多刷面经!
我把所有遇到的面试题都做了一个整理,并且阅读了很多大牛的博客之后写了解析,免费分享给大家,算是一个感恩回馈吧,有需要的朋友【点击我】免费获取。祝大家早日拿到自己心怡的工作!
篇幅有限,仅展示部分内容