深入浅出 Vue-Loader 自定义块

开发 前端
vue-i18n[1] 是 Vue 的国际化插件。如果使用 SFC 的方式写组件的话,可以在 .vue 文件中定义 <i18n> 块 ,然后在块内写入对应的词条。

[[437854]]

 本文转载自微信公众号「码农小余」,作者Jouryjc 。转载本文请联系码农小余公众号。

本文大纲:

  • 通过 vue-i18n 的 了解 customBlocks 和基本配置;
  • 从源码层面了解 vue-loader 对 customBlocks 的处理

vue-i18n

vue-i18n[1] 是 Vue 的国际化插件。如果使用 SFC 的方式写组件的话,可以在 .vue 文件中定义 块 ,然后在块内写入对应的词条。这个 i18n 标签就是 customBlocks。举个例子:

  1. <template> 
  2.   <p>{{ $t('hello') }}</p> 
  3. </template> 
  4.  
  5. <script> 
  6. // App.vue 
  7. export default { 
  8.   name'App' 
  9. </script> 
  10.  
  11. <i18n locale="en"
  12.   "hello""hello, world!!!!" 
  13. </i18n> 
  14.  
  15. <i18n locale="ja"
  16.   "hello""こんにちは、世界!" 
  17. </i18n> 
  1. // main.js 
  2. import Vue from 'vue' 
  3. import VueI18n from 'vue-i18n' 
  4. import App from './App.vue' 
  5.  
  6. Vue.use(VueI18n) 
  7.  
  8. const i18n = new VueI18n({ 
  9.   locale: 'ja'
  10.   messages: {} 
  11. }) 
  12.  
  13. new Vue({ 
  14.   i18n, 
  15.   el: '#app'
  16.   render: h => h(App) 
  17. }) 

上述代码定义了日文和英文两种语法,只要改变 locale 的值,就能达到切换语言的效果。除了上述用法,还支持支持引入 yaml 或者 json 等文件:

  1. <i18n src="./locales.json"></i18n> 
  1. // locales.json 
  2.   "en": { 
  3.     "hello""hello world" 
  4.   }, 
  5.   "ja": { 
  6.     "hello""こんにちは、世界" 
  7.   } 

<i18n>其他用法可以查阅使用文档[2];

要让 customBlock 起作用,需要指定 customBlock 的 loader,如果没有指定,对应的块会默默被忽略。?? 中的 webpack 配置:

  1. const path = require('path'
  2. const VueLoaderPlugin = require('vue-loader/lib/plugin'
  3.  
  4. module.exports = { 
  5.   mode: 'development'
  6.   entry: path.resolve(__dirname, './main.js'), 
  7.   output: { 
  8.     path: path.resolve(__dirname, 'dist'), 
  9.     filename: 'bundle.js'
  10.     publicPath: '/dist/' 
  11.   }, 
  12.   devServer: { 
  13.     stats: 'minimal'
  14.     contentBase: __dirname 
  15.   }, 
  16.   module: { 
  17.     rules: [ 
  18.       { 
  19.         test: /\.vue$/, 
  20.         loader: 'vue-loader' 
  21.       }, 
  22.       { 
  23.         test: /\.js$/, 
  24.         loader: 'babel-loader' 
  25.       }, 
  26.       //  customBlocks 对应的 rule 
  27.       { 
  28.         // 使用 resourceQuery 来为一个没有 lang 的自定义块匹配一条规则 
  29.         // 如果找到了一个自定义块的匹配规则,它将会被处理,否则该自定义块会被默默忽略 
  30.         resourceQuery: /blockType=i18n/, 
  31.         // Rule.type 设置类型用于匹配模块。它防止了 defaultRules 和它们的默认导入行为发生 
  32.         type: 'javascript/auto'
  33.         // 这里指的是 vue-i18n-loader 
  34.         use: [path.resolve(__dirname, '../lib/index.js')] 
  35.       } 
  36.     ] 
  37.   }, 
  38.   plugins: [new VueLoaderPlugin()] 

从上述代码可以看到,如果你要在 SFC 中使用 customBlock 功能,只需要下面两步:

实现一个处理 customBlock 的 loader 函数;

配置 webpack.module.rules ,指定 resourceQuery: /blockType=你的块名称/ 然后使用步骤一的 loader 去处理即可;

源码分析

通常一个 loader 都是具体某一种资源的转换、加载器,但 vue-loader 不是,它能够处理每一个定义在 SFC 中的块:通过拆解 block -> 组合 loader -> 处理 block -> 组合每一个 block 的结果为最终代码的工作流,完成对 SFC 的处理。下面我们就依次详细地拆解这条流水线!

拆解 block

我们知道,使用 vue-loader 一定需要引入 vue-loader-plugin,不然的话就会给你报一个大大的错误:

  1. `vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.` 

VueLoaderPlugin 定义在 vue-loader\lib\plugin-webpack4.js:

  1. const id = 'vue-loader-plugin' 
  2. const NS = 'vue-loader' 
  3.  
  4. class VueLoaderPlugin { 
  5.   apply (compiler) { 
  6.     // add NS marker so that the loader can detect and report missing plugin 
  7.     if (compiler.hooks) { 
  8.       // webpack 4 
  9.       compiler.hooks.compilation.tap(id, compilation => { 
  10.         const normalModuleLoader = compilation.hooks.normalModuleLoader // 同步钩子,管理所有模块loader 
  11.         normalModuleLoader.tap(id, loaderContext => { 
  12.           loaderContext[NS] = true 
  13.         }) 
  14.       }) 
  15.     } 
  16.  
  17.     // use webpack's RuleSet utility to normalize user rules 
  18.     const rawRules = compiler.options.module.rules 
  19.     // https://webpack.js.org/configuration/module/#modulerules 
  20.     const { rules } = new RuleSet(rawRules) 
  21.      
  22.     // 将你定义过的 loader 复制并应用到 .vue 文件里相应语言的块 
  23.     const clonedRules = rules 
  24.       .filter(r => r !== vueRule) 
  25.       .map(cloneRule) 
  26.  
  27.     // ... 
  28.     // 个人对这个命名的理解是 pitcher 是投手的意思,进球得分,所以可以理解成给当前的块和 loader 丰富功能 😁 
  29.     // 给 template 块加 template-loader,给 style 块加 stype-post-loader 
  30.     // 其他功能...后面再看 
  31.     const pitcher = { 
  32.       loader: require.resolve('./loaders/pitcher'), 
  33.       resourceQuery: query => { 
  34.         const parsed = qs.parse(query.slice(1)) 
  35.         return parsed.vue != null 
  36.       }, 
  37.       options: { 
  38.         cacheDirectory: vueLoaderUse.options.cacheDirectory, 
  39.         cacheIdentifier: vueLoaderUse.options.cacheIdentifier 
  40.       } 
  41.     } 
  42.  
  43.     // 覆盖原来的rules配置 
  44.     compiler.options.module.rules = [ 
  45.       pitcher, 
  46.       ...clonedRules, 
  47.       ...rules 
  48.     ] 
  49.   } 

VueLoaderPlugin 作用是将你定义的其他 loader 添加到 SFC 的各个块中并修改配置中的 module.rules。pitcher-loader[3] 是后续一个重要的角色。阿宝哥的多图详解,一次性搞懂Webpack Loader[4]有详细的分享,没了解过滴童鞋可以先去认识一下这个“投手”的作用。

了解完 VueLoaderPlugin,我们看到 vue-loader:

  1. module.exports = function (source) { 
  2.   const loaderContext = this 
  3.  
  4.   // ... 
  5.   // 编译 SFC —— 解析.vue文件,生成不同的 block 
  6.   const descriptor = parse({ 
  7.     source, 
  8.     compiler: options.compiler || loadTemplateCompiler(loaderContext),  // 默认使用 vue-template-compiler 
  9.     filename, 
  10.     sourceRoot, 
  11.     needMap: sourceMap 
  12.   }) 
  13.    
  14.   // ... 

本小节核心就是这个 parse 方法。将 SFC 代码传通过自定义编译器或者默认的 @vue/component-compiler-utils 去解析。具体执行过程这里就不展开详细分析了,感兴趣童鞋可以前往[咖聊] “模板编译”真经。生成的 descriptor 结果如下图所示:

接下来就针对 descriptor 的每一个 key 去生成第一次代码:

  1. module.exports = function (source) { 
  2.   const loaderContext = this 
  3.  
  4.   // ... 
  5.   // 编译 SFC —— 解析.vue文件,生成不同的 block 
  6.   const descriptor = parse({ 
  7.     source, 
  8.     compiler: options.compiler || loadTemplateCompiler(loaderContext),  // 默认使用 vue-template-compiler 
  9.     filename, 
  10.     sourceRoot, 
  11.     needMap: sourceMap 
  12.   }) 
  13.    
  14.   // ... 
  15.   // template 
  16.   let templateImport = `var render, staticRenderFns` 
  17.   let templateRequest 
  18.   if (descriptor.template) { 
  19.     const src = descriptor.template.src || resourcePath 
  20.     const idQuery = `&id=${id}` 
  21.     const scopedQuery = hasScoped ? `&scoped=true` : `` 
  22.     const attrsQuery = attrsToQuery(descriptor.template.attrs) 
  23.     const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}` 
  24.     const request = templateRequest = stringifyRequest(src + query) 
  25.     templateImport = `import { render, staticRenderFns } from ${request}` 
  26.   } 
  27.  
  28.   // script 
  29.   let scriptImport = `var script = {}` 
  30.   if (descriptor.script) { 
  31.     const src = descriptor.script.src || resourcePath 
  32.     const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js'
  33.     const query = `?vue&type=script${attrsQuery}${inheritQuery}` 
  34.     const request = stringifyRequest(src + query) 
  35.     scriptImport = ( 
  36.       `import script from ${request}\n` + 
  37.       `export * from ${request}` // support named exports 
  38.     ) 
  39.   } 
  40.  
  41.   // styles 
  42.   let stylesCode = `` 
  43.   if (descriptor.styles.length) { 
  44.     stylesCode = genStylesCode( 
  45.       loaderContext, 
  46.       descriptor.styles, 
  47.       id, 
  48.       resourcePath, 
  49.       stringifyRequest, 
  50.       needsHotReload, 
  51.       isServer || isShadow // needs explicit injection? 
  52.     ) 
  53.   } 
  54.  
  55.   let code = ` 
  56. ${templateImport} 
  57. ${scriptImport} 
  58. ${stylesCode} 
  59.  
  60. /* normalize component */ 
  61. import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)} 
  62. var component = normalizer( 
  63.   script, 
  64.   render, 
  65.   staticRenderFns, 
  66.   ${hasFunctional ? `true` : `false`}, 
  67.   ${/injectStyles/.test(stylesCode) ? `injectStyles` : `null`}, 
  68.   ${hasScoped ? JSON.stringify(id) : `null`}, 
  69.   ${isServer ? JSON.stringify(hash(request)) : `null`} 
  70.   ${isShadow ? `,true` : ``} 
  71.   `.trim() + `\n` 
  72.    
  73.   // 判断是否有customBlocks,调用genCustomBlocksCode生成自定义块的代码 
  74.   if (descriptor.customBlocks && descriptor.customBlocks.length) { 
  75.     code += genCustomBlocksCode( 
  76.       descriptor.customBlocks, 
  77.       resourcePath, 
  78.       resourceQuery, 
  79.       stringifyRequest 
  80.     ) 
  81.   } 
  82.   // ...省略一些热更代码 
  83.    
  84.   return code 
  85.  
  86. // vue-loader\lib\codegen\customBlocks.js 
  87. module.exports = function genCustomBlocksCode ( 
  88.   blocks, 
  89.   resourcePath, 
  90.   resourceQuery, 
  91.   stringifyRequest 
  92. ) { 
  93.   return `\n/* custom blocks */\n` + blocks.map((block, i) => { 
  94.     // i18n有很多种用法,有通过src直接引入其他资源的用法,这里就是获取这个参数 
  95.     // 对于demo而言,没有定义外部资源,这里是'' 
  96.     const src = block.attrs.src || resourcePath 
  97.     // 获取其他属性,demo中就是&locale=en和&locale=ja 
  98.     const attrsQuery = attrsToQuery(block.attrs) 
  99.     // demo中是'' 
  100.     const issuerQuery = block.attrs.src ? `&issuerPath=${qs.escape(resourcePath)}` : '' 
  101.     // demo中是'' 
  102.     const inheritQuery = resourceQuery ? `&${resourceQuery.slice(1)}` : '' 
  103.     const query = `?vue&type=custom&index=${i}&blockType=${qs.escape(block.type)}${issuerQuery}${attrsQuery}${inheritQuery}` 
  104.     return ( 
  105.       `import block${i} from ${stringifyRequest(src + query)}\n` + 
  106.       `if (typeof block${i} === 'function') block${i}(component)` 
  107.     ) 
  108.   }).join(`\n`) + `\n` 

template、style、script 这些块我们直接略过,重点看看 customBlocks 的处理逻辑。逻辑比较简单,遍历 customBlocks 去获取一些 query 变量,最终返回 customBlocks code。我们看看最终通过第一次调用 vue-loader 返回的 code:

  1. /* template块 */ 
  2. import { render, staticRenderFns } from "./App.vue?vue&type=template&id=a9794c84&" 
  3. /* script 块 */ 
  4. import script from "./App.vue?vue&type=script&lang=js&" 
  5. export * from "./App.vue?vue&type=script&lang=js&" 
  6.  
  7.  
  8. /* normalize component */ 
  9. import normalizer from "!../node_modules/vue-loader/lib/runtime/componentNormalizer.js" 
  10. var component = normalizer( 
  11.   script, 
  12.   render, 
  13.   staticRenderFns, 
  14.   false
  15.   null
  16.   null
  17.   null 
  18.    
  19.  
  20. /* 自定义块,例子中即 <i18n> 块的代码 */ 
  21. import block0 from "./App.vue?vue&type=custom&index=0&blockType=i18n&locale=en" 
  22. if (typeof block0 === 'function') block0(component) 
  23. import block1 from "./App.vue?vue&type=custom&index=1&blockType=i18n&locale=ja" 
  24. if (typeof block1 === 'function') block1(component) 
  25.  
  26. /* hot reload */ 
  27. if (module.hot) { 
  28.   var api = require("C:\\Jouryjc\\vue-i18n-loader\\node_modules\\vue-hot-reload-api\\dist\\index.js"
  29.   api.install(require('vue')) 
  30.   if (api.compatible) { 
  31.     module.hot.accept() 
  32.     if (!api.isRecorded('a9794c84')) { 
  33.       api.createRecord('a9794c84', component.options) 
  34.     } else { 
  35.       api.reload('a9794c84', component.options) 
  36.     } 
  37.     module.hot.accept("./App.vue?vue&type=template&id=a9794c84&"function () { 
  38.       api.rerender('a9794c84', { 
  39.         render: render, 
  40.         staticRenderFns: staticRenderFns 
  41.       }) 
  42.     }) 
  43.   } 
  44. component.options.__file = "example/App.vue" 
  45. export default component.exports 

紧接着继续处理 import:

  1. /* template块 */ 
  2. import { render, staticRenderFns } from "./App.vue?vue&type=template&id=a9794c84&" 
  3. /* script 块 */ 
  4. import script from "./App.vue?vue&type=script&lang=js&" 
  5.  
  6. /* 自定义块,例子中即 <i18n> 块的代码 */ 
  7. import block0 from "./App.vue?vue&type=custom&index=0&blockType=i18n&locale=en" 
  8. import block1 from "./App.vue?vue&type=custom&index=1&blockType=i18n&locale=ja" 

组合 loader

我们可以看到,上述所有资源都有 ?vue 的 query 参数,匹配到了 pitcher-loader ,该“投手”登场了。分析下 import block0 from "./App.vue?vue&type=custom&index=0&blockType=i18n&locale=en" 处理:

  1. module.exports.pitch = function (remainingRequest) { 
  2.   const options = loaderUtils.getOptions(this) 
  3.   const { cacheDirectory, cacheIdentifier } = options 
  4.   const query = qs.parse(this.resourceQuery.slice(1)) 
  5.  
  6.   let loaders = this.loaders 
  7.  
  8.   // if this is a language block request, eslint-loader may get matched 
  9.   // multiple times 
  10.   if (query.type) { 
  11.     // 剔除eslint-loader 
  12.     if (/\.vue$/.test(this.resourcePath)) { 
  13.       loaders = loaders.filter(l => !isESLintLoader(l)) 
  14.     } else { 
  15.       // This is a src import. Just make sure there's not more than 1 instance 
  16.       // of eslint present. 
  17.       loaders = dedupeESLintLoader(loaders) 
  18.     } 
  19.   } 
  20.  
  21.   // 提取pitcher-loader 
  22.   loaders = loaders.filter(isPitcher) 
  23.  
  24.   // do not inject if user uses null-loader to void the type (#1239) 
  25.   if (loaders.some(isNullLoader)) { 
  26.     return 
  27.   } 
  28.  
  29.   const genRequest = loaders => { 
  30.     // Important: dedupe since both the original rule 
  31.     // and the cloned rule would match a source import request. 
  32.     // also make sure to dedupe based on loader path. 
  33.     // assumes you'd probably never want to apply the same loader on the same 
  34.     // file twice. 
  35.     // Exception: in Vue CLI we do need two instances of postcss-loader 
  36.     // for user config and inline minification. So we need to dedupe baesd on 
  37.     // path AND query to be safe. 
  38.     const seen = new Map() 
  39.     const loaderStrings = [] 
  40.  
  41.     loaders.forEach(loader => { 
  42.       const identifier = typeof loader === 'string' 
  43.         ? loader 
  44.         : (loader.path + loader.query) 
  45.       const request = typeof loader === 'string' ? loader : loader.request 
  46.       if (!seen.has(identifier)) { 
  47.         seen.set(identifier, true
  48.         // loader.request contains both the resolved loader path and its options 
  49.         // query (e.g. ??ref-0) 
  50.         loaderStrings.push(request) 
  51.       } 
  52.     }) 
  53.  
  54.     return loaderUtils.stringifyRequest(this, '-!' + [ 
  55.       ...loaderStrings, 
  56.       this.resourcePath + this.resourceQuery 
  57.     ].join('!')) 
  58.   } 
  59.  
  60.   // script、template、style... 
  61.  
  62.   // if a custom block has no other matching loader other than vue-loader itself 
  63.   // or cache-loader, we should ignore it 
  64.   // 如果除了vue-loader没有其他的loader,就直接忽略 
  65.   if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) { 
  66.     return `` 
  67.   } 
  68.  
  69.   // When the user defines a rule that has only resourceQuery but no test, 
  70.   // both that rule and the cloned rule will match, resulting in duplicated 
  71.   // loaders. Therefore it is necessary to perform a dedupe here. 
  72.   const request = genRequest(loaders) 
  73.   return `import mod from ${request}; export default mod; export * from ${request}` 

pitcher-loader 做了 3 件事:

  • 剔除 eslint-loader,避免重复 lint;
  • 剔除 pitcher-loader 自身;
  • 根据不同的 query.type,生成对应的 request,并返回结果;

中 customBlocks 返回的结果如下:

  1. import mod from "-!../lib/index.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=custom&index=0&blockType=i18n&locale=en"
  2. export default mod; 
  3. export * from "-!../lib/index.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=custom&index=0&blockType=i18n&locale=en" 
  4.  
  5. // ja 
  6. import mod from "-!../lib/index.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=custom&index=1&blockType=i18n&locale=ja"
  7. export default mod; 
  8. export * from "-!../lib/index.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=custom&index=1&blockType=i18n&locale=ja" 

处理 block

根据 import 的表达式,我们可以看到,此时会通过 vue-loader -> vue-i18n-loader 依次处理拿到结果,此时再进入到 vue-loader 跟前面第一次生成 code 不一样的地方是:此时 incomingQuery.type 是有值的。对于 custom 而言,这里就是 custom:

  1. // ... 
  2. // if the query has a type field, this is a language block request 
  3. // e.g. foo.vue?type=template&id=xxxxx 
  4. // and we will return early 
  5. if (incomingQuery.type) { 
  6.     return selectBlock( 
  7.         descriptor, 
  8.         loaderContext, 
  9.         incomingQuery, 
  10.         !!options.appendExtension 
  11.     ) 
  12. // ... 

会执行到 selectBlock:

  1. module.exports = function selectBlock ( 
  2.   descriptor, 
  3.   loaderContext, 
  4.   query, 
  5.   appendExtension 
  6. ) { 
  7.   // template 
  8.   // script 
  9.   // style 
  10.  
  11.   // custom 
  12.   if (query.type === 'custom' && query.index != null) { 
  13.     const block = descriptor.customBlocks[query.index
  14.     loaderContext.callback( 
  15.       null
  16.       block.content, 
  17.       block.map 
  18.     ) 
  19.     return 
  20.   } 

最后会执行到 vue-i18n-loader:

  1. const loader: webpack.loader.Loader = function ( 
  2.   source: string | Buffer, 
  3.   sourceMap: RawSourceMap | undefined 
  4. ): void { 
  5.   if (this.version && Number(this.version) >= 2) { 
  6.     try { 
  7.       // 缓存结果,在输入和依赖没有发生改变时,直接使用缓存结果 
  8.       this.cacheable && this.cacheable() 
  9.       // 输出结果 
  10.       this.callback( 
  11.         null
  12.         `module.exports = ${generateCode(source, parse(this.resourceQuery))}`, 
  13.         sourceMap 
  14.       ) 
  15.     } catch (err) { 
  16.       this.emitError(err.message) 
  17.       this.callback(err) 
  18.     } 
  19.   } else { 
  20.     const message = 'support webpack 2 later' 
  21.     this.emitError(message) 
  22.     this.callback(new Error(message)) 
  23.   } 
  24.  
  25. /** 
  26.  * 将i18n标签生成代码 
  27.  * @param {string | Buffer} source 
  28.  * @param {ParsedUrlQuery} query 
  29.  * @returns {string} code 
  30.  */ 
  31. function generateCode(source: string | Buffer, query: ParsedUrlQuery): string { 
  32.   const data = convert(source, query.lang as string) 
  33.   let value = JSON.parse(data) 
  34.  
  35.   if (query.locale && typeof query.locale === 'string') { 
  36.     value = Object.assign({}, { [query.locale]: value }) 
  37.   } 
  38.  
  39.   // 特殊字符转义,\u2028 -> 行分隔符,\u2029 -> 段落分隔符,\\ 反斜杠 
  40.   value = JSON.stringify(value) 
  41.     .replace(/\u2028/g, '\\u2028'
  42.     .replace(/\u2029/g, '\\u2029'
  43.     .replace(/\\/g, '\\\\'
  44.  
  45.   let code = '' 
  46.   code += `function (Component) { 
  47.   Component.options.__i18n = Component.options.__i18n || [] 
  48.   Component.options.__i18n.push('${value.replace(/\u0027/g, '\\u0027')}'
  49.   delete Component.options._Ctor 
  50. }\n` 
  51.   return code 
  52.  
  53. /** 
  54.  * 转换各种用法为json字符串 
  55.  */ 
  56. function convert(source: string | Buffer, lang: string): string { 
  57.   const value = Buffer.isBuffer(source) ? source.toString() : source 
  58.  
  59.   switch (lang) { 
  60.     case 'yaml'
  61.     case 'yml'
  62.       const data = yaml.safeLoad(value) 
  63.       return JSON.stringify(data, undefined, '\t'
  64.     case 'json5'
  65.       return JSON.stringify(JSON5.parse(value)) 
  66.     default
  67.       return value 
  68.   } 
  69.  
  70. export default loader 

上述代码就比较简单了,拿到 source 生成 value,最终 push 到 Component.options.__i18n 中,针对不同的情况有不同的处理方式(json、yaml等)。

至此,整个 vue 文件就构建结束了, 最终构建完的代码如下:

  1. "./lib/index.js!./node_modules/vue-loader/lib/index.js?!./example/App.vue?vue&type=custom&index=0&blockType=i18n&locale=en"
  2. (function (module, exports) { 
  3.  
  4.     eval("module.exports = function (Component) {\n  Component.options.__i18n = Component.options.__i18n || []\n  Component.options.__i18n.push('{\"en\":{\"hello\":\"hello, world!!!!\"}}')\n  delete Component.options._Ctor\n}\n\n\n//# sourceURL=webpack:///./example/App.vue?./lib!./node_modules/vue-loader/lib??vue-loader-options"); 
  5.  
  6. }) 

至于 vue-i18n 怎么识别 Component.options.__i18n 就放一段代码,感兴趣可以去阅读 vue-i18n[5] 的代码哦。

  1. if (options.__i18n) { 
  2.     try { 
  3.         let localeMessages = options.i18n && options.i18n.messages ? options.i18n.messages : {}; 
  4.         options.__i18n.forEach(resource => { 
  5.             localeMessages = merge(localeMessages, JSON.parse(resource)); 
  6.         }); 
  7.         Object.keys(localeMessages).forEach((locale) => { 
  8.             options.i18n.mergeLocaleMessage(locale, localeMessages[locale]); 
  9.         }); 
  10.     } catch (e) { 
  11.         { 
  12.             error(`Cannot parse locale messages via custom blocks.`, e); 
  13.         } 
  14.     } 

本文从 vue-i18n 的工具切入,分享了如何在 SFC 中定义一个自定义块。然后从 vue-loader 源码分析了 SFC 的处理流程,整个过程如下图所示:

  1. 从 webpack 构建开始,会调用到插件,VueLoaderPlugin 在 normalModuleLoader 钩子上会被执行;
  2. 在引入 SFC 时,第一次匹配到 vue-loader,会通过 @vue/component-compiler-utils 将代码解析成不同的块,例如 template、script、style、custom;
  3. 生成的 code,会继续匹配 loader,?vue 会匹配上“投手”pitcher-loader;
  4. pitcher-loader 主要做 3 件事:首先因为 vue 整个文件已经被 lint 处理过了,所以局部代码时过滤掉 eslint-loader;其次过滤掉自身 pitcher-loader;最后通过 query.type 去生成不同的 request 和 code;
  5. 最终 code 会再次匹配上 vue-loader,此时第二次执行,incomingQuery.type 都会指定对应的块,所以会根据 type 调用 selectBlock 生成最终的块代码。

参考资料

[1]vue-i18n: https://kazupon.github.io/vue-i18n/

[2]使用文档: https://kazupon.github.io/vue-i18n/guide/sfc.html#basic-usage

[3]pitcher-loader: https://webpack.docschina.org/api/loaders/#pitching-loader

[4]多图详解,一次性搞懂Webpack Loader: https://juejin.cn/post/6992754161221632030#heading-3

[5]vue-i18n: https://github.com/kazupon/vue-i18n

 

责任编辑:武晓燕 来源: 码农小余
相关推荐

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2019-01-07 15:29:07

HadoopYarn架构调度器

2017-07-02 18:04:53

块加密算法AES算法

2012-05-21 10:06:26

FrameworkCocoa

2021-07-20 15:20:02

FlatBuffers阿里云Java

2022-09-26 09:01:15

语言数据JavaScript

2012-02-21 13:55:45

JavaScript

2019-11-11 14:51:19

Java数据结构Properties

2009-11-30 16:46:29

学习Linux

2009-11-18 13:30:37

Oracle Sequ

2022-12-02 09:13:28

SeataAT模式

2018-11-09 16:24:25

物联网云计算云系统

2019-12-04 10:13:58

Kubernetes存储Docker

2021-04-27 08:54:43

ConcurrentH数据结构JDK8

2022-01-11 07:52:22

CSS 技巧代码重构

2022-11-09 08:06:15

GreatSQLMGR模式

2022-10-31 09:00:24

Promise数组参数

2021-07-19 11:54:15

MySQL优先队列

2010-07-26 12:57:12

OPhone游戏开发
点赞
收藏

51CTO技术栈公众号