【重学webpack系列——webpack5.0】
1-15节主要讲webpack的使用,当然,建议结合《webpack学完这些就够了》一起学习。
从本节开始,专攻webpack原理,只有深入原理,才能学到webpack设计的精髓,从而将技术点运用到实际项目中。
可以点击上方专栏订阅哦。
以下是本节正文:
由于webpack基本配置无法满足开发者需求,所以需要借助plugin对webpack构建流程进行修改,来达到改变或优化webpack编译结果的目的。
1.你有没有开发过插件,你是怎么开发开发插件的,你开发了什么插件?(同一类问题面试点
)
- 一般来说,插件是一个类
- 类上有一个apply方法,一般我们插件的逻辑就写在这个apply方法内。因为apply方法在安装插件时,会被webpack compiler调用,并且将compiler传给apply方法,也就是参数参数是compiler。
- 由于webpack的事件流是由tapable前后贯穿的,所以webpack在内部提供了很多钩子,我们在apply方法中去注册特定钩子的事件,那么webpack就会在特定的时机来调用这些钩子对应的事件函数。
- 比如:资源编译结束我需要将文件压缩存档,那么在apply方法中就可以写compiler.hooks.done.tap(‘XXXPlugin’, 压缩存档的回调函数),这个代码的意思就是我在done的这个时机去注册一个事件,tap方法就是注册事件,事件名是第一个参数’xxxPlugin’,事件执行的内容就是压缩存档的回调函数。其实这个done源码中对应的就是tapable的AsyncSeriesHook异步串行钩子,相当于在这个钩子上tap了一个事件。那么等webpack编译完成的时候,就会触发tapable的callAsync函数,这个函数的意思就是异步触发之前done的时候注册的钩子。那么也就是在编译完成的时候回执行压缩存档的回调函数。这就完成了一个插件开发了。
- 其实,tapable就是一个订阅发布,tap的时候注册一些事件,放到队列中,然后call的时候去一个个触发,只不过tapable在触发的时候根据不同的钩子类型改变了触发的顺序,比如SyncBailHook注册的钩子,触发的时候,一旦有返回值,就不继续执行下个事件函数了。当然源码中也不是直接循环调用这些事件函数的,而是new了一个Function,通过对每个队列构造一个函数,去执行的。
- 另外,插件有两个重要的对象,一个是compiler,另一个是compilation:
- compiler,这个webpack编译过程就一个compiler,代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
- compilation:每次资源构建都会有一个compilation,所以它代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
- 最后,钩子的使用的话就很简单,在webpack配置文件中,new一个插件这个类,然后传一些配置参数进去就可以了。webpack会自动去调apply方法的。
2.webpack相关问答
1.问:webpack配置文件中,插件的顺序可以随意改吗?
有些可以,有些不可以。
哪些不可以呢?监听的是同一个钩子的,一般不能随意改,因为一旦顺序变换了,钩子一触发,结果也会变化。
如果不是同一个钩子的,一般来说是可以改的。具体也要考虑业务流程。
2.问:你有开发过webpack-plugin吗?
答:有,然后举个例子,要提到apply、compiler的干嘛的、compilation是干嘛的,然后说下例子的思路,思路如下:
开发一个一键式部署的插件:
- 一个命令,对应启动webpack的发布插件
- 发布插件主要功能包含:
- 将代码提交到测试分支/正式分支,打tag,删除分支等git操作
- 压缩打包成果物
- 将成果物放到指定的SVN目录下
- 执行脚本,将成果物在SVN下解压,并且push上去
- 通过接口,启动测试环境/正式环境的部署(可根据命令配置)
- 继续问:压缩是怎么实现的?
答:压缩是用jszip库实现的,但是如果让我自己实现的话,也可以。然后把实现原理说一下
- 继续问:devops相关知识
答:去了解下整个devops流程,,特别是阿里云与华为云的实践
讨论:webpack的差量打包。
学习文章:Webpack 插件开发如此简单!_奇舞周刊-优快云博客](https://blog.youkuaiyun.com/qiwoo_weekly/article/details/104549042?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_utm_term~default-1.control&spm=1001.2101.3001.4242)
3.开发的插件举例:
1. 资源文件的产出列表
// 资源文件的产出列表
class AssetPlugin{
constructor(options){
this.options = options;
}
apply(compiler){
// 这个compiler只有一个,每当监听到文件的变化,就会创建一个新的complication
// 每当compiler开启一次新的编译,就会创建一个新的compilation,触发一次compilation事件。
console.log(Object.keys(compiler.hooks))
compiler.hooks.compilation.tap('AssetPlugin', (compilation) => {
compilation.hooks.chunkAsset.tap('AssetPlugin', (chunk, filename) => { // chunk代码块对象,filename文件名
// console.log(chunk, filename)
console.log(Object.keys(compilation.hooks))
})
})
}
}
module.exports = AssetPlugin;
/*
compiler 一般用来监听编译的流程,比如开始、结束、emit等
compilation 一般用来监听编译过程的一些资源,比如chunkAsset等
那么compiler一般能监听的流程(compiler.hooks.后面的属性)可以通过`Object.keys(compiler.hooks)`来查看
那么compilation一般能监听的资源(compilation.hooks.后面的属性)可以通过`Object.keys(compilation.hooks)`来查看
https://webpack.docschina.org/api/compilation-hooks/
compiler.hooks下面的属性:
[ 'initialize',
'shouldEmit',
'done',
'afterDone',
'additionalPass',
'beforeRun',
'run',
'emit',
'assetEmitted',
'afterEmit',
'thisCompilation',
'compilation',
'normalModuleFactory',
'contextModuleFactory',
'beforeCompile',
'compile',
'make',
'finishMake',
'afterCompile',
'watchRun',
'failed',
'invalid',
'watchClose',
'shutdown',
'infrastructureLog',
'environment',
'afterEnvironment',
'afterPlugins',
'afterResolvers',
'entryOption' ]
compilation.hooks下面的属性:
[ 'buildModule',
'rebuildModule',
'failedModule',
'succeedModule',
'stillValidModule',
'addEntry',
'failedEntry',
'succeedEntry',
'dependencyReferencedExports',
'executeModule',
'prepareModuleExecution',
'finishModules',
'finishRebuildingModule',
'unseal',
'seal',
'beforeChunks',
'afterChunks',
'optimizeDependencies',
'afterOptimizeDependencies',
'optimize',
'optimizeModules',
'afterOptimizeModules',
'optimizeChunks',
'afterOptimizeChunks',
'optimizeTree',
'afterOptimizeTree',
'optimizeChunkModules',
'afterOptimizeChunkModules',
'shouldRecord',
'additionalChunkRuntimeRequirements',
'runtimeRequirementInChunk',
'additionalModuleRuntimeRequirements',
'runtimeRequirementInModule',
'additionalTreeRuntimeRequirements',
'runtimeRequirementInTree',
'runtimeModule',
'reviveModules',
'beforeModuleIds',
'moduleIds',
'optimizeModuleIds',
'afterOptimizeModuleIds',
'reviveChunks',
'beforeChunkIds',
'chunkIds',
'optimizeChunkIds',
'afterOptimizeChunkIds',
'recordModules',
'recordChunks',
'optimizeCodeGeneration',
'beforeModuleHash',
'afterModuleHash',
'beforeCodeGeneration',
'afterCodeGeneration',
'beforeRuntimeRequirements',
'afterRuntimeRequirements',
'beforeHash',
'contentHash',
'afterHash',
'recordHash',
'record',
'beforeModuleAssets',
'shouldGenerateChunkAssets',
'beforeChunkAssets',
'additionalChunkAssets',
'additionalAssets',
'optimizeChunkAssets',
'afterOptimizeChunkAssets',
'optimizeAssets',
'afterOptimizeAssets',
'processAssets',
'afterProcessAssets',
'processAdditionalAssets',
'needAdditionalSeal',
'afterSeal',
'renderManifest',
'fullHash',
'chunkHash',
'moduleAsset',
'chunkAsset',
'assetPath',
'needAdditionalPass',
'childCompiler',
'log',
'processWarnings',
'processErrors',
'statsPreset',
'statsNormalize',
'statsFactory',
'statsPrinter',
'normalModuleLoader' ]
*/
2.将产出文件打包的插件
const Jz = require('jszip');
const { RawSource } = require("webpack-sources");
class JsZip{
constructor(options){
this.options = options; // filename是压缩完成后的压缩包的名字,不带后缀
}
apply(compiler){
compiler.hooks.emit.tapAsync('JsPlugin', (compilation, callback) => {
console.log(compilation.assets)
var zip = new Jz();
for (let filename in compilation.assets) {
let source = compilation.assets[filename].source();
zip.file(filename, source);
}
zip.generateAsync({type:"nodebuffer"})
.then((content) => {
compilation.assets[this.options.filename + '.zip'] = new RawSource(content);
callback();
});
})
}
}
module.exports = JsZip;