vscode+babel開發一個智能移除未使用變量的插件(實戰)

本篇文章分享一個在vscode中結合babel開發一個智能移除未使用變量插件的方法,希望對大家有所幫助!

vscode+babel開發一個智能移除未使用變量的插件(實戰)

vscode 已經成為前端不可缺失的開發工具之一,之所以 vscode 能夠獲得開發者的青睞,我想和它“無所不能”的插件體系有很大一部分關系。在工作中我們能用它來開發純工具型的插件,也可以用它開發一些和公司業務相結合的功能插件。在這里我分享一個通過結合babel來實現一個能夠智能移除未使用的變量插件,希望對大家開發 vscode 插件有一定的啟發和幫助?!就扑]學習:《vscode》】

正文

今天我們首先來熟悉一下 vscode 插件項目的搭建流程

1、使用官方提供的腳手架初始化一個項目

安裝腳手架

#?npm?形式 npm?install?-g?yo?generator-code #?yarn?形式 yarn?global?add?yo?generator-code

運行腳手架

#?運行腳手架 yo?code

選擇模板,考慮到有些開發者對 TypeScript 并不熟悉,所以我們這里選擇 New Extension (JavaScript)

??What?type?of?extension?do?you?want?to?create??New?Extension?(JavaScript) ??What's?the?name?of?your?extension??rm-unuse-var ??What's?the?identifier?of?your?extension??rm-unuse-var ??What's?the?description?of?your?extension??移除未使用的變量 ??Enable?JavaScript?type?checking?in?'jsconfig.json'??Yes ??Initialize?a?git?repository??Yes ??Which?package?manager?to?use??yarn

這是我們最終生成的目錄結構

vscode+babel開發一個智能移除未使用變量的插件(實戰)

我們先來運行一下這個插件試試

vscode+babel開發一個智能移除未使用變量的插件(實戰)

點擊上面運行按鈕,會打開一個新的 vscode 窗口,在新窗口中按下Ctrl+Shift+P輸入Hello World,在窗口右下角會看到一個提示框,說明我們第一個 vscode 插件運行成功運行了。

vscode+babel開發一個智能移除未使用變量的插件(實戰)

2、自定義命令、快捷鍵、菜單

vscode 插件很多功能都是基于一個個命令實現的,我們可以自定義一些命令,這個命令將出現在按下Ctrl+Shift+P后的命令列表里面,同時可以給命令配置快捷鍵、配置資源管理器菜單、編輯器菜單、標題菜單、下拉菜單、右上角圖標等。

3、如何添加命令列表

package.json 部分配置

{ ??//?擴展的激活事件 ??"activationEvents":?["onCommand:rm-unuse-var.helloWorld"], ??//?入口文件 ??"main":?"./extension.js", ??//?添加指令 ??"contributes":?{ ????"commands":?[ ??????{ ????????//?這里的值必須和activationEvents里面配置的一樣 ????????"command":?"rm-unuse-var.helloWorld", ????????//?這個就是我們指令的名稱,可以修改這里的值重新運行插件試試看 ????????"title":?"Hello?World" ??????} ????] ??} }

在開發中快捷鍵的使用方式是最便捷的,接下來我們修改一下配置,讓插件支持快捷鍵的方式運行。

{ ??"contributes":?{ ????"commands":?[ ??????{ ????????//?這里的值必須和activationEvents里面配置的一樣 ????????"command":?"rm-unuse-var.helloWorld", ????????//?這個就是我們指令的名稱,可以修改這里的值重新運行插件試試看 ????????"title":?"Hello?World" ??????} ????], ????//?快捷鍵綁定 ????"keybindings":?[ ??????{ ????????"command":?"rm-unuse-var.helloWorld", ????????"key":?"ctrl+6", ????????"mac":?"cmd+6" ??????} ????] ??} }

我們再重新運行一下,通過快捷鍵Ctrl+6看看我們的插件是否能夠正常運行。沒錯就是這么簡單,我們的插件已經能夠支持快捷鍵的形式運行了。

4、叫 helloWorld 太土了,下一步我們來修改一下指令的名稱

package.json

{ ??"activationEvents":?["onCommand:rm-unuse-var.rm-js-var"], ??"main":?"./extension.js", ??"contributes":?{ ????"commands":?[ ??????{ ????????"command":?"rm-unuse-var.rm-js-var", ????????"title":?"Hello?World" ??????} ????], ????"keybindings":?[ ??????{ ????????"command":?"rm-unuse-var.rm-js-var", ????????"key":?"ctrl+6", ????????"mac":?"cmd+6" ??????} ????] ??} }

因為我們在extension.js中注冊了指令的名稱,所以也要同步修改

let?disposable?=?vscode.commands.registerCommand( ??"rm-unuse-var.rm-js-var", ??function?()?{ ????vscode.window.showInformationMessage("Hello?World?from?rm-unuse-var!"); ??} );

5、安裝babel相關庫

我們修改代碼可以分為 3 個步驟

1、將代碼解析成 AST 語法樹 2、遍歷修改 AST 語法樹 3、根據修改過的 AST 語法樹生成新的代碼

這 3 個步驟 babel 都有對應的庫來處理

  • @babel/parser生成 AST 語法樹,文檔地址(https://www.babeljs.cn/docs/babel-parser)

  • @babel/traverse遍歷 AST 語法樹,文檔地址(https://www.babeljs.cn/docs/babel-traverse)

  • @babel/generator根據 AST 語法樹生成代碼,文檔地址(https://www.babeljs.cn/docs/babel-generator)

  • @babel/types工具庫,文檔地址(https://www.babeljs.cn/docs/babel-types)

6、我們來看下這些庫的基本用法,比如實現一個將 es6 的箭頭函數轉換成普通函數

轉換前

const?say?=?()?=>?{ ??console.log("hello"); };

轉換后

function?say()?{ ??console.log("hello"); }

代碼實現,代碼部分寫死僅供學習參考

const?t?=?require("@babel/types"); const?parser?=?require("@babel/parser"); const?traverse?=?require("@babel/traverse").default; const?generate?=?require("@babel/generator").default; //?1、將代碼解析成?AST?語法樹 const?ast?=?parser.parse(`const?say?=?()?=>?{ ??console.log("hello"); };`); //?2、遍歷修改?AST?語法樹 traverse(ast,?{ ??VariableDeclaration(path)?{ ????const?{?node?}?=?path; ????//?寫死找到第一個申明 ????const?declaration?=?node.declarations[0]; ????//?定義的內容 ????const?init?=?declaration.init; ????//?判斷是否是箭頭函數 ????if?(t.isArrowFunctionExpression(init))?{ ??????//?將原來的表達式替換成新生成的函數 ??????path.replaceWith( ????????t.functionDeclaration( ??????????declaration.id, ??????????init.params, ??????????init.body, ??????????init.generator, ??????????init.async ????????) ??????); ????} ??}, }); //?3、根據修改過的?AST?語法樹生成新的代碼 console.log(generate(ast).code); /* function?say()?{ ??console.log("hello"); } */

很多同學肯定好奇現在我們的表達式比較簡單還好,如果復雜的話定義嵌套會非常深和復雜,這個時候應該怎么知道去替換哪個節點?。其實這里可以借助vscode 這是一個在線轉換 AST 的網站。我們可以打開兩個窗口,把轉換前的代碼放到第一個窗口,把需要轉換的接口放到第二個窗口。這個時候我們就可以對比一下轉換前后的差異,來實現我們的代碼了。

vscode+babel開發一個智能移除未使用變量的插件(實戰)

vscode+babel開發一個智能移除未使用變量的插件(實戰)

6、思考插件如何實現?

1、獲取編輯器當前打開的 js 文件的代碼 2、將代碼解析成 AST 語法樹 3、遍歷 AST 語法樹,刪除未使用的定義 4、根據修改過的 AST 語法樹生成新的代碼 5、替換當前 js 文件的代碼

其中 2、4 我們已經會了,接下來只需要看下 1、3、5 如何實現就行

1 和 5 我們可以通過 vscode 提供的方法

1、獲取編輯器當前打開的 js 文件的代碼

import?*?as?vscode?from?"vscode"; //?當前打開的文件 const?{?activeTextEditor?}?=?vscode.window; //?然后通過document下的getText就能輕松獲取到我們的代碼了 const?code?=?activeTextEditor.document.getText();

5、替換當前 js 文件的代碼

activeTextEditor.edit((editBuilder)?=>?{ ??editBuilder.replace( ????//?因為我們要全文件替換,所以我們需要定義一個從頭到位的區間 ????new?vscode.Range( ??????new?vscode.Position(0,?0), ??????new?vscode.Position(activeTextEditor.document.lineCount?+?1,?0) ????), ????//?我們的新代碼 ????generate(ast).code ??); });

好了接下來我們就剩核心的第 3 步了。

3、遍歷 AST 語法樹,刪除未使用的定義

我們先來分析一下,未使用的定義包含了哪些?

import?vue?from?"vue"; const?a?=?{?test1:?1,?test2:?2?}; const?{?test1,?test2?}?=?a; function?b()?{} let?c?=?()?=>?{}; var?d?=?()?=>?{};

然后在線 ast 轉換網站,復制這些內容進去看看生成的語法樹結構

vscode+babel開發一個智能移除未使用變量的插件(實戰)

我們先來實現一個例子吧,比如把下面代碼中沒有用的變量移除掉

轉換前

var?a?=?1; var?b?=?2; console.log(a);

轉換后

var?a?=?1; console.log(a);
  • scope.getBinding(name) 獲取當前所有綁定
  • scope.getBinding(name).referenced 綁定是否被引用
  • scope.getBinding(name).constantViolations 獲取當前所有綁定修改
  • scope.getBinding(name).referencePaths 獲取當前所有綁定路徑

代碼實現

const?t?=?require("@babel/types"); const?parser?=?require("@babel/parser"); const?traverse?=?require("@babel/traverse").default; const?generate?=?require("@babel/generator").default;  const?ast?=?parser.parse(`var?a?=?1; var?b?=?2; console.log(a);`);  traverse(ast,?{ ??VariableDeclaration(path)?{ ????const?{?node?}?=?path; ????const?{?declarations?}?=?node; ????//?此處便利可以處理?const?a?=?1,b?=?2;?這種場景 ????node.declarations?=?declarations.filter((declaration)?=>?{ ??????const?{?id?}?=?declaration; ??????//?const?{?b,?c?}?=?a; ??????if?(t.isObjectPattern(id))?{ ????????//?path.scope.getBinding(name).referenced?判斷變量是否被引用 ????????//?通過filter移除掉沒有使用的變量 ????????id.properties?=?id.properties.filter((property)?=>?{ ??????????const?binding?=?path.scope.getBinding(property.key.name); ??????????return?!!binding?.referenced; ????????}); ????????//?如果對象中所有變量都沒有被應用,則該對象整個移除 ????????return?id.properties.length?>?0; ??????}?else?{ ????????//?const?a?=?1; ????????const?binding?=?path.scope.getBinding(id.name); ????????return?!!binding?.referenced; ??????} ????}); ????//?如果整個定義語句都沒有被引用則整個移除 ????if?(node.declarations.length?===?0)?{ ??????path.remove(); ????} ??}, }); console.log(generate(ast).code);

7、了解基本處理流程之后,我們就來看下最終的代碼實現吧

const?t?=?require("@babel/types"); const?parser?=?require("@babel/parser"); const?traverse?=?require("@babel/traverse").default; const?generate?=?require("@babel/generator").default;  const?ast?=?parser.parse( ??`import?vue?from?'vue'; ??var?a?=?1; var?b?=?2; var?{?test1,?test2?}?=?{?test1:?1,?test2:?2?}; function?c(){} function?d(){} d(); console.log(a,?test1);`, ??{ ????sourceType:?"module", ??} );  traverse(ast,?{ ??//?處理?const?var?let ??VariableDeclaration(path)?{ ????const?{?node?}?=?path; ????const?{?declarations?}?=?node;  ????node.declarations?=?declarations.filter((declaration)?=>?{ ??????const?{?id?}?=?declaration; ??????if?(t.isObjectPattern(id))?{ ????????id.properties?=?id.properties.filter((property)?=>?{ ??????????const?binding?=?path.scope.getBinding(property.key.name); ??????????return?!!binding?.referenced; ????????}); ????????return?id.properties.length?>?0; ??????}?else?{ ????????const?binding?=?path.scope.getBinding(id.name); ????????return?!!binding?.referenced; ??????} ????});  ????if?(node.declarations.length?===?0)?{ ??????path.remove(); ????} ??}, ??//?處理?import ??ImportDeclaration(path)?{ ????const?{?node?}?=?path; ????const?{?specifiers?}?=?node; ????if?(!specifiers.length)?{ ??????return; ????} ????node.specifiers?=?specifiers.filter((specifier)?=>?{ ??????const?{?local?}?=?specifier; ??????const?binding?=?path.scope.getBinding(local.name); ??????return?!!binding?.referenced; ????}); ????if?(node.specifiers.length?===?0)?{ ??????path.remove(); ????} ??}, ??//?處理?function ??FunctionDeclaration(path)?{ ????const?{?node?}?=?path; ????const?{?id?}?=?node; ????const?binding?=?path.scope.getBinding(id.name); ????if?(!binding?.referenced)?{ ??????path.remove(); ????} ??}, }); console.log(generate(ast).code);

8、vscode 設置我們的插件只支持 js 文件的限制

因為我們現在實現是針對 js 文件的,所以打開其他類型的文件我們可以讓我們的快捷鍵失效。 我們可以修改package.jsonpackage.json

{ ??"contributes":?{ ????"commands":?[ ??????{ ????????"command":?"rm-unuse-var.remove", ????????"title":?"Hello?World" ??????} ????], ????"keybindings":?[ ??????{ ????????"command":?"rm-unuse-var.remove", ????????"key":?"ctrl+6", ????????"mac":?"cmd+6", ????????"when":?"resourceLangId?==?javascript" ??????} ????] ??} }

9、整合到我們前面創建的項目中去

此處省略… 相信看了上面這些介紹大家已經完全有能力自己整合了

10、打包發布插件

打包我們可以vsce工具

全局安裝 vsce

#?npm npm?i?vsce?-g #?yarn yarn?global?add?vsce

打包插件

打包前先修改 README.md 文件否則會報錯

vsce?package

執行完畢之后會生成一個.vsix 文件

如果要在本地 vscode 使用可以直接導入

vscode+babel開發一個智能移除未使用變量的插件(實戰)

如果要發布到市場的話,我們需要先注冊賬號?https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions

#?登錄賬號 vsce?login?your-publisher-name #?發布 vsce?publish

發布成功之后就能在我們的市場上看到了 vscode 也可以在 vscode 中搜索打我們的插件

vscode+babel開發一個智能移除未使用變量的插件(實戰)

總結

到此為止,相信大家對 vscode 插件開發的基本流程已經有了了解。

覺得文章對你有所幫助,可以點個贊?

當然 vscode 插件還有非常多的配置沒有介紹,后面如果有時間可以單獨整理成一篇文章來介紹

如果在開發過程中有問題或者其他前端技術問題也可以加我微信rjjs1221交流,或者直接在評論區回復。

源碼地址?https://github.com/taoxhsmile/rm-unuse-var

更多關于VSCode的相關知識,請訪問:vscode??!

以上就是

? 版權聲明
THE END
喜歡就支持一下吧
點贊9 分享