揭秘webpack插件工作流程和原理

本文详细介绍了Webpack4中的Tapable模块如何关联插件,展示了如何使用SyncHook和AsyncSeriesHook等钩子,以及如何在Compiler和Compilation对象上添加和调用插件。重点讲解了关键的事件监听和执行机制,以及Webpack编译流程中的关键步骤和插件应用场景。
摘要由CSDN通过智能技术生成

* 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类暴露了taptapAsynctapPromise方法,可以根据钩子的同步/异步方式来选择一个函数注入逻辑。

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(负责编译)


开发插件首先要知道compilercompilation 对象是做什么的

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 用法相同,钩子类型不同,也可以在某些钩子上访问 tapAsynctapPromise。

控制台输出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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可获取,包括答案解析。

tion (compilation, callback) {

// compilation.chunks 存放所有代码块,是一个数组

compilation.chunks.forEach(function (chunk) {

// chunk 代表一个代码块

// 代码块由多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块

chunk.forEachModule(function (module) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-Tkp4Vpul-1712762256105)]

[外链图片转存中…(img-tVcIrpov-1712762256106)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-6aJCoAUv-1712762256106)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

文章到这里就结束了,如果觉得对你有帮助可以点个赞哦,如果有需要前端校招面试题PDF完整版的朋友可以点击这里即可获取,包括答案解析。

[外链图片转存中…(img-hiKYrnIi-1712762256107)]

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值