日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
import方式隨心轉(zhuǎn),感受Babel插件的威力

本文轉(zhuǎn)載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg 。轉(zhuǎn)載本文請聯(lián)系神光的編程秘籍公眾號。

創(chuàng)新互聯(lián)網(wǎng)站建設公司一直秉承“誠信做人,踏實做事”的原則,不欺瞞客戶,是我們最起碼的底線! 以服務為基礎(chǔ),以質(zhì)量求生存,以技術(shù)求發(fā)展,成交一個客戶多一個朋友!專注中小微企業(yè)官網(wǎng)定制,成都網(wǎng)站設計、成都網(wǎng)站建設、外貿(mào)網(wǎng)站建設,塑造企業(yè)網(wǎng)絡形象打造互聯(lián)網(wǎng)企業(yè)效應。

當我們 import 一個模塊的時候,可以這樣默認引入:

 
 
 
 
  1. import path from 'path'; 
  2.  
  3. path.join('a', 'b'); 
  4.  
  5. function func() { 
  6.     const sep = 'aaa'; 
  7.     console.log(path.sep); 

也可以這樣解構(gòu)引入:

 
 
 
 
  1. import { join, sep as _sep } from 'path'; 
  2. join('a', 'b'); 
  3.  
  4. function func() { 
  5.   const sep = 'aaa'; 
  6.   console.log(_sep); 

第一種默認引入叫 default import,第二種解構(gòu)引入叫 named import。

不知道大家習慣用哪一種。

如果有個需求,讓你把所有的 default import 轉(zhuǎn)成 named import,你會怎么做呢?

可能你會說這個不就是找到所有用到引入變量的地方,修改成直接調(diào)用方法,然后那些方法名以解構(gòu)的方式寫在 import 語句里么。

但如果說要改的項目有 100 多個這種文件呢?(觸發(fā) treeshking 要這樣改)

這時候就可以考慮 babel 插件了,它很適合做這種有規(guī)律且數(shù)量龐大的代碼的自動修改。

讓我們通過這個例子感受下 babel 插件的威力吧。

因為代碼比較多,大家可能沒耐心看,要不我們先看效果吧:

測試效果

輸入代碼是這樣:

 
 
 
 
  1. import path from 'path'; 
  2.  
  3. path.join('a', 'b'); 
  4.  
  5. function func() { 
  6.     const sep = 'aaa'; 
  7.     console.log(path.sep); 

我們引入該 babel 插件,讀取輸入代碼并做轉(zhuǎn)換:

 
 
 
 
  1. const { transformFileSync } = require('@babel/core'); 
  2. const importTransformPlugin = require('./plugin/importTransform'); 
  3. const path = require('path'); 
  4.  
  5. const { code } = transformFileSync(path.join(__dirname, './sourceCode.js'), { 
  6.     plugins: [[importTransformPlugin]] 
  7. }); 
  8.  
  9. console.log(code); 

打印如下:

我們完成了 default import 到 named import 的自動轉(zhuǎn)換。

可能有的同學擔心重名問題,我們測試一下:

可以看到,插件已經(jīng)處理了重名問題。

思路分析

import 語句中間的部分叫做 specifier,我們可以通過 astexplorer.net 來可視化的查看它的 AST。

比如這樣一條 import 語句:

 
 
 
 
  1. import React, {useState as test, useEffect} from 'react'; 

它對應的 AST 是這樣的:

也就是說默認 import 是 ImportDefaultSpecifier,而解構(gòu) import 是 ImportSpecifier

ImportSpecifier 語句有 local 和 imported 屬性,分別代表引入的名字和重命名后的名字:

那我們的目的明確了,就是把 ImportDefaultSpecifier 轉(zhuǎn)成 ImportSpecifier,并且使用到的屬性方法來設置 imported 屬性,需要重命名的還要設置下 local 屬性。

怎么知道使用到哪些屬性方法呢?也就是如何分析變量的引用呢?

babel 提供了 scope 的 api,用于作用域分析,可以拿到作用域中的聲明,和所有引用這個聲明的地方。

比如這里就可以用 scope.getBinding 方法拿到該變量的聲明:

 
 
 
 
  1. const binding = scope.getBinding('path'); 

然后用 binding.references 就可以拿到所有引用這個聲明的地方,也就是 path.join 和 path.sep。

之后就可以把這兩處引用改為直接的方法調(diào)用,然后修改下 import 語句為解構(gòu)就可以了。

我們總結(jié)一下步驟:

  • 找到 import 語句中的 ImportDefaultSpecifier
  • 拿到 ImportDefaultSpecifier 在作用域的聲明(binding)
  • 找到所有引用該聲明的地方(reference)
  • 修改各處引用為直接調(diào)用函數(shù)的形式,收集函數(shù)名
  • 如果作用域中有重名的變量,則生成一個唯一的函數(shù)名
  • 根據(jù)收集的函數(shù)名來修改 ImportDefaultSpecifier 為 ImportSpecifier

原理大概過了一遍,我們來寫下代碼

代碼實現(xiàn)

babel 插件是函數(shù)返回對象的形式,返回的對象中主要是通過 visitor 屬性來指定對什么 AST 做什么處理。

我們搭一個 babel 插件的骨架:

 
 
 
 
  1. const { declare } = require('@babel/helper-plugin-utils'); 
  2.  
  3. const importTransformPlugin = declare((api, options, dirname) => { 
  4.     api.assertVersion(7); 
  5.  
  6.     return { 
  7.         visitor: { 
  8.             ImportDeclaration(path) { 
  9.                  
  10.             } 
  11.         } 
  12.     } 
  13. }); 
  14.  
  15. module.exports = importTransformPlugin; 

這里我們要處理的是 import 語句 ImportDeclaration。

@babel/helper-plugin-utils 包的 declare 方法的作用是給 api 擴充一個 assertVersion 方法。而 assertVersion 的作用是如果這個插件工作在了 babel6 上就會報錯說這個插件只能用在 babel7,可以避免報的錯看不懂。

path 是用于操作 AST 的一些 api,而且也保留了 node 之間的關(guān)聯(lián),比如 parent、sibling 等。

接下來進入正題:

我們要先取出 specifiers 的部分,然后找出 ImportDefaultSpecifier:

 
 
 
 
  1. ImportDeclaration(path) { 
  2.     // 找到 import 語句中的 default import 
  3.     const importDefaultSpecifiers = path.node.specifiers.filter(item => api.types.isImportDefaultSpecifier(item)); 
  4.     // 對每個 default import 做轉(zhuǎn)換 
  5.     importDefaultSpecifiers.forEach(defaultSpecifier => { 
  6.  
  7.     }); 

然后對每一個 default import 都要根據(jù)在作用域中的聲明找到所有引用的地方:

 
 
 
 
  1.  // import 變量的名字 
  2. const importId = defaultSpecifier.local.name; 
  3. // 該變量的聲明 
  4. const binding = path.scope.getBinding(importId); 
  5.  
  6. binding.referencePaths.forEach(referencePath=> { 
  7.    
  8. }); 

然后對每個引用到該 import 的地方都做修改,改為直接調(diào)用函數(shù),并且把函數(shù)名收集起來。這里要注意的是,如果作用域中有同名變量還要生成一個新的唯一 id。

 
 
 
 
  1. // 該變量的聲明 
  2. const binding = path.scope.getBinding(importId); 
  3.  
  4. const referedIds = []; 
  5. const transformedIds = []; 
  6. // 收集所有引用該聲明的地方的方法名 
  7. binding.referencePaths.forEach(referencePath=> { 
  8.     const currentPath = referencePath.parentPath; 
  9.     const methodName = currentPath.node.property.name; 
  10.  
  11.     // 之前方法名 
  12.     referedIds.push(currentPath.node.property); 
  13.   
  14.     if (!currentPath.scope.getBinding(methodName)) {// 如果作用域沒有重名變量 
  15.         const methodNameNode = currentPath.node.property; 
  16.         currentPath.replaceWith(methodNameNode); 
  17.  
  18.         transformedIds.push(methodNameNode); // 轉(zhuǎn)換后的方法名 
  19.     } else {// 如果作用域有重名變量 
  20.         const newMethodName = referencePath.scope.generateUidIdentifier(methodName); 
  21.         currentPath.replaceWith(newMethodName); 
  22.  
  23.         transformedIds.push(newMethodName); // 轉(zhuǎn)換后的方法名 
  24.     } 
  25. }); 

這部分邏輯比較多,著重講一下。

我們對每個引用了該變量的地方都要記錄下引用了哪個方法,比如 path.join、path.sep 就引用了 join 和 sep 方法。

然后就要把 path.join 替換成 join,把 path.sep 替換成 sep。

如果作用域中有了 join 或者 sep 的聲明,需要生成一個新的 id,并且記錄下新的 id 是什么。

收集了所有的方法名,就可以修改 import 語句了:

 
 
 
 
  1. // 轉(zhuǎn)換 import 語句為 named import 
  2. const newSpecifiers = referedIds.map((id, index) => api.types.ImportSpecifier(transformedIds[index], id)); 
  3. path.node.specifiers = newSpecifiers; 

沒有 babel 插件基礎(chǔ)可能看的有點暈,沒關(guān)系,知道他是做啥的就行。我們接下來試下效果。

思考和代碼

我們做了 default import 到 named import 的自動轉(zhuǎn)換,其實反過來也一樣,不也是分析 scope 的 binding 和 reference,然后去修改 AST 么?感興趣的同學可以試下反過來轉(zhuǎn)換怎么寫。

插件全部代碼如下:

 
 
 
 
  1. const { declare } = require('@babel/helper-plugin-utils'); 
  2.  
  3. const importTransformPlugin = declare((api, options, dirname) => { 
  4.     api.assertVersion(7); 
  5.  
  6.     return { 
  7.         visitor: { 
  8.             ImportDeclaration(path) { 
  9.                 // 找到 import 語句中的 default import 
  10.                 const importDefaultSpecifiers = path.node.specifiers.filter(item => api.types.isImportDefaultSpecifier(item)); 
  11.                 // 對每個 default import 做轉(zhuǎn)換 
  12.                 importDefaultSpecifiers.forEach(defaultSpecifier => { 
  13.                     // import 變量的名字 
  14.                     const importId = defaultSpecifier.local.name; 
  15.                     // 該變量的聲明 
  16.                     const binding = path.scope.getBinding(importId); 
  17.  
  18.                     const referedIds = []; 
  19.                     const transformedIds = []; 
  20.                     // 收集所有引用該聲明的地方的方法名 
  21.                     binding.referencePaths.forEach(referencePath=> { 
  22.                         const currentPath = referencePath.parentPath; 
  23.                         const methodName = currentPath.node.property.name; 
  24.  
  25.                         // 之前方法名 
  26.                         referedIds.push(currentPath.node.property); 
  27.  
  28.                         if (!currentPath.scope.getBinding(methodName)) {// 如果作用域沒有重名變量 
  29.                             const methodNameNode = currentPath.node.property; 
  30.                             currentPath.replaceWith(methodNameNode); 
  31.  
  32.                             transformedIds.push(methodNameNode); // 轉(zhuǎn)換后的方法名 
  33.                         } else {// 如果作用域有重名變量 
  34.                             const newMethodName = referencePath.scope.generateUidIdentifier(methodName); 
  35.                             currentPath.replaceWith(newMethodName); 
  36.  
  37.                             transformedIds.push(newMethodName); // 轉(zhuǎn)換后的方法名 
  38.                         } 
  39.                     }); 
  40.  
  41.                     // 轉(zhuǎn)換 import 語句為 named import 
  42.                     const newSpecifiers = referedIds.map((id, index) => api.types.ImportSpecifier(transformedIds[index], id)); 
  43.                     path.node.specifiers = newSpecifiers; 
  44.                 }); 
  45.             } 
  46.         } 
  47.     } 
  48. }); 
  49. module.exports = importTransformPlugin; 

總結(jié)

我們要做 default import 轉(zhuǎn) named import,也就是 ImportDefaultSpecifier 轉(zhuǎn) ImportSpecifier,要通過 scope 的 api 分析 binding 和 reference,找到所有引用的地方,替換成直接調(diào)用函數(shù)的形式,然后再去修改 import 語句的 AST 就可以了。

babel 插件特別適合做這種有規(guī)律且轉(zhuǎn)換量比較大的需求,在一些場景下是有很大的威力的。


文章題目:import方式隨心轉(zhuǎn),感受Babel插件的威力
瀏覽路徑:http://m.5511xx.com/article/codpepe.html