之前的文章中提到loader
是在哪执行的,有兴趣的大佬可以去看下。
坏蛋Dan:webpack5流程分析4 - NormalFactory、loader、plugin相关
另外由于有些是在公司写的,有些是在家里写的,所以截图内容会有些不同,但不影响分析。
在进入loader
的入口前,我们得知道loader
是在什么时候在哪里被调用的。之前的文章里分析过了,在NormalModule
的_doBuild
中执行,也就是module
被转换成source
的时候调用。并且是用了一个名叫loader-runner
的包。
然后直接进入主题,先来看下代码,200多行,我们一点一点分析。
先来看下source
是什么
调试工具里没法截图全部,所以这里只有一个``
thread-loader
: 一个第三方包,支持多进程方式解析处理资源,奇怪的是我这边并没有thread-loader | webpackwebpack.js.org/loaders/thread-loader/
plugin_1.default.NS
: 这个plugin_1
指的是VueLoaderPlugin
,当你需要使用vue-laoder
时,你需要在配置文件中加上这个plguin
。来都来了,就顺便把这个VueLoaderPlugin
给说了。
首先会判断你的版本,然后加载不同的文件,因为webpack5
做了一些破坏性的更新,所以这里做了兼容处理。
一进来便是一堆rule
相关的,把人都整懵了。但是你仔细看你会发现这些字段其实有些和我们在配置文件中写的module.rules
里的rule
一样,比如test
、include
、exclude
、resolve
等。
module.rules
:大家应该已经很熟悉了,就不多说了。[1]
RuleSetCompiler
:之前的文章中并没有分析这个,主要是流程上并不影响。但现在这里有的话就不放过了。官网并没有对这个类做解释,不过看名字就知道是一个rule
集合的处理器。
先来生成的看下数据
先是创建一个名为rule
的hook
,如果你不清楚tapable
是什么可以去看一下我之前的写的文章,这里就不多说了。将然后将传入的plugins
都apply
。然后我们来看下被apply
的plugin
做了什么,篇幅问题就不贴代码了。
先是将传入的plugins
都执行,注册到hook
中。然后我们来看下被apply
的plugin
做了什么。
BasicMatcherRulePlugin
: 先是监听ruleSetCompiler
的rule
这个hook
,被执行时先是判断自己的rule
里面的字段(也就是property
)是否还有没被调用的,有的话先将这个字段删除,然后调用ruleSetCompiler
的compileCondition
方法,将这个ruleProperty
在rule
对应的value
转换为用于匹配的function
。
compileCondition
这个方法就不看代码了,简单的说下就是根据你rule
的字段做判断,然后返回一个function
用来验证你这个字段的rule
,比如test
字段在rule
中的value
对应的是一个RegExp
正则,那么通过这个方法返回的会是一个(v) => condition.test(v)
,这里的condition
就是test
字段对应的value
,一个正则。
接着将这个匹配用的function
存入result.conditions
中,这个result
是一个集合,从ruleSetCompiler
中来,具体是从哪来的,干啥的我们先不说,后面会提到。ObjectMatcherRulePlugin
: 和BasicMatcherRulePlugin
做的事差不多,就不多说了。UseEffectRulePlugin
: 对use
和loader
字段的判断,最终已function
的方式存储到result.effects
中,具体这么做的就不说了。等会看下数据即可。暂时就分析到这里,我们先往下分析,之后会再次使用到这个ruleSetCompiler
的。
首先,先是监听compiler
的compilation
这个hook
,在compilation
被创建之后执行。然后监听compilation
的loader
这个hook
,在loader
被加载被执行时,将loaderContext.vue-loader = true
。这样做是为了确保这个插件一定在vue-loader
之前执行,如果不是,vue-loader
会报错。
然后便是遍历你配置的规则,如果规则是eslint
的就不处理,查找这个规则是否符合$.vue
或者$.vue.html
的规则。如果遍历完毕没找到符合的规则就报错。
接着判断这个规则是否有vue-loader
字眼,如果没有则报错。
然后clone
除了vue
的规则之外的所有规则,并加入了一些自定义字段,对比原来的rule
,每个rule
中多了resource、resourceQuery
两个字段。
来看下cloneRule
方法做了什么
一上来就是执行ruleSetCompiler.compileRule
这个方法,正好我们顺着这个方法把ruleSetCompiler
做了什么给说了。
进入到compileRule
方法中。
做的事情非常简单,就是调用rule
这个hook
而这个hook
将触发所有监听这个hook
的事件,也就是说这个hook
将把前面注册的一堆plugin
都走一遍。这个时候你应该已经发现了,这些plugin
和这个ruleSetCompiler
的关系了,并且应该也猜到这些个plugin
具体是做什么用的了。是的,这些所谓的plugin
就是把你写配置文件中module.rules
中每个rule
里的有的字段,比如test
,use
等。而ruleSetCompiler
收集这些字段,等到rule
被传进来后走一遍这些字段的判断,最终转换成result
这个集合。
所以我们直接来看下这个方法执行后的数据。test
等用于匹配判断的将被放到condition
中,而use
、loader
等用于处理的则会被放到effects
中。
总结一下rulePlugin
和ruleSetCompiler
rulePlugin
拓展module.rules
中每个rule
可用的字段,而ruleSetCompiler
用于加载这些plugin
并将每个rule
都过一遍这些plugin
转换成一个带有conditions
和effects
格式的集合。
接着我们回到cloneRule
中,重写除了enforce
处理之外的rule
的use
字段,删除loader、options
字段。接着给rule
新增两个字段,前面提到过的resource
和resourceQuery
。
这段代码非常重要,所以单独拉出来了。
利用闭包在resource
执行时缓存resources
到currentResource
中。然后在resouceQuery
执行时将这个数据传入ruleResouce
方法中。而resourceQuery
方法则是先将请求的路径参数从字符串转换成对象,然后判断是否带有vue
字段,如果没有则return false
,如果有的话先确认是否是符合vue
文件的请求,不是则return false
。如果是的话将请求的路径改写为resouce.query.lang
的格式。比如请求的是?vue&type=style&index=0&id=4a49fab7&lang=css
,那么在转换后的路径会是xxx.vue.css
。接着是调用闭包缓存的rule
的condition
的fn
来判断是否匹配到了。如果没匹配到则return false
。
这一段代码处理的非常巧妙,首先利用了闭包缓存这个rule
和resources
,然后通过判断传入的query
是否符合vue
请求路径,不符合return false
, 只有最后符合这个rule
的规则的vue
资源请求才会被执行。
比如当前是/$.css/
的规则,那么对应的loader
则为style-loader、css-loader
。如果此时请求的资源是普通的css
文件则return false
,而如果请求的是vue?lang=css
则return true
,因为我们要处理的只有vue
相关的请求资源,并且对应rule
对了才会return true
。这样就完美的避开了因为改写其它rule
带来的问题并且自身的代码也能被这些loader
处理。
然后我们再回到cloneRule
中,删除test
字段后返回Object.assign
之后的rule
。
总结一下cloneRule
做了什么
ruleSetCompiler.compileRule
将rule
过一遍所有注册了的rulePlugin
,转换rule
的字段,具体可以往回看。rawRule
的use
, 将loader
等字段的value
合并到use
中。Object.assign
原来的rule
,然后新增两个字段,简单的说就是为了能让vue
相关的请求用正确的loader
,具体分析请往回看。test
字段,这个不清除干啥的。。。rule
中带有rules
和oneOf
的字段则递归cloneRule
处理rule.rules/oneOf
。接着让我们回到VueLoaderPlguin
中
如果你也调试中,你会发现template
的请求是不会带有lang
的,也就是说并没有loader
能处理vue
的template
资源。所以这里加入了对模块的处理的rule
和loader
。
这个TemplateLoader
就不看代码了,因为涉及到vue/compiler-sfc
这个包,这个是准备后面开个文章分析学习的,所以这里简单的说下就是loader
将template
模板代码变成ast
的形式,然后再转换为浏览器能识别的runtime
。
接着回到VueLoaderPlugin
中
这一段针对的是对js/ts
文件有匹配rule
, 有趣的是jsRuleCheck
是判断type
是否是template
结合注释for each rule that matches plain .js files, also create a clone and match it against the compiled template code inside *.vue files, so that compiled vue render functions receive the same treatment as user code (mostly babel)
我们可以知道这是用来处理render function
的,让render function
得到和开发者的普通js
的同样待遇。因为render function
并不是最开始就有的,而是template
通过templateLoader
转换成的,所以这里需要特殊处理。
又一个rule
,结合注释1global pitcher (responsible for injecting template compiler loader & CSS post loader)
注释2This pitching loader is responsible for intercepting all vue block requests and transform it into appropriate requests.
猜测这里面做了两件事,一个是调整loader
顺序,另一个是拦截vue
相关资源的请求,改写为可以被loader
识别的。
我们来分析下这个pitcher
先是获取loader
的identifier
,如果loader
自身不是字符串形式就拼接它的path
和query
。
接着判断这个query
的类型是否是style
的,如果是,先找到css-loader
的位置,然后判断是否是inline
的样式,是的话
style-post-loader
:结合代码和注释This is a post loader that handles scoped CSS transforms. Injected right before css-loader by the global pitcher (../pitch.js) for any <style> selection requests initiated from within vue files.
。我们能推测出这段代码是用来给sfc
中的scoped css
添加scoped id
的,确保样式作用的范围是正确的。
stlye-inline-loader
: 转换成esm
。接着我们回到pitcher.js
中继续往下看
最后这个方法用来改写请求路径和确保都能export
出去,尤其是`template``的。
那这个loader
就讲完了,我们来总结一下pitcher
css
的文件,那么就改写loaders
,在执行css-loader
前先执行style-post-loader
给sfc scoped css
加上scoped id
确保样式只作用于这个scoped
中。如果是inline css
,那么就不执行style-loader、css-loader
等。在执行完style-post-loader
后执行style-inline-loader
,将资源export
出去。template
资源的引入方式,确保能export
出去。改写request
路径,将loader
的路径和ruleSet
中的位置以及request
资源路径拼接到一起。接着让我们回到VueLoaderPlugin
中接着往下看。
最后是重写compiler.options.module.rules
来看下这个重写后的rules
,加了一些“加料”的rule
总结下VueLoaderPlugin
做了什么
首先将vue-loader
的状态变为true
,这样在调用vue-loader
的时候就不会报错,这样做是为了确保VueLoaderPlugin
的执行先于vue-loader
。接着判断是否有vue-loader
这个loader
,没有直接报错。
然后复制一份不包含use vue-loader
的rules
,给他们加resouce
喝resourceQuery
两个字段,其中resource
是用于缓存request source
,而resourceQuery
则是一个方法,利用闭包缓存当前的rule
和source
,判断这里面请求的是vue
相关的资源并且资源能被这个rule
匹配到,这样做确保vue
相关的请求资源被vue-loader
处理后能被其他loader
也处理了。
然后注入templateLoader
用于处理vue
的template
为render function
。
接着又copy
了一份和处理js/ts
资源的loader
相关的rules
,这样做是为了template
通过templateLoader
转换得到的render function
的js/ts
代码能被这些loader
也处理了。
然后又注入pitcher
这个loader
,用来处理两个问题。
css-loader
前注入style-post-loader
来给vue sfc scoped css
增加scoped id
来处理vue sfc
中的scoped css
资源作用域问题。如果是inline css
资源则不再调用css-loader
以及css-loader
之后执行的loader
,相反,使用style-inline-poster
将这些inline css
变成esm
资源,export
出去。request
路径,将loader
的路径和loader
在ruleSet
中的位置以及request
资源路径拼接到一起。 将template
的资源export
出去。
最后覆盖compiler.options.module.rules
具体分析请往回看。总结完VueLoaderPlugin
后我们回到vue-loader
中继续往下看。
这一段都是在初始化一些数据,我们就不分析了,直接看下重要的几个字段
loaderContext
:这个就不看了,就是_doBuild
中将compiler、compilation
等合并到一起的一个对象filename
:descriptor
:这一段代码中将.vue
文件源码字符串拆分为template
、script
以及style
。至于里面怎么做的我们之后的文章会分析。现在先继续往下看。
这一段很简单,就不看descriptorCache
的代码了,就是将这个解释后的descriptor
以[filename]: descriptor
的格式缓存到全局Cache
这个集合中。下次获取如果存在则直接get
,不存在则再执行一次parse
。
这一段很简单也很重要,这里id
就是scoped
的id
,现在我们知道了这个是根据文件路径甚至加上文件源码生成的hash
值。
先是判断是否是typescript
,判断script
标签中是否带有lang
。然后拼接请求路径,scriptImport
则是一段runtime
。
和script
一样,都是判断场景然后生成请求runtime
。
css
这个自然也是一样的操作。
data-v-${id}
:大家应该都很熟悉了。这段结合注释可以知道是和vue-devTool
相关,就不多说了。
如果你的文件中加了一段不是script、template、style
的奇怪标签,那么就会被这里捕获到,一样会被导入。但是除了function
之外都不会执行。
最后注入了一段runtime
,让这些属性能被注入到components
中。
/#__PURE__/
: 通知webpack
这个是"纯净的",是可以tree shake
掉的。
这一段是和热更新有关的,来看下这个hotReload.js
,先看下templateRequest
。
templateRequest
这是一段runtime
,到时会被带上浏览器,现在我们看的不是很懂,后面会开个文章分析,所以这里简单的看下代码即可。 而这段runtime
最核心的一段就是api.rerender
这里。
将组件导出去。
最后看一下完整的components
的runtime
。
当浏览器请求这个资源的时候才会执行。
最后return
出去。
总结一下vue-loader
做了什么。
简单地说就是先调用compiler-sfc
包将.vue
文件切割成template、script、style、custom
四部分,然后分别拼装请求路径(path + query)
。再加上一些其他runtime
,最终组成一个components
的runtime
被带上浏览器中,等待调用执行。
VueLoaderPlugin
前面总结了,这里就不说了。
这里还有两个点没有分析
compiler-sfc
hotReload
这段runtime
这两个准备后面开个文章分析。
最后如果对大佬你来说有点用的话麻烦点的赞,谢谢!
上面说到的hot-reload
相关的得等到分析vue
的runtime
包才会涉及到。
编辑于 2023-01-24 21:25・IP 属地广东