1.Loader1.1的loader是做什么的?webpack只能理解JavaScript和JSON文件,这是webpack开箱即用的内置功能。**loader**使webpack能够处理其他类型的文件并将它们转换为有效的模块以供应用程序使用并添加到依赖关系图中。也就是说,webpack把任何文件都当做一个模块,loader可以导入任何类型的模块,但是webpack本身并不支持css文件等解析,这时候就需要用到我们的loader机制了。我们的加载器主要使用两个属性让我们的webpack进行链接识别:测试属性识别哪些文件将被转换。use属性定义了转换时应该使用哪个加载器。那么问题来了,大家肯定想知道如果要自定义一个loader怎么办?1.2DevelopmentGuidelines),在编写时,我们应该遵循这套准则来规范我们的加载器:简单易用。使用链传球。(由于加载程序可以链接,请确保每个加载程序都有单一职责)模块化输出。确保无状态。(加载器转换时不要保留之前的状态,每次运行都应该独立于其他编译模块和同一模块之前的编译结果)充分利用官方加载器实用程序。文档加载器依赖项。解决模块依赖关系。根据模块类型,可能有不同的模式来指定依赖关系。例如在CSS中,使用@import和url(...)语句来声明依赖项。这些依赖关系应该由模块系统来解决。这可以通过以下两种方式之一完成:将它们变成require语句。使用this.resolve函数解析路径。提取通用代码。避免绝对路径。使用对等依赖。如果您的加载器只是简单地包装另一个包,您应该将该包作为peerDependency包含在内。1.3开始加载器是一个nodejs模块。它导出一个函数。该函数只有一个输入参数。该参数是包含资源文件内容的字符串,函数的返回值是处理后的内容。也就是说,最简单的加载器是这样的:module.exports=function(content){//content是传入的源内容字符串returncontent}使用加载器时,它只能接收一个输入参数,这个参数是一个字符串包含资源文件的内容。是的,至此,最简单的loader就完成了!接下来我们看看如何为它添加丰富的功能。1.4四种加载器我们基本上可以把常见的加载器分为四种:同步加载器异步加载器"Raw"LoaderPitchingloader①同步加载器和异步加载器一般的加载器转换都是同步的,我们可以使用上面提到的直接返回结果的方式,return我们的处理结果:module.exports=function(content){//对内容做一些处理constres=dosth(content)returnres}也可以直接用this.callback()这个api,然后在最后直接**returnundefined**方法告诉webpack去this.callback()寻找他想要的结果。这个api接受这些参数:this.callback(err:Error|null,//无法正常编译或者直接给一个nullcontent时的Error:string|Buffer,//我们处理后返回的内容可以是string或者Buffer()sourceMap?:SourceMap,//Optional可以是正常解析的sourcemapmeta?:any//Optional可以是任何东西,比如ApublicASTsyntaxtree);接下来举个例子:这里注意可以使用[this.getOptions()](https://webpack.docschina.org/api/loaders/#thisgetoptionsschema)从webpack5获取配置参数,this.getOptions可以获取加载程序上下文对象。它用于代替loader-utils中的getOptions方法。module.exports=function(content){//获取用户传递给当前加载器的参数constoptions=this.getOptions()constres=someSyncOperation(content,options)this.callback(null,res,sourceMaps);//这里注意,既然用了this.callback,就return就return},一个同步加载器就完成了!说说异步:同步和异步的区别很容易理解。一般我们的转换过程是同步的,但是当我们遇到,比如需要网络请求等场景,那么为了避免阻塞构建步骤,我们会采用异步的构建方式。对于异步加载器,我们主要需要通过this.async()来告知webpack本次构建操作是异步的,废话不多说,看代码就明白了:module.exports=function(content){varcallback=this.async()someAsyncOperation(content,function(err,result){if(err)returncallback(err)callback(null,result,sourceMaps,meta)})}②"Raw"loader默认情况下,资源文件会被转换成UTF-8个字符串,然后传递给加载程序。通过将raw设置为true,加载程序可以接收原始Buffer。每个加载器都可以将其处理结果以String或Buffer的形式传递。编译器将在加载程序之间相互转换它们。熟悉的文件加载器使用它。简而言之:你添加module.exports.raw=true;传给你的是Buffer,返回类型不一定是Buffer,webpack没有限制。module.exports=function(content){console.log(contentinstanceofBuffer);//truereturndoSomeOperation(content)}//focus↓module.exports.raw=true;③pitchingloader我们每个loader都可以有一个pitch方法,大家都知道loader是从右到左调用的,其实这之前有一个从左到右执行每个loader的pitch方法的过程。pitch方法一共有三个参数:remainingRequest:由loader链中自己后面的loader和带!的资源文件的绝对路径组成的字符串。作为连接器。precedingRequest:loader链中自身前面的loader的绝对路径,由!组成的字符串作为连接器。data:存储在每个loader上下文中的固定字段,可用于pitch向loader传递数据。pitch中传给data的数据在后续调用执行阶段可以在this.data中获取:module.exports=function(content){returnsomeSyncOperation(content,this.data.value);//这里是this.data.value===42};module.exports.pitch=function(remainingRequest,precedingRequest,data){data.value=42;};笔记!如果在loader的pitch方法中返回了一个值,那么He会直接“回去”,跳过后面的步骤。举个例子:假设我们现在是这样的:use:['a-loader','b-loader','c-loader'],thennormal调用顺序如下:nowthepitchofb-loader改为返回值://b-loader.jsmodule.exports=function(content){returnsomeSyncOperation(content);};module.exports.pitch=function(remainingRequest,precedingRequest,data){return"哎,我直接返回,只是为了好玩~"};那么当前的调用就会变成这样,直接“回头”,跳过原来的其他三个步骤:1.5OtherAPIthis.addDependency:添加一个文件来监控。一旦文件发生变化,就会再次调用loader进行处理。清除loader的所有依赖this.context:文件所在目录(不包括文件名)this.data:pitch阶段和普通调用阶段共享的对象this.getOptions(schema):用于获取配置的加载器参数选项this.resolve:likerequ像ire表达式一样解析requestresolve(context:string,request:string,callback:function(err,result:string))。this.loaders:所有加载器的数组。它在音高阶段是可写的。this.resource:获取当前请求路径,包含参数:'/abc/resource.js?rrr'this.resourcePath:路径不带参数:'/abc/resource.js'this.sourceMap:bool类型,是否应该generatedAsourceMap官方也提供了很多有用的API,这里只介绍一些常用的,大家可以点击链接了解更多👇更详细的可以参考官方链接1.6来个简单实用的功能实现接下来我们简单练习制作两个loader,功能是在编译好的文件中添加/**company@year*/格式的注释代码,简单的去掉代码中的console.log,我们链式调用它们:(source,options.sign))return}functionaddSign(content,sign){return`/**来自webpack配置${sign}*/\n${content}`}console-loader.jsmodule.exports=function(content){returnhandleConsole(content)}functionhandleConsole(content){returncontent.replace(/console.log\(['|"](.*?)['|"]\)/,'')}调用的函数测试方法简单实现。这里主要说一下如何测试和调用我们本地的loader。有两种方式,一种是通过npm链接方式测试。这个方法的具体使用我就不赘述了。你可以简单地检查一下。另一种是直接在项目中配置路径。有两种情况:1.匹配(测试)单个loader,可以简单的在rule对象中设置path.resolve指向这个本地文件webpack.config.js{test:/\.js$/use:[{loader:path.resolve('path/to/loader.js'),options:{/*...*/}}]}2.匹配(测试)多个loader,可以使用resolveLoader.modules配置,webpack会从这些目录中搜索这些装载机。比如你的项目中有/loaders本地目录:webpack.config.jsresolveLoader:{//这里是先找到node_modules目录,如果没有,再到loaders目录下找modules:['node_modules',小路。resolve(__dirname,'loaders')]}配置在这里使用我们的webpack配置如下:module:{rules:[{test:/\.js$/,use:['console-loader',{loader:'company-loader',options:{sign:'we-doctor@2021',},},],},],},项目中的index.js:functionfn(){console.log("thisisamessage")return"1234"}执行编译后的bundle.js:可以看到编译后的文件中体现了两个loader的功能。/******/(()=>{//webpackBootstrapvar__webpack_exports__={};/*!*************************!*\!***./src/index.js***!\***************************//**we-doctor@2021*/functionfn(){return"1234"}/******/})();二、Plugin为什么会有plugin?Plugin提供了比loader更完整的功能。它使用了stagedbuildcallbacks,webpack为我们提供了很多hooks用来让开发者在构建阶段可以自由的引入自己的行为。基本结构一个基本的插件需要包含这几个部分:一个JavaScript类一个apply方法,apply方法在webpack加载插件时调用,传入compiler对象。使用不同的hooks来指定你需要发生的处理行为.异步调用时,需要调用webpack或者Promise提供的回调(后续异步编译部分会详细介绍)
