preface
Vscode has become one of the indispensable development tools for the front end, and I think the popularity of vscode has a lot to do with its “do-it-all” plug-in system. In our work, we can use it to develop pure tool-based plug-ins, and we can also use it to develop some functional plug-ins combined with the company’s business. Here I share a plug-in that can intelligently remove unused variables by combining Babel. I hope it will be inspired and helpful for you to develop vscode plug-in.
The body of the
Today we will start by familiarizing ourselves with the setup process of vscode plug-in projects
1. Initialize a project using officially provided scaffolding
Erection of scaffolding
# NPM form
npm install -g yo generator-code
# yarn form
yarn global add yo generator-code
Copy the code
Running scaffold
# Run scaffolding
yo code
Copy the code
Select a template. For developers unfamiliar with TypeScript, we’ll use 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? Remove unused variables? Enable JavaScript type checking in 'jsconfig.json'? Yes ? Initialize a git repository? Yes ? Which package manager to use? yarnCopy the code
This is the directory structure we eventually generated
Let’s run the plugin first
Click the “run” button above to open a new vscode window. Press Ctrl+Shift+P to enter “Hello World” in the new window. You will see a prompt box in the lower right corner of the window, indicating that our first vscode plug-in has been successfully run.
2. Customize commands, shortcut keys, and menus
Vscode plugin many functions are based on a command, we can customize some commands, this command will appear in the command list after pressing Ctrl+Shift+P, at the same time, you can configure the command shortcut key, configuration Explorer menu, editor menu, title menu, drop-down menu, icon in the upper right corner, etc.
3. How to add a command list
Package. json partial configuration
{
// Extended activation events
"activationEvents": ["onCommand:rm-unuse-var.helloWorld"].// Import file
"main": "./extension.js".// Add directives
"contributes": {
"commands": [{// The value must be the same as that configured in activationEvents
"command": "rm-unuse-var.helloWorld".// This is the name of our directive, you can change the value here and try to run the plugin again
"title": "Hello World"}}}]Copy the code
Shortcuts are the easiest way to use them in development, so let’s modify the configuration to allow the plugin to work in this way.
{
"contributes": {
"commands": [{// The value must be the same as that configured in activationEvents
"command": "rm-unuse-var.helloWorld".// This is the name of our directive, you can change the value here and try to run the plugin again
"title": "Hello World"}].// Shortcut key binding
"keybindings": [{"command": "rm-unuse-var.helloWorld"."key": "ctrl+6"."mac": "cmd+6"}}}]Copy the code
Let’s run it again and use the shortcut Ctrl+6 to see if our plugin works. Yes, it’s as simple as that, and our plugin already supports keyboard shortcuts.
4, Calling helloWorld is too boring, next let’s change the name of the directive
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"}}}]Copy the code
Because we registered the name of the directive in extension.js, we need to synchronize the changes as well
let disposable = vscode.commands.registerCommand(
"rm-unuse-var.rm-js-var".function () {
vscode.window.showInformationMessage("Hello World from rm-unuse-var!"); });Copy the code
5, installation,babel
Related to the library
We can modify the code in three steps
3. Generate new code based on the modified AST syntax tree
Babel has libraries to handle all three of these steps
@babel/parser
Generate AST syntax treeThe document address@babel/traverse
Walk through the AST syntax treeThe document address@babel/generator
Generate code from the AST syntax treeThe document address@babel/types
Tool libraryThe document address
Let’s take a look at some of the basic uses of these libraries, such as implementing an es6 arrow function into a normal function
Transformation before
const say = () = > {
console.log("hello");
};
Copy the code
After the transformation
function say() {
console.log("hello");
}
Copy the code
Code implementation, code part write dead for learning reference only
const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;
// 1. Parse code into an AST syntax tree
const ast = parser.parse(`const say = () => { console.log("hello"); }; `);
// 2. Modify AST syntax tree
traverse(ast, {
VariableDeclaration(path) {
const { node } = path;
// Find the first statement
const declaration = node.declarations[0];
// The contents of the definition
const init = declaration.init;
// check if it is an arrow function
if (t.isArrowFunctionExpression(init)) {
// Replace the original expression with the newly generated functionpath.replaceWith( t.functionDeclaration( declaration.id, init.params, init.body, init.generator, init.async ) ); }}});// generate new code based on the modified AST syntax tree
console.log(generate(ast).code);
/* function say() { console.log("hello"); } * /
Copy the code
I’m sure many of you are wondering if our expression is relatively simple, but if it’s complicated, defining nesting can be very deep and complicated. How do you know which node to replace? . In fact, here you can use astExplorer.net/ which is an online conversion of AST website. We can open two Windows, putting the code before conversion in the first window and the interface to be converted in the second window. At this point we can compare the difference before and after the transformation to implement our code.
6. Think about how plug-ins are implemented.
Get the code of the currently open JS file in the editor. 2. Parse the code into the AST syntax tree. 3
We already know 2 and 4. Now we just need to see how 1, 3 and 5 can be implemented. Ok
1 and 5 we can use the methods provided by vscode
1, get the editor’s currently open JS file code
import * as vscode from "vscode";
// The currently open file
const { activeTextEditor } = vscode.window;
// Then we can get our code easily from getText under document
const code = activeTextEditor.document.getText();
Copy the code
5, replace the current JS file code
activeTextEditor.edit((editBuilder) = > {
editBuilder.replace(
// Since we want full file replacement, we need to define an interval from scratch
new vscode.Range(
new vscode.Position(0.0),
new vscode.Position(activeTextEditor.document.lineCount + 1.0)),// Our new code
generate(ast).code
);
});
Copy the code
Ok, now we are left with the core step 3.
3. Traverse the AST syntax tree to delete unused definitions
Let’s start by looking at, what does an unused definition cover?
import vue from "vue";
const a = { test1: 1.test2: 2 };
const { test1, test2 } = a;
function b() {}
let c = () = > {};
var d = () = > {};
Copy the code
Then the online AST transforms the site and copies the content to see the generated syntax tree structure
Let’s implement an example first, such as removing unnecessary variables from the following code
Transformation before
var a = 1;
var b = 2;
console.log(a);
Copy the code
After the transformation
var a = 1;
console.log(a);
Copy the code
- Scope.getbinding (name) gets all current bindings
- Scope.getbinding (name). Referenced Whether the binding is referenced
- Scope.getbinding (name). ConstantViolations Gets all current binding changes
- Scope.getbinding (name). ReferencePaths Obtains all binding paths
Code implementation
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; This scenario
node.declarations = declarations.filter((declaration) = > {
const { id } = declaration;
// const { b, c } = a;
if (t.isObjectPattern(id)) {
// path.scope.getBinding(name). Referenced Determine whether a variable is referenced
// Filter to remove unused variables
id.properties = id.properties.filter((property) = > {
const binding = path.scope.getBinding(property.key.name);
return!!!!! binding? .referenced; });// If all variables in the object are not applied, the object is removed entirely
return id.properties.length > 0;
} else {
// const a = 1;
const binding = path.scope.getBinding(id.name);
return!!!!! binding? .referenced; }});// If the entire definition statement is not referenced, the entire definition statement is removed
if (node.declarations.length === 0) { path.remove(); }}});console.log(generate(ast).code);
Copy the code
7. Now that you understand the basic process, let’s look at the final code implementation
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, {// Handle 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(); }},/ / handle the 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(); }},/ / processing 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);
Copy the code
8. Vscode sets the limit that our plugin only supports js files
Since our current implementation is for JS files, we can disable our shortcut keys when opening other types of files. We can modify package.json package.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"}}}]Copy the code
9. Integrate into the project we created earlier
Omitted here… I believe that we have seen the above introduction is fully capable of their own integration
10. Package and publish plug-ins
For packaging we can use vsCE tools
Install VSCE globally
# npm
npm i vsce -g
# yarn
yarn global add vsce
Copy the code
Packaging plug-in
Modify the readme. md file before packaging otherwise an error will be reported
vsce package
Copy the code
After execution, a.vsix file is generated
You can import it directly if you want to use it locally with vscode
If you want to release to the market, we need to register account code.visualstudio.com/api/working…
# Login account
vsce login your-publisher-name
# release
vsce publish
Copy the code
Released after successful can saw in our market marketplace.visualstudio.com/items?itemN… You can also search for our plugin in vscode
conclusion
So far, I believe you have an understanding of the basic process of vscode plug-in development.
If you find this article helpful, you can like 😊
Of course, there are a lot of configuration of vscode plug-in that has not been introduced. If you have time later, you can organize it into a separate article to introduce
If you have problems in the development process or other front-end technical problems, you can also add my wechat RJJS1221 communication, or directly reply in the comments section.
Source address github.com/taoxhsmile/…