1.源码细读-深入了解terser-webpack-plugin的插插件实现
2.webpack5loader和plugin原理解析
3.WebPack插件实现:打包之后自动混淆加密JS文件
4.手摸手写个webpack plugin
5.详解webpackpluginçåçåç¼åä¸ä¸ªplugin
6.webpack工作流程/原理
源码细读-深入了解terser-webpack-plugin的实现
terser-webpack-plugin 是一个基于 webpack 的插件,它利用 terser 库对 JavaScript 代码进行压缩和混淆。原理其核心功能在于通过在 webpack 的插插件运行时钩子 optimizeChunkAssets 中注册,实现了代码优化过程。原理在 apply 函数中,插插件它获取 compilation 实例,原理脉聊源码19并通过 tapPromise 注册一个异步任务,插插件当 webpack 执行优化阶段时,原理每个 chunk 会触发这个任务,插插件执行 minify 函数进行压缩处理。原理
optimise 函数是插插件实际的任务处理入口,它负责具体的原理优化流程。函数内部,插插件scheduleTask 负责并行处理,原理如果开启 parallel 模式,插插件会利用jest-worker提供的线程池进行并发工作,线程池管理复杂,根据 node 版本不同采用 worker_threads 或 child_process。minify 函数则是压缩和混淆代码的核心操作,它直接使用 terser 库完成任务。
总的来说,terser-webpack-plugin 的优化流程包括在 webpack 的优化阶段对代码进行压缩,使用 Jest 的 worker 线程池进行并行处理,以及通过 terser 库的实际压缩操作。理解这些核心环节,可以帮助开发者更深入地掌握该插件的使用和工作原理。
webpack5loader和plugin原理解析
大家好,今天为大家解析下loader和plugin一、区别loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,源码时代退款最终一起打包到指定的文件中
plugin赋予了Webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决loader无法实现的其他事从整个运行时机上来看,如下图所示:
可以看到,两者在运行时机上的区别:
loader运行在打包文件之前plugins在整个编译周期都起作用在Webpack运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果
对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程
下面我们来看看loader和plugin实现的原理
Loader原理loader概念帮助webpack将不同类型的文件转换为webpack可识别的模块。
loader执行顺序分类
pre:前置loader
normal:普通loader
inline:内联loader
post:后置loader
执行顺序
4类loader的执行优级为:pre>normal>inline>post。
相同优先级的loader执行顺序为:从右到左,从下到上。
例如:
//此时loader执行顺序:loader3-loader2-loader1module:{ rules:[{ test:/\.js$/,loader:"loader1",},{ test:/\.js$/,loader:"loader2",},{ test:/\.js$/,loader:"loader3",},],},//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},使用loader的方式
配置方式:在webpack.config.js文件中指定loader。(pre、normal、postloader)
内联方式:在每个import语句中显式指定loader。(inlineloader)
开发一个loader1.最简单的loader//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};它接受要处理的源码作为参数,输出转换后的js代码。
2.loader接受的参数content源文件的内容
mapSourceMap数据
meta数据,可以是任何内容
loader分类1.同步loadermodule.exports=function(content,map,meta){ returncontent;};this.callback方法则更灵活,因为它允许传递多个参数,而不仅仅是content。
module.exports=function(content,map,meta){ //传递map,让source-map不中断//传递meta,让下一个loader接收到其他参数this.callback(null,content,map,meta);return;//当调用callback()函数时,总是返回undefined};2.异步loadermodule.exports=function(content,map,meta){ constcallback=this.async();//进行异步操作setTimeout(()=>{ callback(null,result,map,meta);},);};由于同步计算过于耗时,在Node.js这样的源码解析基础单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的loader异步化。但如果计算量很小,同步loader也是可以的。
3.RawLoader默认情况下,资源文件会被转化为UTF-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的Buffer。
module.exports=function(content){ //content是一个Buffer数据returncontent;};module.exports.raw=true;//开启RawLoader4.PitchingLoadermodule.exports=function(content){ returncontent;};module.exports.pitch=function(remainingRequest,precedingRequest,data){ console.log("dosomethings");};webpack会先从左到右执行loader链中的每个loader上的pitch方法(如果有),然后再从右到左执行loader链中的每个loader上的普通loader方法。
在这个过程中如果任何pitch有返回值,则loader链被阻断。webpack会跳过后面所有的的pitch和loader,直接进入上一个loader。
loaderAPI方法名含义用法this.async异步回调loader。返回this.callbackconstcallback=this.async()this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err,content,sourceMap?,meta?)this.getOptions(schema)获取loader的optionsthis.getOptions(schema)this.emitFile产生一个文件this.emitFile(name,content,sourceMap)this.utils.contextify返回一个相对路径this.utils.contextify(context,request)this.utils.absolutify返回一个绝对路径this.utils.absolutify(context,request)更多文档,请查阅webpack官方loaderapi文档
手写clean-log-loader作用:用来清理js代码中的console.log
//loaders/clean-log-loader.jsmodule.exports=functioncleanLogLoader(content){ //将console.log替换为空returncontent.replace(/console\.log\(.*\);?/g,"");};手写banner-loader作用:给js代码添加文本注释
loaders/banner-loader/index.js
constschema=require("./schema.json");module.exports=function(content){ //获取loader的options,同时对options内容进行校验//schema是options的校验规则(符合JSONschema规则)constoptions=this.getOptions(schema);constprefix=`/**Author:${ options.author}*/`;return`${ prefix}\n${ content}`;};loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},0手写babel-loader作用:编译js代码,将ES6+语法编译成ES5-语法。
下载依赖
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},1loaders/babel-loader/index.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},2loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},3手写file-loader作用:将文件原封不动输出出去
下载包
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},4loaders/file-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},5loader配置
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},6手写style-loader作用:动态创建style标签,插入js中的样式代码,使样式生效。
loaders/style-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},7Plugin原理Plugin的作用通过插件我们可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。
Plugin工作原理webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是学校漫游源码一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack通过Tapable来组织这条复杂的生产线。webpack在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出Webpack」
站在代码逻辑的角度就是:webpack在编译代码过程中,会触发一系列Tapable钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当webpack构建的时候,插件注册的事件就会随着钩子的触发而执行了。
Webpack内部的钩子什么是钩子钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。
TapableTapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks,在Tapable源码中可以看到,他们是:
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},8Tapable还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
tap:可以注册同步钩子和异步钩子。
tapAsync:回调方式注册异步钩子。
tapPromise:Promise方式注册异步钩子。
Plugin构建对象Compilercompiler对象中保存着完整的盼宇源码Webpack环境配置,每次启动webpack构建时它都是一个独一无二,仅仅会创建一次的对象。
这个对象会在首次启动Webpack时创建,我们可以通过compiler对象上访问到Webapck的主环境配置,比如loader、plugin等等配置信息。
它有以下主要属性:
compiler.options可以访问本次启动webpack时候所有的配置文件,包括但不限于loaders、entry、output、plugin等等完整配置信息。
compiler.inputFileSystem和compiler.outputFileSystem可以进行文件操作,相当于Nodejs中fs。
compiler.hooks可以注册tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑。
compilerhooks文档
Compilationcompilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。
一个compilation对象会对构建依赖图中所有模块,进行编译。在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。
它有以下主要属性:
compilation.modules可以访问所有模块,打包的每一个文件都是一个模块。
compilation.chunkschunk即是多个modules组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。
compilation.assets可以访问本次打包生成所有文件的结果。
compilation.hooks可以注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改。
compilationhooks文档
生命周期简图开发一个插件最简单的插件plugins/test-plugin.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},9注册hook//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};0启动调试通过调试查看compiler和compilation对象数据情况。
package.json配置指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};1运行指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2此时控制台输出以下内容:
PSC:\Users\\Desktop\source>//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2>source@1.0.0debug>node--inspect-brk./node_modules/webpack-cli/bin/cli.jsDebuggerlisteningonws://.0.0.1:/ea-7b--a7-fccForhelp,see:/post/开发思路:
我们需要借助html-webpack-plugin来实现
在html-webpack-plugin输出index.html前将内联runtime注入进去
删除多余的runtime文件
如何操作html-webpack-plugin?官方文档
实现:
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};7WebPack插件实现:打包之后自动混淆加密JS文件
在WebPack中实现对打包生成的JS文件进行混淆加密,可采用JShaman插件,确保代码安全性和可维护性。
插件实现步骤如下:
1、创建插件文件JShamanObfuscatorPlugin.js,编写代码用于混淆加密JS文件。该插件需实现混淆加密功能,以便对编译后的JS文件进行保护。
2、在webpack.config.js配置文件中引入JShamanObfuscatorPlugin,并将其添加到plugins数组中。配置时需确保插件能够正确识别并处理打包过程中的JS文件,实现混淆加密。
功能测试阶段,通过编写Example.js和Mod.js两个示例文件来验证混淆加密效果。
Example.js示例代码如下,用于展示未混淆加密前的JS代码结构。
Mod.js示例代码如下,展示未混淆加密前的JS代码逻辑。
运行Webpack编译,生成的bundle.js文件中,JS代码已被混淆加密处理。加密后的代码呈现出高度不可读性,有效保护了源代码信息。
相比之下,未使用JShaman插件的bundle.js文件,其代码结构和逻辑清晰可读。对比显示,插件实现的混淆加密功能显著提高了代码的安全性和复杂性。
手摸手写个webpack plugin
本周的任务是深入了解和编写webpack插件。插件是什么?它实际上是一个具有 apply 方法的JavaScript对象,更准确地说,就是一种函数。它可以是普通的函数,也可以是特殊的函数,用于实现特定功能。
相比loader,插件可以解决一些loader无法处理的场景,比如在打包开始前进行环境配置、打包结束后发送通知邮件等。例如,HtmlWebpackPlugin插件可以帮助我们在dist文件夹下自动生成index.html文件,并在其中自动引入打包后的css和js脚本文件。安装并配置插件后,运行打包指令,可以看到生成的index.html文件自动引入了生成的main.js脚本文件。
接下来,我们深入探讨HtmlWebpackPlugin的源码,了解如何编写插件。通过观察源码,我们可以发现插件是通过apply方法在特定生命周期内执行操作。以HtmlWebpackPlugin为例,其在初始化阶段会利用Compiler提供的hook函数,实现生成html文件并引入资源的功能。
为了更深入理解插件的工作原理,我们可以执行yarn run webpack命令,跟踪webpack的运行过程,了解不同hook函数的调用时机和作用。例如,afterDone钩子函数会在打包完成后触发,用于执行发送通知邮件等操作。通过观察Compiler类的实现,我们可以理解如何在插件中访问和利用Compiler实例对象,以及如何在特定钩子函数的回调中实现自定义逻辑。
接下来,我们以一句话需求为例,实现一个在webpack打包后发送通知邮件的插件。首先,我们需要安装nodemailer库以实现邮件发送功能。然后,根据需求编写插件代码,利用Compiler提供的hook函数,如afterDone,实现邮件发送逻辑。运行webpack后,我们可以看到通知邮件被成功发送。为了方便他人使用,我们还可以将自定义插件发布到npm仓库。
在插件开发中,还有一些重要的概念,如SyncHook和AsyncHook,它们分别用于同步和异步操作。另外,了解如何在插件中正确使用这些概念,对于提高插件的可复用性和功能性至关重要。官方文档提供了关于编写插件的详细指导,建议开发者深入学习。
总之,通过本篇文章的学习,我们不仅掌握了webpack插件的基本概念和使用方法,还深入探讨了其原理和实战应用。希望这些知识能帮助开发者在实际项目中灵活运用插件,提升开发效率。
详解webpackpluginçåçåç¼åä¸ä¸ªplugin
plugin解å³äºä»ä¹é®é¢ï¼
plugin解å³äºWebpackæ建çå½å¨æè¿ç¨ä¸çåè½å®å¶é®é¢ï¼å¯ä»¥å©ç¨pluginåä¸å°webpackæ建æµç¨ä¸çå个é¶æ®µå¹¶å«æåä¸äºä»£ç å¤çã
æ¯å¦ï¼æå åéè¦çæä¸ä¸ªhtmlæ件ï¼é£ä¹å°±å¯ä»¥ä½¿ç¨html-webpack-pluginãè¿æï¼å¨æå ä¹åædistæ件å é¤ï¼å°±å¯ä»¥ä½¿ç¨clean-webpack-pluginã
webpackæ¬èº«å°±æ¯ä¸ä¸ªæ建è¿ç¨çç¶ææºï¼å ¶èªèº«çæ ¸å¿åè½ä¹æ¯æ建å¨loaderåpluginçæºå¶ä¸çã
compileråcompilationå ·ä½æ¯å¹²ä»ä¹çï¼é¦å æ们æ¥çä¸ä¸ªwebpackèªå¸¦çæ件BannerPlugin代ç ï¼å ¶å®webpackçå¾å¤æ ¸å¿å ¬å¸å°±æ¯å©ç¨æ件æ¥å®ç°çã
æ件çæ ¼å¼
ä¸ä¸ªJavaScriptå½æ°æJavaScriptç±»;
å¨å®ååä¸å®ä¹çapplyæ¹æ³ï¼ä¼å¨å®è£ æ件æ¶è¢«è°ç¨ï¼å¹¶è¢«webpackcompilerè°ç¨ä¸æ¬¡;
æå®ä¸ä¸ªè§¦åå°webpackæ¬èº«çäºä»¶é©åï¼å³hooksï¼ç¨äºç¹å®æ¶æºå¤çé¢å¤çé»è¾;
classBannerPlugin{ constructor(options){ this.options=options;}apply(compiler){ constoptions=this.options;constbanner=this.banner;compiler.hooks.compilation.tap("BannerPlugin",compilation=>{ compilation.hooks.processAssets.tap({ name:"BannerPlugin"},()=>{ for(constchunkofcompilation.chunks){ for(constfileofchunk.files){ constdata={ chunk,filename:file};//çæ注éconstcomment=compilation.getPath(banner,data);//æ注éå å ¥å°æ件ä¸compilation.updateAsset(file,old=>{ constsource=options.footer?newConcatSource(old,"\n",comment):newConcatSource(comment,"\n",old);returnsource;}});}}}}}}ä»ä»£ç ä¸åºç°äºcompileråcompilationï¼é£å®ä»¬å°åºæ¯ä»ä¹å¢ï¼
compilercompiler模åæ¯Webpackææ ¸å¿ç模åãæ¯æ¬¡æ§è¡Webpackæ建çæ¶åï¼å¨Webpackå é¨ï¼ä¼é¦å å®ä¾åä¸ä¸ªCompiler对象ï¼ç¶åè°ç¨å®çrunæ¹æ³æ¥å¼å§ä¸æ¬¡å®æ´çç¼è¯è¿ç¨ãcompiler对象代表äºå®æ´çwebpackç¯å¢é ç½®ï¼æ件å¯ä»¥éè¿å®è·åå°webpackçé 置信æ¯ï¼å¦entryãoutputãmoduleçé ç½®ã
compileré©åcompileræå¾å¤é©åï¼ä¸é¢åªä»ç»å¸¸ç¨çå 个:é©åå?|Tapableç±»å|触åæ¶æº|ä¼ å ¥callbackçåæ°?||--------------------|-----------------|------------------------------------------|---------------------------------||entryOption?|SyncBailHook|å¨webpackä¸çentryé ç½®å¤çè¿ä¹å|contextï¼entry||afterPlugins|SyncHook?|åå§åå®å ç½®æ件ä¹å|compiler||environment?|SyncHook?|åå¤ç¼è¯ç¯å¢ï¼webpackpluginsé ç½®åå§åå®æä¹å?|compiler||beforeRun?|AsyncSeriesHook?|å¼å§æ£å¼ç¼è¯ä¹å?|compiler||run|AsyncSeriesHook?|å¼å§ç¼è¯ä¹åï¼è¯»årecordsä¹åï¼|compiler||compile|SyncHook?|ä¸æ¬¡compilationç¼è¯å建ä¹å|compilationParams||compilation?|SyncHook?|compilationå建æåä¹å?|compilationï¼compilationParams||emit|AsyncSeriesHook?|çæèµæºå°outputç®å½ä¹å?|compilation?||done|AsyncSeriesHook?|compilationå®æä¹å?|stats?||failed|SyncHook?|compilation失败|
æ´ä¸ªCompilerå®æ´å°å±ç°äºWebpackçæ建æµç¨ï¼
åå¤é¶æ®µï¼makeä¹ååçäºæ é½å±äºåå¤é¶æ®µï¼è¿é¶æ®µçcalbackå ¥å以compiler为主ï¼
ç¼è¯é¶æ®µï¼è¿é¶æ®µä»¥compilationçé©å为主ï¼calbackå ¥å以compilation为主ï¼
产åºé¶æ®µï¼è¿é¶æ®µä»compilationå¼å§ï¼æååå°Compileré©åä¸ï¼calbackä¼ å ¥åæ°æ¯è·ç»æç¸å ³çæ°æ®ï¼å æ¬statsãerrorã
compilationå¨compilationé¶æ®µï¼æ¨¡åä¼è¢«å è½½(loaded)ãå°å(sealed)ãä¼å(optimized)ãåå(chunked)ãåå¸(hashed)åéæ°å建(restored)ï¼Compilation对象å å«äºå½åç模åèµæºãç¼è¯çæèµæºãååçæ件çãCompilation对象ä¹æä¾äºå¾å¤äºä»¶åè°ä¾æ件åæ©å±ï¼éè¿Compilationä¹è½è¯»åå°Compiler对象ã
Compilationé©å
å¨Compilationä¸å¤çç对象åå«æ¯moduleãchunkãassetsï¼ç±modulesç»æchunksï¼ç±chunksçæassetsï¼å¤ç顺åºæ¯ï¼moduleâmodulesâchunksâassetsï¼å ä»å个moduleå¼å§å¤çï¼æ¥æ¾ä¾èµå ³ç³»ï¼æåå®æå个moduleå¤çï¼å®æå ¨é¨modulesä¹åï¼å¼å§chunksé¶æ®µå¤çï¼æåå¨æ ¹æ®ä¼åé ç½®ï¼æéçæassetsã
æ以æ´ä¸ªCompilationççå½å¨æé©åè½ç¶æ¯è¾å¤ï¼ä½æ¯å¤§è§å¾ä¸æ¯å´ç»è¿ä¸ªé¡ºåºè¿è¡çï¼å ·ä½çé©åå¯ä»¥æ¥çwebpackå®ç½ã
Stats对象å¨Webpackçåè°å½æ°ä¸ä¼å¾å°stats对象ãè¿ä¸ªå¯¹è±¡å®é æ¥èªäºCompilation.getStats()ï¼è¿åçæ¯ä¸»è¦å«æmodulesãchunksåassetsä¸ä¸ªå±æ§å¼ç对象ã
modulesï¼è®°å½äºææ解æåç模åï¼
chunksï¼è®°å½äºææchunkï¼
assetsï¼è®°å½äºææè¦çæçæ件ã
æäºå¯¹compilercompilationçç解ï¼é£ç°å¨æ¥ççBannerPluginçå®ç°ï¼è¿ä¸ªæ件çåè½æ¯å¨æåçæçæ件ç头é¨å ä¸ä¸æ®µæ们èªå®ä¹ç注éï¼é£ä¹å®çæ§è¡æ¶æºè¯å®æ¯å¨ç¼è¯å®æä¹åï¼çææå æ件ä¹é´ï¼ä¹å°±æ¯å¨compiler.hooks.compilationè¿ä¸ªå¤§é©åä¸é¢çprocessAssetsé©åéé¢æ§è¡æ们çé»è¾ã
ç¼åpluginplugin:å¨æ件尾é¨æå ¥ä¸æ®µæ³¨éé¦å å建ä¸ä¸ªplugins/FootPlugin.jsï¼ä»£ç å¦ä¸:
const{ ConcatSource}=require('webpack-sources')classFootPlugin{ constructor(options){ this.options=options}apply(compiler){ compiler.hooks.compilation.tap('FootPlugin',compilation=>{ compilation.hooks.processAssets.tap('FootPlugin',()=>{ for(constchunkofcompilation.chunks){ for(constfileofchunk.files){ console.log('file--',file)//bundle.js//å®ä¹æ³¨éçå 容constcomment=`/*${ this.options.banner}*/`compilation.updateAsset(file,old=>{ //æ注éåæ§ä»£ç è¿è¡æ¼æ¥returnnewConcatSource(old,'\n',comment)})}}})})}}module.exports=FootPluginwebpack.config.js
constFootPlugin=require('./plugins/FootPlugin')module.exports={ plugins:[newwebpack.BannerPlugin({ banner:'欢è¿å¦ä¹ '}),newFootPlugin({ banner:'ç»æå¦ä¹ '})]}å¯ä»¥çå°å¨bundle.jsçå¼å¤´åç»å°¾é½æ对åºç注éã
plugin:æä»¶è¶ è¿ä¸å®å¤§å°æ¶ç»åºè¦åconst{ resolve}=require('path')constfs=require('fs')classBundleSizeWebpackPlugin{ constructor(options){ this.options=options}apply(compiler){ const{ sizeLimit}=this.optionsconsole.log('bundlesizeplugin')//å¨ç¼è¯å®æåï¼æ§è¡åè°ï¼æ¿å°æå åæ件路å¾ï¼ç¶å读åæ件信æ¯è·åæ件大å°ï¼ç¶åå®ä¹ä¸äºé»è¾compiler.hooks.done.tap('BundleSizePlugin',stats=>{ const{ path,filename}=stats.compilation.outputOptionsconstbundlePath=resolve(path,filename)const{ size}=fs.statSync(bundlePath)constbundleSize=size/if(bundleSize<sizeLimit){ console.log('safe:bundle-size',bundleSize,'\nsizelimit:',sizeLimit)}else{ console.warn('unsafe:bundle-size',bundleSize,'\nsizelimit:',sizeLimit)}})}}module.exports=BundleSizeWebpackPluginæ¬ç« å°è¿éå°±ç»æäºï¼æ们å¼å§ä»ç»äºwebpackçæ ¸å¿æ¦å¿µï¼æäºå¯¹webpackçåºæ¬é ç½®çäºè§£ï¼æ¥çå©ç¨css-loaderåstyle-loader对webpackçloaderæºå¶è¿è¡äºè¯¦ç»åæï¼æåï¼å¯¹webpackpluginçå·¥ä½æºå¶åæµç¨è¿è¡äºæ¢³çï¼å¹¶æåäºä¸¤ä¸ªpluginï¼è®©ä½ 对pluginä¸åè§å¾é¥ä¸å¯åãæäºè¿äºåç½®ç¥è¯ï¼å°±å¯ä»¥å¯¹æ们åç项ç®è¿è¡å·¥ç¨åçæ¹é äºãæå¾ ä½ çå¦ä¹ ã
åæï¼/post/webpack工作流程/原理
webpack 从启动构建到输出结果,贯穿了一系列关键事件节点,涉及初始化参数、配置合并与插件加载、开始编译、确定入口、编译模块以及完成模块编译并输出等核心步骤,最后输出完成,整个流程高效而有序。
初始化参数是构建流程的起点,webpack 解析配置参数,整合 shell 传入和配置文件的参数,形成最后的配置结果。通过 optimist,参数解析变得简单高效,确保了参数的完整性和正确性。
在配置合并与插件加载阶段,webpack 将配置项整合到 options 对象中,并加载用户配置的插件。optimist 对参数进行分析并整合,确保了构建过程的灵活性与扩展性。
开始编译的阶段中,webpack 通过运行 Compiler,注册所有插件并触发它们在构建生命周期中的事件节点,执行编译任务。Compiler 的 run 方法是实际入口,构建过程围绕 Compilation 对象展开,该对象负责组织整个打包过程,包含关键步骤,如 addEntry、_addModuleChain、buildModule 等,确保了模块的正确构建与依赖关系的准确管理。
确定入口是构建流程的重要一步,从配置的 entry 入口出发,解析文件构建 AST 语法树,识别依赖并递归解析,直至所有入口依赖的文件都被处理。
编译模块阶段涉及递归调用所有配置的 loader,对文件进行转换,找出模块依赖并递归处理,确保了文件内容的正确转换与依赖关系的准确构建。
完成模块编译并输出阶段,递归结束后,得到每个文件的结果,构建代码块 chunk,根据 entry 配置生成最终代码。封装过程中,webpack 调用 createChunkAssets 方法生成打包后的代码,选择合适的模板进行封装,确保代码的优化与功能的添加。
输出完成是构建流程的终点,webpack 调用 emitAssets 方法,按照输出配置将文件输出到指定目录,打包过程至此结束。
在整个构建流程中,一系列插件在恰当的时机执行,如 UglifyPlugin 可以在 loader 转换递归完前对结果进行压缩优化,确保了构建过程的高效与质量。