这两天看了下webpack加载机制,loader运行原理,又去看了下vue-loader源码,领悟了些东西,发文记录下。
vue-loader源码目录:

源码目录各个文件大致解释下:
1):codegen目录下有4个文件:customBlocks.js,hotReload.js,
styleInjection.js,utils.js。
customBlocks.js用于生成vue单文件组件里自定义块的代码
hotReload.js用于生成vue单文件中template的热更新代码注入
styleInjection.用于生成vue单文件中style块的代码,如果开启了css-module会根据hotReload配置判断是否注入热更新代码
2):loaders目录下有3个文件:pitcher.js,stylePostLoader.js,templateLoader.js。
pitcher.js用于根据vue-loader解析分发后的不同的request路径生成对应的带loader解析的全路径。
stylePostLoader.js中使用了@vue/component-compiler-utils中的compileStyle去做样式scoped转换。
templateLoader.js中使用了@vue/component-compiler-utils中的compileTemplate去做模板编译生成render和staticRenderFns。
3):runtime目录下有一个文件:componentNormalizer.js。
componentNormalizer.js用于生成两个对象导出,exports和options,传入了scriptExports对象,里面包含vue组件实例化配置信息,然后下面又对ssr做了处理,在beforeCreate钩子加入了一个hook,这个hook作用是调用之前在styleInjection.js文件里生成的injectStyles方法注入当前组件的样式。
4):index.d.ts用于typescript类型声明支持。
5):index.js为vue-loader加载的主入口。
6):plugin-webpack4.js和plugin-webpack5.js是针对不同webpack版本的文件plugin逻辑文件。
7):plugin.js是VueLoaderPlugin入口文件,根据当前webpack版本去加载不同的plugin-webpack文件。
vue-loader处理逻辑:
1):VueLoaderPlugin插件:
1.1:调用apply方法里去tap compilation和normal-module-loader事件,给loaderContext['vue-loader']设置true。
1.2:通过compiler获取到webpack配置中的rules规则,利用webpack/lib/RuleSet这个工具去实例化了一个RuleSet对象获取到一个rules对象,这个rules对象是配置的所有规则。
1.3:通过调用一个createMatcher方法去匹配出.vue或.vue.html文件的规则,createMatcher这个方法传入fake文件名,源码中为'foo.vue'和'foo.vue.html'。
1.4:判断上一步获取的.vue或.vue.html规则是否存在,不存在则抛出错误。
1.5:再去这个匹配到的vue规则中去找vue-loader的规则,如果没有找到抛出错误,如果找到给这个规则增加ident和options,ident用于其它loader(比如template-loader就是这样处理的:template-loader??vue-loader-options)获取当前vue-loader中的options配置。
1.6:从所有的rules中过滤出不包含vueRule的其它规则,然后进行复写为clonedRules,复写的逻辑为在resourceQuery方法中根据当前vue块请求路径中的lang属性进行一个loader规则匹配,比如:当前请求为App.vue?vue&type=template&lang=pug则会使用App.vue.pug去匹配出对应的loader规则。
1.7:最后覆盖webpack的rules规则,覆盖的rules顺序为pitcher-loader(vue-loader的pitch方法,用于对匹配到的块生成全webpack-loader路径),上一步中复写的clonedRules,除了vueRule的其它loaders这3块loader。
1.8:至此VueLoaderPlugin逻辑完毕,可以看出VueLoaderPlugin起了对vue单文件中块的对应lang指定的loaders匹配处理的关键作用。
2):loader:
1.1:入口loader,判断thread-loader和VueLoaderPlugin是否存在且加载,如果没有则抛出错误。
1.2:query解析,options解析,isServer,isShadow,isProduction,context,filename等获取处理。
1.3:使用@vue/component-compiler-utils的parse方法去生成descriptor对象。
1.4:第一种情况:如果请求存在type参数,表示这是一个块请求,则调用selectBlock去选择对应的块内容返回。
1.5:在selectBlock中有4中判断,template,script,style,custom这4中不同的块逻辑处理,都是用上面生成的descriptor对象去取出不同的内容,template取descriptor.template.content,script取descriptor.script.content,style取style.content,custom取block.content。
1.6:第二种情况:不存在type参数表示为vue文件(不是文件解析后的块)请求,会依次生成template模板,script模板,style模板,如果存在自定义块则还会生成custom模板,如果热更新配置打开还会生成热更新模板,最后依次拼接返回。如下图:

1.7:此时这个文件加载后会去命中1.4步的逻辑。
3):pitcher
1.1:picth方法是loader执行前的会执行的方法,pitch是正向执行,loader是反向执行。
1.2:如上面所说的vue-loader的pitch这个方法会在loader的query字符串中vue字段存在时命中。
1.3:如果命中则进入pitcher.js逻辑。
1.4:在pitcher.js中进行一些loader过滤,主要就是过滤掉eslint-loader和它本身loader。
1.5:根据query.type去判断style,tempalte,custom几个块,命中的话就去分出afterLoaders,beforeLoaders,然后调用genRequest方法去拼接成一个完整的所有匹配到的loader的请求路径加文件路径及query参数。如:
import mod from "-!../node_modules/vue-style-loader/index.js??ref--11-oneOf-1-0!../node_modules/css-loader/dist/cjs.js??ref--11-oneOf-1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/postcss-loader/src/index.js??ref--11-oneOf-1-2!../node_modules/stylus-loader/index.js??ref--11-oneOf-1-3!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=style&index=0&lang=stylus&"; export default mod; export * from "-!../node_modules/vue-style-loader/index.js??ref--11-oneOf-1-0!../node_modules/css-loader/dist/cjs.js??ref--11-oneOf-1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/postcss-loader/src/index.js??ref--11-oneOf-1-2!../node_modules/stylus-loader/index.js??ref--11-oneOf-1-3!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=style&index=0&lang=stylus&"
此时:loaders会依次从最右边执行,最后拿到了最终的处理结果。
全文完毕,写的不好的地方请指出,谢谢。