自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入淺出 Vue-Loader 自定義塊

開發(fā) 前端
vue-i18n[1] 是 Vue 的國際化插件。如果使用 SFC 的方式寫組件的話,可以在 .vue 文件中定義 <i18n> 塊 ,然后在塊內(nèi)寫入對(duì)應(yīng)的詞條。

[[437854]]

 本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)小余」,作者Jouryjc 。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)小余公眾號(hào)。

本文大綱:

  • 通過 vue-i18n 的 了解 customBlocks 和基本配置;
  • 從源碼層面了解 vue-loader 對(duì) customBlocks 的處理

vue-i18n

vue-i18n[1] 是 Vue 的國際化插件。如果使用 SFC 的方式寫組件的話,可以在 .vue 文件中定義 塊 ,然后在塊內(nèi)寫入對(duì)應(yīng)的詞條。這個(gè) i18n 標(biāo)簽就是 customBlocks。舉個(gè)例子:

  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 的值,就能達(dá)到切換語言的效果。除了上述用法,還支持支持引入 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,如果沒有指定,對(duì)應(yīng)的塊會(huì)默默被忽略。?? 中的 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 對(duì)應(yīng)的 rule 
  27.       { 
  28.         // 使用 resourceQuery 來為一個(gè)沒有 lang 的自定義塊匹配一條規(guī)則 
  29.         // 如果找到了一個(gè)自定義塊的匹配規(guī)則,它將會(huì)被處理,否則該自定義塊會(huì)被默默忽略 
  30.         resourceQuery: /blockType=i18n/, 
  31.         // Rule.type 設(shè)置類型用于匹配模塊。它防止了 defaultRules 和它們的默認(rèn)導(dǎo)入行為發(fā)生 
  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 功能,只需要下面兩步:

實(shí)現(xiàn)一個(gè)處理 customBlock 的 loader 函數(shù);

配置 webpack.module.rules ,指定 resourceQuery: /blockType=你的塊名稱/ 然后使用步驟一的 loader 去處理即可;

源碼分析

通常一個(gè) loader 都是具體某一種資源的轉(zhuǎn)換、加載器,但 vue-loader 不是,它能夠處理每一個(gè)定義在 SFC 中的塊:通過拆解 block -> 組合 loader -> 處理 block -> 組合每一個(gè) block 的結(jié)果為最終代碼的工作流,完成對(duì) SFC 的處理。下面我們就依次詳細(xì)地拆解這條流水線!

拆解 block

我們知道,使用 vue-loader 一定需要引入 vue-loader-plugin,不然的話就會(huì)給你報(bào)一個(gè)大大的錯(cuò)誤:

  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 復(fù)制并應(yīng)用到 .vue 文件里相應(yīng)語言的塊 
  23.     const clonedRules = rules 
  24.       .filter(r => r !== vueRule) 
  25.       .map(cloneRule) 
  26.  
  27.     // ... 
  28.     // 個(gè)人對(duì)這個(gè)命名的理解是 pitcher 是投手的意思,進(jìn)球得分,所以可以理解成給當(dāng)前的塊和 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 的各個(gè)塊中并修改配置中的 module.rules。pitcher-loader[3] 是后續(xù)一個(gè)重要的角色。阿寶哥的多圖詳解,一次性搞懂Webpack Loader[4]有詳細(xì)的分享,沒了解過滴童鞋可以先去認(rèn)識(shí)一下這個(gè)“投手”的作用。

了解完 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),  // 默認(rèn)使用 vue-template-compiler 
  9.     filename, 
  10.     sourceRoot, 
  11.     needMap: sourceMap 
  12.   }) 
  13.    
  14.   // ... 

本小節(jié)核心就是這個(gè) parse 方法。將 SFC 代碼傳通過自定義編譯器或者默認(rèn)的 @vue/component-compiler-utils 去解析。具體執(zhí)行過程這里就不展開詳細(xì)分析了,感興趣童鞋可以前往[咖聊] “模板編譯”真經(jīng)。生成的 descriptor 結(jié)果如下圖所示:

接下來就針對(duì) descriptor 的每一個(gè) 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),  // 默認(rèn)使用 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,調(diào)用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直接引入其他資源的用法,這里就是獲取這個(gè)參數(shù) 
  95.     // 對(duì)于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 這些塊我們直接略過,重點(diǎn)看看 customBlocks 的處理邏輯。邏輯比較簡(jiǎn)單,遍歷 customBlocks 去獲取一些 query 變量,最終返回 customBlocks code。我們看看最終通過第一次調(diào)用 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 

緊接著繼續(xù)處理 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 參數(shù),匹配到了 pitcher-loader ,該“投手”登場(chǎng)了。分析下 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,避免重復(fù) lint;
  • 剔除 pitcher-loader 自身;
  • 根據(jù)不同的 query.type,生成對(duì)應(yīng)的 request,并返回結(jié)果;

中 customBlocks 返回的結(jié)果如下:

  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

根據(jù) import 的表達(dá)式,我們可以看到,此時(shí)會(huì)通過 vue-loader -> vue-i18n-loader 依次處理拿到結(jié)果,此時(shí)再進(jìn)入到 vue-loader 跟前面第一次生成 code 不一樣的地方是:此時(shí) incomingQuery.type 是有值的。對(duì)于 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. // ... 

會(huì)執(zhí)行到 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.   } 

最后會(huì)執(zhí)行到 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.       // 緩存結(jié)果,在輸入和依賴沒有發(fā)生改變時(shí),直接使用緩存結(jié)果 
  8.       this.cacheable && this.cacheable() 
  9.       // 輸出結(jié)果 
  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標(biāo)簽生成代碼 
  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.   // 特殊字符轉(zhuǎn)義,\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.  * 轉(zhuǎn)換各種用法為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 

上述代碼就比較簡(jiǎn)單了,拿到 source 生成 value,最終 push 到 Component.options.__i18n 中,針對(duì)不同的情況有不同的處理方式(json、yaml等)。

至此,整個(gè) vue 文件就構(gòu)建結(jié)束了, 最終構(gòu)建完的代碼如下:

  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 怎么識(shí)別 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 中定義一個(gè)自定義塊。然后從 vue-loader 源碼分析了 SFC 的處理流程,整個(gè)過程如下圖所示:

  1. 從 webpack 構(gòu)建開始,會(huì)調(diào)用到插件,VueLoaderPlugin 在 normalModuleLoader 鉤子上會(huì)被執(zhí)行;
  2. 在引入 SFC 時(shí),第一次匹配到 vue-loader,會(huì)通過 @vue/component-compiler-utils 將代碼解析成不同的塊,例如 template、script、style、custom;
  3. 生成的 code,會(huì)繼續(xù)匹配 loader,?vue 會(huì)匹配上“投手”pitcher-loader;
  4. pitcher-loader 主要做 3 件事:首先因?yàn)?vue 整個(gè)文件已經(jīng)被 lint 處理過了,所以局部代碼時(shí)過濾掉 eslint-loader;其次過濾掉自身 pitcher-loader;最后通過 query.type 去生成不同的 request 和 code;
  5. 最終 code 會(huì)再次匹配上 vue-loader,此時(shí)第二次執(zhí)行,incomingQuery.type 都會(huì)指定對(duì)應(yīng)的塊,所以會(huì)根據(jù) type 調(diào)用 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

 

責(zé)任編輯:武曉燕 來源: 碼農(nóng)小余
相關(guān)推薦

2011-07-04 10:39:57

Web

2021-03-16 08:54:35

AQSAbstractQueJava

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2009-11-17 17:31:58

Oracle COMM

2021-07-19 11:54:15

MySQL優(yōu)先隊(duì)列

2023-12-04 13:22:00

JavaScript異步編程

2010-07-26 12:57:12

OPhone游戲開發(fā)

2016-10-14 13:53:05

JavascriptDOMWeb

2016-10-14 14:32:58

JavascriptDOMWeb

2010-07-16 09:11:40

JavaScript內(nèi)存泄漏

2024-01-09 12:05:24

SSH協(xié)議端口

2022-01-11 07:52:22

CSS 技巧代碼重構(gòu)

2019-12-04 10:13:58

Kubernetes存儲(chǔ)Docker

2022-11-09 08:06:15

GreatSQLMGR模式

2021-04-27 08:54:43

ConcurrentH數(shù)據(jù)結(jié)構(gòu)JDK8

2009-11-18 13:30:37

Oracle Sequ
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)