写点什么

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:551571

评论

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

第十一届网络视听大会:鸿蒙激活产业创新,以系统级AI技术赋能体验升级

最新动态

wiztree免费的c盘清理软件

源字节1号

开源 软件开发 前端开发 后端开发 小程序开发

如何规避数字人直播间封禁风险?

青否数字人

数字人

CMake调用第三方库的两种方法

百度搜索:蓝易云

Linux 运维 云服务器 cmake find_package

慢工之旅:婺源的故事

明道云

关于 ASP.NET Core 中的管道和中间件

雄鹿 @

ASP.NET Core

LoRA 及其衍生技术总览:An Overview of the LoRA Family

Baihai IDP

人工智能 程序员 AI LoRa 白海科技

Sora原理?Sora技术报告解读(思维导图版)

蓉蓉

sora

火出圈的Sora到底是什么?5000字长文带你彻底了解Sora!

蓉蓉

openai GPT-4 sora

糖豆完成鸿蒙原生应用核心版本开发,开创全场景视频健身新潮流

最新动态

DOM(文档对象模型):理解网页结构与内容操作的关键技术

小万哥

xml 程序人生 编程语言 软件工程 前端开发

解决centos离线安装cmake找不到OpenSSL问题

百度搜索:蓝易云

Linux centos 运维 openssl cmake

【Ubuntu20.04】安装gcc11 g++11, Ubuntu18.04

百度搜索:蓝易云

Linux ubuntu 运维 云服务器 gcc11

实时渲染是什么意思?实时渲染和离线渲染的区别

3DCAT实时渲染

实时渲染

青否交互数字人源码的亮点!

青否数字人

数字人

containerd快速安装指南🚀

GousterCloud

Docker cRI Containerd

一文看懂Unibot solana链狙击机器人

开发丨飞机丨 @aivenli

2024-03-30:用go语言,集团里有 n 名员工,他们可以完成各种各样的工作创造利润, 第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与, 如果成员参与

福大大架构师每日一题

福大大架构师每日一题

containerd系统级学习大纲

GousterCloud

容器 云原生 Serverless Kubernetes Containerd

查看服务器/IIS日志、log、访问信息基本方法

百度搜索:蓝易云

云计算 运维 服务器 IIS 云服务器

使用git克隆仓库报错:Warning: Permanently added‘github.com’ to the .....(ssh )

百度搜索:蓝易云

云计算 Linux 运维 云服务器 ECS

2024中国充电桩展|2024常州国际充电设施展会

秋硕展览

事业-最佳实践-编码-单元测试-测试方法结构

南山

最佳实践 单元测试 编码规范 #ATRIP

《算法(第4版)(中文版)》PDF

程序员李木子

贺!伊克罗德信息与墨奇科技战略合作,共创生成式AI新未来

伊克罗德信息科技

#人工智能

回南天、沙尘天轮番来袭?华为天气这份每日早报请及时查收!

最新动态

事业-最佳实践-编码-单元测试-改变认知

南山

测试 单元测试

Vim如何清空文件

百度搜索:蓝易云

vim 云计算 Linux 运维 云服务器

Mysql/etc/my.cnf参数详解

百度搜索:蓝易云

MySQL Linux 运维 云服务器 /etc/my.cnf

《自动机理论、语言和计算导论》阅读笔记:p68-p114

codists

编译原理

关于 ASP.NET Core 中的文件提供程序

雄鹿 @

ASP.NET Core

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