立即领取|华润集团、宁德核电、东风岚图等 20+ 标杆企业数字化人才培养实践案例 了解详情
写点什么

webpack 系列之四 loader 详解 3

  • 2019-09-19
  • 本文字数:21094 字

    阅读完需:约 69 分钟

webpack 系列之四 loader 详解3

前 2 篇文章 webpack loader 详解 1 和 webpack loader 详解 2 主要通过源码分析了 loader 的配置,匹配和加载,执行等内容,这篇文章会通过具体的实例来学习下如何去实现一个 loader。


这里我们来看下 vue-loader(v15) 内部的相关内容,这里会讲解下有关 vue-loader 的大致处理流程,不会深入特别细节的地方。


git clone git@github.com:vuejs/vue-loader.git
复制代码


我们使用 vue-loader 官方仓库当中的 example 目录的内容作为整篇文章的示例。


首先我们都知道 vue-loader 配合 webpack 给我们开发 Vue 应用提供了非常大的便利性,允许我们在 SFC(single file component) 中去写我们的 template/script/style,同时 v15 版本的 vue-loader 还允许开发在 SFC 当中写 custom block。最终一个 Vue SFC 通过 vue-loader 的处理,会将 template/script/style/custom block 拆解为独立的 block,每个 block 还可以再交给对应的 loader 去做进一步的处理,例如你的 template 是使用 pug 来书写的,那么首先使用 vue-loader 获取一个 SFC 内部 pug 模板的内容,然后再交给 pug 相关的 loader 处理,可以说 vue-loader 对于 Vue SFC 来说是一个入口处理器。


在实际运用过程中,我们先来看下有关 Vue 的 webpack 配置:


const VueloaderPlugin = require('vue-loader/lib/plugin')
module.exports = { ... module: { rules: [ ... { test: /\.vue$/, loader: 'vue-loader' } ] }
plugins: [ new VueloaderPlugin() ] ...}
复制代码


一个就是 module.rules 有关的配置,如果处理的 module 路径是以.vue 形式结尾的,那么会交给 vue-loader 来处理,同时在 v15 版本必须要使用 vue-loader 内部提供的一个 plugin,它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /.js$/ 的规则,那么它会应用到 .vue 文件里的块,说到这里我们就一起先来看看这个 plugin 里面到底做了哪些工作。</p><h2>1.VueLoaderPlugin</h2><p>我们都清楚 webpack plugin 的装载过程是在整个 webpack 编译周期中初始阶段,我们先来看下 VueLoaderPlugin 内部源码的实现:</p><pre><code>//&nbsp;vue-loader/lib/plugin.js<br/><br/>class&nbsp;VueLoaderPlugin&nbsp;{<br/>&nbsp;&nbsp;apply()&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;...<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;use&nbsp;webpack's&nbsp;RuleSet&nbsp;utility&nbsp;to&nbsp;normalize&nbsp;user&nbsp;rules<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;rawRules&nbsp;=&nbsp;compiler.options.module.rules<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;{&nbsp;rules&nbsp;}&nbsp;=&nbsp;new&nbsp;RuleSet(rawRules)<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;find&nbsp;the&nbsp;rule&nbsp;that&nbsp;applies&nbsp;to&nbsp;vue&nbsp;files<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;判断是否有给`.vue`或`.vue.html`进行 &nbsp;module.rule&nbsp;的配置<br/>&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;vueRuleIndex&nbsp;=&nbsp;rawRules.findIndex(createMatcher(`foo.vue`))<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(vueRuleIndex&nbsp;&lt;&nbsp;0)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vueRuleIndex&nbsp;=&nbsp;rawRules.findIndex(createMatcher(`foo.vue.html`))<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;vueRule&nbsp;=&nbsp;rules[vueRuleIndex]<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;...<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;判断对于`.vue`或`.vue.html`配置的 &nbsp;module.rule&nbsp;是否有 &nbsp;vue-loader<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;get&nbsp;the&nbsp;normlized&nbsp;&quot;use&quot;&nbsp;for&nbsp;vue&nbsp;files<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;vueUse&nbsp;=&nbsp;vueRule.use<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;get&nbsp;vue-loader&nbsp;options<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;vueLoaderUseIndex&nbsp;=&nbsp;vueUse.findIndex(u&nbsp;=&gt;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;/^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)<br/>&nbsp;&nbsp;&nbsp;&nbsp;})<br/>&nbsp;&nbsp;&nbsp;&nbsp;...<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;创建 &nbsp;pitcher&nbsp;loader&nbsp;的配置<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;pitcher&nbsp;=&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loader:&nbsp;require.resolve('./loaders/pitcher'),<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resourceQuery:&nbsp;query&nbsp;=&gt;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;parsed&nbsp;=&nbsp;qs.parse(query.slice(1))<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;parsed.vue&nbsp;!=&nbsp;null<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;options:&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cacheDirectory:&nbsp;vueLoaderUse.options.cacheDirectory,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cacheIdentifier:&nbsp;vueLoaderUse.options.cacheIdentifier<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;拓展开发者的 &nbsp;module.rule&nbsp;配置,加入 &nbsp;vue-loader&nbsp;内部提供的 &nbsp;pitcher&nbsp;loader<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;replace&nbsp;original&nbsp;rules<br/>&nbsp;&nbsp;&nbsp;&nbsp;compiler.options.module.rules&nbsp;=&nbsp;[<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pitcher,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...clonedRules,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...rules<br/>&nbsp;&nbsp;&nbsp;&nbsp;]<br/>&nbsp;&nbsp;}<br/>}<br/></code></pre><p>这个 plugin 主要完成了以下三部分的工作:</p><p>判断是否有给.vue 或.vue.html 进行 module.rule 的配置;</p><p>判断对于.vue 或.vue.html 配置的 module.rule 是否有 vue-loader;</p><p>拓展开发者的 module.rule 配置,加入 vue-loader 内部提供的 pitcher loader</p><p>我们看到有关 pitcher loader 的 rule 匹配条件是通过 resourceQuery 方法来进行判断的,即判断 module path 上的 query 参数是否存在 vue,例如:</p><pre><code>//&nbsp;这种类型的 &nbsp;module&nbsp;path&nbsp;就会匹配上<br/>'./source.vue?vue&amp;type=template&amp;id=27e4e96e&amp;scoped=true&amp;lang=pug&amp;'<br/></code></pre><p>如果存在的话,那么就需要将这个 loader 加入到构建这个 module 的 loaders 数组当中。以上就是 VueLoaderPlugin 所做的工作,其中涉及到拓展后的 module rule 里面加入的 pitcher loader 具体做的工作后文会分析。</p><h2>2.Step 1</h2><p>接下来我们看下 vue-loader 的内部实现。首先来看下入口文件的相关内容:</p><pre><code>//&nbsp;vue-loader/lib/index.js<br/>...<br/>const&nbsp;{&nbsp;parse&nbsp;}&nbsp;=&nbsp;require('@vue/component-compiler-utils')<br/><br/>function&nbsp;loadTemplateCompiler&nbsp;()&nbsp;{<br/>&nbsp;&nbsp;try&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;require('vue-template-compiler')<br/>&nbsp;&nbsp;}&nbsp;catch&nbsp;(e)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;Error(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`[vue-loader]&nbsp;vue-template-compiler&nbsp;must&nbsp;be&nbsp;installed&nbsp;as&nbsp;a&nbsp;peer&nbsp;dependency,&nbsp;`&nbsp;+<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`or&nbsp;a&nbsp;compatible&nbsp;compiler&nbsp;implementation&nbsp;must&nbsp;be&nbsp;passed&nbsp;via&nbsp;options.`<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;}<br/>}<br/><br/>module.exports&nbsp;=&nbsp;function(source)&nbsp;{<br/>&nbsp;&nbsp;const&nbsp;loaderContext&nbsp;=&nbsp;this&nbsp;//&nbsp;获取 &nbsp;loaderContext&nbsp;对象<br/><br/>&nbsp;&nbsp;//&nbsp;从 &nbsp;loaderContext&nbsp;获取相关参数<br/>&nbsp;&nbsp;const&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;target,&nbsp;//&nbsp;webpack&nbsp;构建目标,默认为 &nbsp;web<br/>&nbsp;&nbsp;&nbsp;&nbsp;request,&nbsp;//&nbsp;module&nbsp;request&nbsp;路径(由 &nbsp;path&nbsp;和 &nbsp;query&nbsp;组成)<br/>&nbsp;&nbsp;&nbsp;&nbsp;minimize,&nbsp;//&nbsp;构建模式<br/>&nbsp;&nbsp;&nbsp;&nbsp;sourceMap,&nbsp;//&nbsp;是否开启 &nbsp;sourceMap<br/>&nbsp;&nbsp;&nbsp;&nbsp;rootContext,&nbsp;//&nbsp;项目的根路径<br/>&nbsp;&nbsp;&nbsp;&nbsp;resourcePath,&nbsp;//&nbsp;module&nbsp;的 &nbsp;path&nbsp;路径<br/>&nbsp;&nbsp;&nbsp;&nbsp;resourceQuery&nbsp;//&nbsp;module&nbsp;的 &nbsp;query&nbsp;参数<br/>&nbsp;&nbsp;}&nbsp;=&nbsp;loaderContext<br/><br/>&nbsp;&nbsp;//&nbsp;接下来就是一系列对于参数和路径的处理<br/>&nbsp;&nbsp;const&nbsp;rawQuery&nbsp;=&nbsp;resourceQuery.slice(1)<br/>&nbsp;&nbsp;const&nbsp;inheritQuery&nbsp;=&nbsp;`&amp;{rawQuery}`<br/>&nbsp;&nbsp;const&nbsp;incomingQuery&nbsp;=&nbsp;qs.parse(rawQuery)<br/>&nbsp;&nbsp;const&nbsp;options&nbsp;=&nbsp;loaderUtils.getOptions(loaderContext)&nbsp;||&nbsp;{}<br/><br/>&nbsp;&nbsp;...<br/>&nbsp;&nbsp;<br/><br/>&nbsp;&nbsp;//&nbsp;开始解析&nbsp;sfc,根据不同的&nbsp;block&nbsp;来拆解对应的内容<br/>&nbsp;&nbsp;const&nbsp;descriptor&nbsp;=&nbsp;parse({<br/>&nbsp;&nbsp;&nbsp;&nbsp;source,<br/>&nbsp;&nbsp;&nbsp;&nbsp;compiler:&nbsp;options.compiler&nbsp;||&nbsp;loadTemplateCompiler(),<br/>&nbsp;&nbsp;&nbsp;&nbsp;filename,<br/>&nbsp;&nbsp;&nbsp;&nbsp;sourceRoot,<br/>&nbsp;&nbsp;&nbsp;&nbsp;needMap:&nbsp;sourceMap<br/>&nbsp;&nbsp;})<br/><br/>&nbsp;&nbsp;//&nbsp;如果&nbsp;query&nbsp;参数上带了&nbsp;block&nbsp;的&nbsp;type&nbsp;类型,那么会直接返回对应&nbsp;block&nbsp;的内容<br/>&nbsp;&nbsp;//&nbsp;例如:foo.vue?vue&amp;type=template,那么会直接返回&nbsp;template&nbsp;的文本内容<br/>&nbsp;&nbsp;if&nbsp;(incomingQuery.type)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;selectBlock(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaderContext,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;incomingQuery,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;!!options.appendExtension<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;...<br/><br/>&nbsp;&nbsp;//&nbsp;template<br/>&nbsp;&nbsp;let&nbsp;templateImport&nbsp;=&nbsp;`var&nbsp;render,&nbsp;staticRenderFns`<br/>&nbsp;&nbsp;let&nbsp;templateRequest<br/>&nbsp;&nbsp;if&nbsp;(descriptor.template)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;src&nbsp;=&nbsp;descriptor.template.src&nbsp;||&nbsp;resourcePath<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;idQuery&nbsp;=&nbsp;`&amp;id={id}`<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;scopedQuery&nbsp;=&nbsp;hasScoped&nbsp;?&nbsp;`&amp;scoped=true`&nbsp;:&nbsp;``<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;attrsQuery&nbsp;=&nbsp;attrsToQuery(descriptor.template.attrs)<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;query&nbsp;=&nbsp;`?vue&amp;type=template{scopedQuery}{inheritQuery}`<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;request&nbsp;=&nbsp;templateRequest&nbsp;=&nbsp;stringifyRequest(src&nbsp;+&nbsp;query)<br/>&nbsp;&nbsp;&nbsp;&nbsp;templateImport&nbsp;=&nbsp;`import&nbsp;{&nbsp;render,&nbsp;staticRenderFns&nbsp;}&nbsp;from&nbsp;{request}`<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;script<br/>&nbsp;&nbsp;let&nbsp;scriptImport&nbsp;=&nbsp;`var&nbsp;script&nbsp;=&nbsp;{}`<br/>&nbsp;&nbsp;if&nbsp;(descriptor.script)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;src&nbsp;=&nbsp;descriptor.script.src&nbsp;||&nbsp;resourcePath<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;attrsQuery&nbsp;=&nbsp;attrsToQuery(descriptor.script.attrs,&nbsp;'js')<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;query&nbsp;=&nbsp;`?vue&amp;type=script{attrsQuery}{inheritQuery}`<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;request&nbsp;=&nbsp;stringifyRequest(src&nbsp;+&nbsp;query)<br/>&nbsp;&nbsp;&nbsp;&nbsp;scriptImport&nbsp;=&nbsp;(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`import&nbsp;script&nbsp;from&nbsp;{request}\n`&nbsp;+<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`export&nbsp;*&nbsp;from&nbsp;{request}`&nbsp;//&nbsp;support&nbsp;named&nbsp;exports<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;styles<br/>&nbsp;&nbsp;let&nbsp;stylesCode&nbsp;=&nbsp;``<br/>&nbsp;&nbsp;if&nbsp;(descriptor.styles.length)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;stylesCode&nbsp;=&nbsp;genStylesCode(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaderContext,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor.styles,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resourcePath,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stringifyRequest,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;needsHotReload,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;isServer&nbsp;||&nbsp;isShadow&nbsp;//&nbsp;needs&nbsp;explicit&nbsp;injection?<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;let&nbsp;code&nbsp;=&nbsp;`<br/>{templateImport}<br/>{stylesCode}<br/><br/>/*&nbsp;normalize&nbsp;component&nbsp;*/<br/>import&nbsp;normalizer&nbsp;from&nbsp;{stringifyRequest(`!{componentNormalizerPath}`)}<br/>var&nbsp;component&nbsp;=&nbsp;normalizer(<br/>&nbsp;&nbsp;script,<br/>&nbsp;&nbsp;render,<br/>&nbsp;&nbsp;staticRenderFns,<br/>&nbsp;&nbsp;{hasFunctional&nbsp;?&nbsp;`true`&nbsp;:&nbsp;`false`},<br/>&nbsp;&nbsp;{/injectStyles/.test(stylesCode)&nbsp;?&nbsp;`injectStyles`&nbsp;:&nbsp;`null`},<br/>&nbsp;&nbsp;{hasScoped&nbsp;?&nbsp;JSON.stringify(id)&nbsp;:&nbsp;`null`},<br/>&nbsp;&nbsp;{isServer&nbsp;?&nbsp;JSON.stringify(hash(request))&nbsp;:&nbsp;`null`}<br/>&nbsp;&nbsp;{isShadow&nbsp;?&nbsp;`,true`&nbsp;:&nbsp;``}<br/>)<br/>&nbsp;&nbsp;`.trim()&nbsp;+&nbsp;`\n`<br/><br/>&nbsp;&nbsp;if&nbsp;(descriptor.customBlocks&nbsp;&amp;&amp;&nbsp;descriptor.customBlocks.length)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;code&nbsp;+=&nbsp;genCustomBlocksCode(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor.customBlocks,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resourcePath,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resourceQuery,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stringifyRequest<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;...<br/><br/>&nbsp;&nbsp;//&nbsp;Expose&nbsp;filename.&nbsp;This&nbsp;is&nbsp;used&nbsp;by&nbsp;the&nbsp;devtools&nbsp;and&nbsp;Vue&nbsp;runtime&nbsp;warnings.<br/>&nbsp;&nbsp;code&nbsp;+=&nbsp;`\ncomponent.options.__file&nbsp;=&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;isProduction<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;For&nbsp;security&nbsp;reasons,&nbsp;only&nbsp;expose&nbsp;the&nbsp;file's&nbsp;basename&nbsp;in&nbsp;production.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;?&nbsp;JSON.stringify(filename)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Expose&nbsp;the&nbsp;file's&nbsp;full&nbsp;path&nbsp;in&nbsp;development,&nbsp;so&nbsp;that&nbsp;it&nbsp;can&nbsp;be&nbsp;opened<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;from&nbsp;the&nbsp;devtools.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;JSON.stringify(rawShortFilePath.replace(/\\/g,&nbsp;'/'))<br/>&nbsp;&nbsp;}`<br/><br/>&nbsp;&nbsp;code&nbsp;+=&nbsp;`\nexport&nbsp;default&nbsp;component.exports`<br/>&nbsp;&nbsp;return&nbsp;code<br/>}<br/></code></pre><p>以上就是 vue-loader 的入口文件(index.js)主要做的工作:对于 request 上不带 type 类型的 Vue SFC 进行 parse,获取每个 block 的相关内容,将不同类型的 block 组件的 Vue SFC 转化成 js module 字符串,具体的内容如下:</p><pre><code>import&nbsp;{&nbsp;render,&nbsp;staticRenderFns&nbsp;}&nbsp;from&nbsp;&quot;./source.vue?vue&amp;type=template&amp;id=27e4e96e&amp;scoped=true&amp;lang=pug&amp;&quot;<br/>import&nbsp;script&nbsp;from&nbsp;&quot;./source.vue?vue&amp;type=script&amp;lang=js&amp;&quot;<br/>export&nbsp;*&nbsp;from&nbsp;&quot;./source.vue?vue&amp;type=script&amp;lang=js&amp;&quot;<br/>import&nbsp;style0&nbsp;from&nbsp;&quot;./source.vue?vue&amp;type=style&amp;index=0&amp;id=27e4e96e&amp;scoped=true&amp;lang=css&amp;&quot;<br/><br/>/*&nbsp;normalize&nbsp;component&nbsp;*/<br/>import&nbsp;normalizer&nbsp;from&nbsp;&quot;!../lib/runtime/componentNormalizer.js&quot;<br/>var&nbsp;component&nbsp;=&nbsp;normalizer(<br/>&nbsp;&nbsp;script,<br/>&nbsp;&nbsp;render,<br/>&nbsp;&nbsp;staticRenderFns,<br/>&nbsp;&nbsp;false,<br/>&nbsp;&nbsp;null,<br/>&nbsp;&nbsp;&quot;27e4e96e&quot;,<br/>&nbsp;&nbsp;null<br/>)<br/><br/>/*&nbsp;custom&nbsp;blocks&nbsp;*/<br/>import&nbsp;block0&nbsp;from&nbsp;&quot;./source.vue?vue&amp;type=custom&amp;index=0&amp;blockType=foo&quot;<br/>if&nbsp;(typeof&nbsp;block0&nbsp;===&nbsp;'function')&nbsp;block0(component)<br/><br/>//&nbsp;省略了有关 &nbsp;hotReload&nbsp;的代码<br/><br/>component.options.__file&nbsp;=&nbsp;&quot;example/source.vue&quot;<br/>export&nbsp;default&nbsp;component.exports<br/></code></pre><p>从生成的 js module 字符串来看:将由 source.vue 提供 render 函数/staticRenderFns,js script,style 样式,并交由 normalizer 进行统一的格式化,最终导出 component.exports。</p><h2>3.Step 2</h2><p>这样 vue-loader 处理的第一个阶段结束了,vue-loader 在这一阶段将 Vue SFC 转化为 js module 后,接下来进入到第二阶段,将新生成的 js module 加入到 webpack 的编译环节,即对这个 js module 进行 AST 的解析以及相关依赖的收集过程,这里我用每个 request 去标记每个被收集的 module(这里只说明和 Vue SFC 相关的模块内容):</p><pre><code>[<br/>&nbsp;'./source.vue?vue&amp;type=template&amp;id=27e4e96e&amp;scoped=true&amp;lang=pug&amp;',<br/>&nbsp;'./source.vue?vue&amp;type=script&amp;lang=js&amp;',<br/>&nbsp;'./source.vue?vue&amp;type=style&amp;index=0&amp;id=27e4e96e&amp;scoped=true&amp;lang=css&amp;',<br/>&nbsp;'./source.vue?vue&amp;type=custom&amp;index=0&amp;blockType=foo'<br/>]<br/></code></pre><p>我们看到通过 vue-loader 处理到得到的 module path 上的 query 参数都带有 vue 字段。这里便涉及到了我们在文章开篇提到的 VueLoaderPlugin 加入的 pitcher loader。如果遇到了 query 参数上带有 vue 字段的 module path,那么就会把 pitcher loader 加入到处理这个 module 的 loaders 数组当中。因此这个 module 最终也会经过 pitcher loader 的处理。此外在 loader 的配置顺序上,pitcher loader 为第一个,因此在处理 Vue SFC 模块的时候,最先也是交由 pitcher loader 来处理。</p><p>事实上对一个 Vue SFC 处理的第二阶段就是刚才提到的,Vue SFC 会经由 pitcher loader 来做进一步的处理。那么我们就来看下 vue-loader 内部提供的 pitcher loader 主要是做了哪些工作呢:</p><p>剔除 eslint loader;<br/>剔除 pitcher loader 自身;<br/>根据不同 type query 参数进行拦截处理,返回对应的内容,跳过后面的 loader 执行的阶段,进入到 module parse 阶段</p><pre><code>//&nbsp;vue-loader/lib/loaders/pitcher.js<br/><br/>module.export&nbsp;=&nbsp;code&nbsp;=&gt;&nbsp;code<br/><br/>module.pitch&nbsp;=&nbsp;function&nbsp;()&nbsp;{<br/>&nbsp;&nbsp;...<br/>&nbsp;&nbsp;const&nbsp;query&nbsp;=&nbsp;qs.parse(this.resourceQuery.slice(1))<br/>&nbsp;&nbsp;let&nbsp;loaders&nbsp;=&nbsp;this.loaders<br/><br/>&nbsp;&nbsp;//&nbsp;剔除 &nbsp;eslint&nbsp;loader<br/>&nbsp;&nbsp;//&nbsp;if&nbsp;this&nbsp;is&nbsp;a&nbsp;language&nbsp;block&nbsp;request,&nbsp;eslint-loader&nbsp;may&nbsp;get&nbsp;matched<br/>&nbsp;&nbsp;//&nbsp;multiple&nbsp;times<br/>&nbsp;&nbsp;if&nbsp;(query.type)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;if&nbsp;this&nbsp;is&nbsp;an&nbsp;inline&nbsp;block,&nbsp;since&nbsp;the&nbsp;whole&nbsp;file&nbsp;itself&nbsp;is&nbsp;being&nbsp;linted,<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;remove&nbsp;eslint-loader&nbsp;to&nbsp;avoid&nbsp;duplicate&nbsp;linting.<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(/\.vue/.test(this.resourcePath))&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaders&nbsp;=&nbsp;loaders.filter(l&nbsp;=&gt;&nbsp;!isESLintLoader(l))<br/>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;This&nbsp;is&nbsp;a&nbsp;src&nbsp;import.&nbsp;Just&nbsp;make&nbsp;sure&nbsp;there's&nbsp;not&nbsp;more&nbsp;than&nbsp;1&nbsp;instance<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;of&nbsp;eslint&nbsp;present.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaders&nbsp;=&nbsp;dedupeESLintLoader(loaders)<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;剔除&nbsp;pitcher&nbsp;loader&nbsp;自身<br/>&nbsp;&nbsp;//&nbsp;remove&nbsp;self<br/>&nbsp;&nbsp;loaders&nbsp;=&nbsp;loaders.filter(isPitcher)<br/><br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;'style')&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;cssLoaderIndex&nbsp;=&nbsp;loaders.findIndex(isCSSLoader)<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(cssLoaderIndex&nbsp;&gt;&nbsp;-1)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;afterLoaders&nbsp;=&nbsp;loaders.slice(0,&nbsp;cssLoaderIndex&nbsp;+&nbsp;1)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;beforeLoaders&nbsp;=&nbsp;loaders.slice(cssLoaderIndex&nbsp;+&nbsp;1)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;request&nbsp;=&nbsp;genRequest([<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...afterLoaders,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stylePostLoaderPath,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...beforeLoaders<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;])<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;`import&nbsp;mod&nbsp;from&nbsp;{request};&nbsp;export&nbsp;default&nbsp;mod;&nbsp;export&nbsp;*&nbsp;from&nbsp;{request}`<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;'template')&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;path&nbsp;=&nbsp;require('path')<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;cacheLoader&nbsp;=&nbsp;cacheDirectory&nbsp;&amp;&amp;&nbsp;cacheIdentifier<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;?&nbsp;[`cache-loader?{JSON.stringify({<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;For&nbsp;some&nbsp;reason,&nbsp;webpack&nbsp;fails&nbsp;to&nbsp;generate&nbsp;consistent&nbsp;hash&nbsp;if&nbsp;we<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;use&nbsp;absolute&nbsp;paths&nbsp;here,&nbsp;even&nbsp;though&nbsp;the&nbsp;path&nbsp;is&nbsp;only&nbsp;used&nbsp;in&nbsp;a<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;comment.&nbsp;For&nbsp;now&nbsp;we&nbsp;have&nbsp;to&nbsp;ensure&nbsp;cacheDirectory&nbsp;is&nbsp;a&nbsp;relative&nbsp;path.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cacheDirectory:&nbsp;path.isAbsolute(cacheDirectory)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;?&nbsp;path.relative(process.cwd(),&nbsp;cacheDirectory)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;cacheDirectory,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cacheIdentifier:&nbsp;hash(cacheIdentifier)&nbsp;+&nbsp;'-vue-loader-template'<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})}`]<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;[]<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;request&nbsp;=&nbsp;genRequest([<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...cacheLoader,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;templateLoaderPath&nbsp;+&nbsp;`??vue-loader-options`,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...loaders<br/>&nbsp;&nbsp;&nbsp;&nbsp;])<br/>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;the&nbsp;template&nbsp;compiler&nbsp;uses&nbsp;esm&nbsp;exports<br/>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;`export&nbsp;*&nbsp;from&nbsp;{request}`<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;if&nbsp;a&nbsp;custom&nbsp;block&nbsp;has&nbsp;no&nbsp;other&nbsp;matching&nbsp;loader&nbsp;other&nbsp;than&nbsp;vue-loader&nbsp;itself,<br/>&nbsp;&nbsp;//&nbsp;we&nbsp;should&nbsp;ignore&nbsp;it<br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;`custom`&nbsp;&amp;&amp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaders.length&nbsp;===&nbsp;1&nbsp;&amp;&amp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaders[0].path&nbsp;===&nbsp;selfPath)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;``<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;When&nbsp;the&nbsp;user&nbsp;defines&nbsp;a&nbsp;rule&nbsp;that&nbsp;has&nbsp;only&nbsp;resourceQuery&nbsp;but&nbsp;no&nbsp;test,<br/>&nbsp;&nbsp;//&nbsp;both&nbsp;that&nbsp;rule&nbsp;and&nbsp;the&nbsp;cloned&nbsp;rule&nbsp;will&nbsp;match,&nbsp;resulting&nbsp;in&nbsp;duplicated<br/>&nbsp;&nbsp;//&nbsp;loaders.&nbsp;Therefore&nbsp;it&nbsp;is&nbsp;necessary&nbsp;to&nbsp;perform&nbsp;a&nbsp;dedupe&nbsp;here.<br/>&nbsp;&nbsp;const&nbsp;request&nbsp;=&nbsp;genRequest(loaders)<br/>&nbsp;&nbsp;return&nbsp;`import&nbsp;mod&nbsp;from&nbsp;{request};&nbsp;export&nbsp;default&nbsp;mod;&nbsp;export&nbsp;*&nbsp;from&nbsp;{request}`<br/>}<br/></code></pre><p>对于 style block 的处理,首先判断是否有 css-loader,如果有的话就重新生成一个新的 request,这个 request 包含了 vue-loader 内部提供的 stylePostLoader,并返回一个 js module,根据 pitch 函数的规则,pitcher loader 后面的 loader 都会被跳过,这个时候开始编译这个返回的 js module。相关的内容为:</p><pre><code>import&nbsp;mod&nbsp;from&nbsp;&quot;-!../node_modules/vue-style-loader/index.js!../node_modules/css-loader/index.js!../lib/loaders/stylePostLoader.js!../lib/index.js??vue-loader-options!./source.vue?vue&amp;type=style&amp;index=0&amp;id=27e4e96e&amp;scoped=true&amp;lang=css&amp;&quot;<br/>export&nbsp;default&nbsp;mod<br/>export&nbsp;*&nbsp;from&nbsp;&quot;-!../node_modules/vue-style-loader/index.js!../node_modules/css-loader/index.js!../lib/loaders/stylePostLoader.js!../lib/index.js??vue-loader-options!./source.vue?vue&amp;type=style&amp;index=0&amp;id=27e4e96e&amp;scoped=true&amp;lang=css&amp;&quot;<br/></code></pre><p>对于 template block 的处理流程类似,生成一个新的 request,这个 request 包含了 vue-loader 内部提供的 templateLoader,并返回一个 js module,并跳过后面的 loader,然后开始编译返回的 js module。相关的内容为:</p><pre><code>export&nbsp;*&nbsp;from&nbsp;&quot;-!../lib/loaders/templateLoader.js??vue-loader-options!../node_modules/pug-plain-loader/index.js!../lib/index.js??vue-loader-options!./source.vue?vue&amp;type=template&amp;id=27e4e96e&amp;scoped=true&amp;lang=pug&amp;&quot;<br/></code></pre><p>这样对于一个 Vue SFC 处理的第二阶段也就结束了,通过 pitcher loader 去拦截不同类型的 block,并返回新的 js module,跳过后面的 loader 的执行,同时在内部会剔除掉 pitcher loader,这样在进入到下一个处理阶段的时候,pitcher loader 不在使用的 loader 范围之内,因此下一阶段 Vue SFC 便不会经由 pitcher loader 来处理。</p><h2>4.Step 3</h2><p>接下来进入到第三个阶段,编译返回的新的 js module,完成 AST 的解析和依赖收集工作,并开始处理不同类型的 block 的编译转换工作。就拿 Vue SFC 当中的 style / template block 来举例,</p><p>style block 会经过以下的流程处理:</p><p>source.vue?vue&amp;type=style -&gt; vue-loader(抽离 style block) -&gt; stylePostLoader(处理作用域 scoped css) -&gt; css-loader(处理相关资源引入路径) -&gt; vue-style-loader(动态创建 style 标签插入 css)</p><p><img src="https://static001.infoq.cn/resource/image/52/81/52cfd8bf428753f737d8fc4d0cbdb581.png" alt=""/></p><p>template block 会经过以下的流程处理:</p><p>source.vue?vue&amp;type=template -&gt; vue-loader(抽离 template block ) -&gt; pug-plain-loader(将 pug 模块转化为 html 字符串) -&gt; templateLoader(编译 html 模板字符串,生成 render/staticRenderFns 函数并暴露出去)</p><p><img src="https://static001.infoq.cn/resource/image/bd/5c/bdfec6cb56b0da86db24249869b4765c.png" alt=""/></p><p>我们看到经过 vue-loader 处理时,会根据不同 module path 的类型(query 参数上的 type 字段)来抽离 SFC 当中不同类型的 block。这也是 vue-loader 内部定义的相关规则:</p><pre><code>//&nbsp;vue-loader/lib/index.js<br/><br/>const&nbsp;qs&nbsp;=&nbsp;require('querystring')<br/>const&nbsp;selectBlock&nbsp;=&nbsp;require('./select')<br/>...<br/><br/>module.exports&nbsp;=&nbsp;function&nbsp;(source)&nbsp;{<br/>&nbsp;&nbsp;...<br/>&nbsp;&nbsp;const&nbsp;rawQuery&nbsp;=&nbsp;resourceQuery.slice(1)<br/>&nbsp;&nbsp;const&nbsp;inheritQuery&nbsp;=&nbsp;`&amp;{rawQuery}`<br/>&nbsp;&nbsp;const&nbsp;incomingQuery&nbsp;=&nbsp;qs.parse(rawQuery)<br/><br/>&nbsp;&nbsp;...<br/>&nbsp;&nbsp;const&nbsp;descriptor&nbsp;=&nbsp;parse({<br/>&nbsp;&nbsp;&nbsp;&nbsp;source,<br/>&nbsp;&nbsp;&nbsp;&nbsp;compiler:&nbsp;options.compiler&nbsp;||&nbsp;loadTemplateCompiler(),<br/>&nbsp;&nbsp;&nbsp;&nbsp;filename,<br/>&nbsp;&nbsp;&nbsp;&nbsp;sourceRoot,<br/>&nbsp;&nbsp;&nbsp;&nbsp;needMap:&nbsp;sourceMap<br/>&nbsp;&nbsp;})<br/><br/>&nbsp;&nbsp;//&nbsp;if&nbsp;the&nbsp;query&nbsp;has&nbsp;a&nbsp;type&nbsp;field,&nbsp;this&nbsp;is&nbsp;a&nbsp;language&nbsp;block&nbsp;request<br/>&nbsp;&nbsp;//&nbsp;e.g.&nbsp;foo.vue?type=template&amp;id=xxxxx<br/>&nbsp;&nbsp;//&nbsp;and&nbsp;we&nbsp;will&nbsp;return&nbsp;early<br/>&nbsp;&nbsp;if&nbsp;(incomingQuery.type)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;selectBlock(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaderContext,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;incomingQuery,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;!!options.appendExtension<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;}<br/>&nbsp;&nbsp;...<br/>}<br/></code></pre><p>当 module path 上的 query 参数带有 type 字段,那么会直接调用 selectBlock 方法去获取 type 对应类型的 block 内容,跳过 vue-loader 后面的处理流程(这也是与 vue-loader 第一次处理这个 module 时流程不一样的地方),并进入到下一个 loader 的处理流程中,selectBlock 方法内部主要就是根据不同的 type 类型(template/script/style/custom),来获取 descriptor 上对应类型的 content 内容并传入到下一个 loader 处理:</p><pre><code>module.exports&nbsp;=&nbsp;function&nbsp;selectBlock&nbsp;(<br/>&nbsp;&nbsp;descriptor,<br/>&nbsp;&nbsp;loaderContext,<br/>&nbsp;&nbsp;query,<br/>&nbsp;&nbsp;appendExtension<br/>)&nbsp;{<br/>&nbsp;&nbsp;//&nbsp;template<br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;`template`)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(appendExtension)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.resourcePath&nbsp;+=&nbsp;'.'&nbsp;+&nbsp;(descriptor.template.lang&nbsp;||&nbsp;'html')<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.callback(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;null,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor.template.content,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor.template.map<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;return<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;script<br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;`script`)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(appendExtension)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.resourcePath&nbsp;+=&nbsp;'.'&nbsp;+&nbsp;(descriptor.script.lang&nbsp;||&nbsp;'js')<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.callback(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;null,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor.script.content,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;descriptor.script.map<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;return<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;styles<br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;`style`&nbsp;&amp;&amp;&nbsp;query.index&nbsp;!=&nbsp;null)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;style&nbsp;=&nbsp;descriptor.styles[query.index]<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(appendExtension)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.resourcePath&nbsp;+=&nbsp;'.'&nbsp;+&nbsp;(style.lang&nbsp;||&nbsp;'css')<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.callback(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;null,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;style.content,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;style.map<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;return<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;//&nbsp;custom<br/>&nbsp;&nbsp;if&nbsp;(query.type&nbsp;===&nbsp;'custom'&nbsp;&amp;&amp;&nbsp;query.index&nbsp;!=&nbsp;null)&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;block&nbsp;=&nbsp;descriptor.customBlocks[query.index]<br/>&nbsp;&nbsp;&nbsp;&nbsp;loaderContext.callback(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;null,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;block.content,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;block.map<br/>&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;return<br/>&nbsp;&nbsp;}<br/>}<br/></code></pre><h2>5.总结</h2><p>通过 vue-loader 的源码我们看到一个 Vue SFC 在整个编译构建环节是怎么样一步一步处理的,这也是得益于 webpack 给开发这提供了这样一种 loader 的机制,使得开发者通过这样一种方式去对项目源码做对应的转换工作以满足相关的开发需求。结合之前的 2 篇(webpack loader 详解 1 和 webpack loader 详解 2)有关 webpack loader 源码的分析,大家应该对 loader 有了更加深入的理解,也希望大家活学活用,利用 loader 机制去完成更多贴合实际需求的开发工作。</p><p><strong>本文转载自公众号滴滴技术(ID:didi_tech)。</strong></p><p><strong>原文链接:</strong></p><p><a href="https://mp.weixin.qq.com/s/yGibmamkMiyj9MaXsdalhw">https://mp.weixin.qq.com/s/yGibmamkMiyj9MaXsdalhw</a></p>


2019-09-19 16:552579

评论

发布
暂无评论
发现更多内容

领航全球云经济行业创新,XTransfer入选Bessemer年度报告

XTransfer技术

从 vue3 和 vite 源码中,我学到了一行代码统一规范团队包管理器的神器

若川

JavaScript 前端 前端开发 Node web前端开发

华为云&易观分析《互联网出海白皮书2022》:新兴市场成掘金热土

易观分析

华为云 出海

MOVE PROTOCOL全球健康宣言,将健康运动进行到底

西柚子

数据中心进化论:华为带来的三个路标与新可能

脑极体

MOVE PROTOCOL全球健康宣言,将健康运动进行到底

小哈区块

面试官:项目中常用的 .env 文件原理是什么?如何实现?

若川

JavaScript 前端 Node

使用 FutureProvider 搞定个人主页异步请求的状态管理

岛上码农

flutter ios 前端 安卓开发 5月月更

LabVIEW控制Arduino采集电位器电压(基础篇—4)

不脱发的程序猿

单片机 LabVIEW LIAT Arduino Uno 采集电位器电压

LabVIEW控制Arduino实现PWM呼吸灯(基础篇—5)

不脱发的程序猿

单片机 LabVIEW LIAT Arduino Uno PWM呼吸灯

什么是区块哈希?哈希游戏开发技术原理方案(源码搭建)

开发微hkkf5566

直播预告|来啦!「DevOps+MLOps Meetup」火热报名中!

第四范式开发者社区

人工智能 DevOps 极狐GitLab 特征平台 MLOps

Vue3 TypeScript 使用教程 - 实战 Vue3 element-plus 开发「待办清单」

蒋川

typescript 低代码 开发工具 Vue 3 Element Plus

网易数帆 Envoy Gateway 实践之旅:坚守 6 年,峥嵘渐显

网易数帆

云原生 Service Mesh istio api 网关 envoy

Vue团队核心成员开发的39行小工具 install-pkg 安装包,值得一学

若川

JavaScript 源码 前端 前端开发 Node

动态规划:打家劫舍 ⛄

空城机

算法 5月月更

200多家ISV入驻!阿里云计算巢发布一周年

阿里云弹性计算

计算巢

得物Tech Leader对管理授权的思考是什么?/得物技术管理集锦

得物技术

管理 思考 技术管理 事前授权 任务分配

leetcode 81. Search in Rotated Sorted Array II 搜索旋转排序数组 II(中等)

okokabcd

LeetCode 查找

从 vue-cli 源码中,我发现了27行读取 json 文件有趣的 npm 包

若川

JavaScript 前端 前端开发 Node web前端开发

ZEGO 开发者文档速查|常用视频配置

ZEGO即构

音视频开发 视频配置

融云漫话:通信中台

融云 RongCloud

MDM结合ESB数据集成说明

agileai

数据治理 数据集成 企业服务总线 主数据平台 基础数据方案

中国20强游戏公司2021年财报分析:业绩大分化时代,开始优胜劣汰

易观分析

游戏公司

开讲!XTransfer技术专家亮相Flink CDC Meetup

XTransfer技术

flink XTransfer

报名进入尾声,赶快申请加入 sealer 开源之夏吧!

阿里巴巴云原生

阿里云 开源 Kubernetes 云原生 sealer

面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么

若川

JavaScript 源码 前端 前端开发 axios

LabVIEW控制Arduino流水灯(基础篇—3)

不脱发的程序猿

单片机 LabVIEW Arduino LIAT 流水灯

模块七:作业

本人法海

「架构实战营」

最佳实践|从Producer 到 Consumer,如何有效监控 Kafka

阿里巴巴云原生

kafka 阿里云 云原生 Promethues

什么是智能合约?3M互助dapp系统开发逻辑拆解

开发微hkkf5566

webpack 系列之四 loader 详解3_文化 & 方法_肖磊_InfoQ精选文章