手把手帶你開發一款提效工具--VScode外掛

得物技術發表於2022-02-23

背景

想必每個熟練的前端弄潮者,在熟練業務之後,常常都會將元件進行抽離公共元件,可以大大的提高開發效率。然而抽離公共元件之後,日常的開發便是建立相同的資料夾,修改router ,修改表單的屬性 fieldsprop 等,重複的建立相同的資料夾,重複修改檔案 name , 重複的寫 router 等,但是作為組內不同的人員,風格又不一致,所以能不能即規範程式碼的風格,又能快速的建立模板呢。

比如我們常見的模板型別

├─componentsName
│   ├─api
│   │  index.js
│        ├─components
│        │     list-table.vue
│        │     list-search.vue
│        │     index.js
│        ├─config
│        │     index.js
│   index.vue
│   route.js

vscode外掛

通過 官方文件 的學習,我們可以發現 vscode 外掛擴充套件的方式,去實現這個功能。

  • 環境安裝
npm i -g yo generator-code // 官方外掛開發腳手架
yo code // 執行腳手架命令

根據步驟我們選擇建立 New Extension

可以選擇自己喜歡的語言 Javascript 或者 TypeScript , 這邊筆者選的是 JavaScript

同樣,我們從國際慣例的 Hello World 開始,選擇好相應的配置

專案結構

專案結構比較簡單,主要的檔案為 package.jsonextension.js 這兩個檔案

{
    "name": "hello-world", // 外掛名稱
    "displayName": "Hello World",
    "description": "hello world",
    "version": "0.0.1", // 外掛版本
    "engines": {
        "vscode": "^1.63.0" // vscode的版本
    },
    "categories": [
        "Other"
    ],
  // 擴充套件的啟用事件
    "activationEvents": [
        "onCommand:hello-world.helloWorld"
    ],
  // 入口檔案
    "main": "./extension.js",
  // vscode外掛大部分功能配置都在這裡配置
    "contributes": {
        "commands": [
            {
                "command": "hello-world.helloWorld",
                "title": "Hello World"
            }
        ]
    },
    "scripts": {
        "lint": "eslint .",
        "pretest": "npm run lint",
        "test": "node ./test/runTest.js"
    },
    "devDependencies": {
        "@types/vscode": "^1.63.0",
        "@types/glob": "^7.1.4",
        "@types/mocha": "^9.0.0",
        "@types/node": "14.x",
        "eslint": "^7.32.0",
        "glob": "^7.1.7",
        "mocha": "^9.1.1",
        "typescript": "^4.4.3",
        "@vscode/test-electron": "^1.6.2"
    }
}

extension.js 檔案內容如下

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "hello-world" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json
    let disposable = vscode.commands.registerCommand('hello-world.helloWorld', function () {
        // The code you place here will be executed every time your command is executed

        // Display a message box to the user
        vscode.window.showInformationMessage('Hello World from Hello World!');
    });

    context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
function deactivate() {}

module.exports = {
    activate,
    deactivate
}

1.  理解

  • main 定義了整個外掛的主入口,所以看到這裡,按照我們的慣性,可以新建 src 資料夾,將 extension.js 已到 src 資料夾下面。
  • contributes.commands 註冊了名為 hello-world.helloWorld 的命令,並在 src/extension.js 實現。
  • 定義完命令之後,還需要在 activationEvents 上新增 onCommand:hello-world.helloWorld

2. 執行除錯

新建完成之後,工程已經幫我們配置好除錯引數

我們只需要點選 Run Extension 即可,此時將開啟一個新的vscode 視窗,顯示Extension Development Host

此時我們按下快捷鍵 command + shift + P ,輸入 Hello 即可看到我們編寫的外掛了,選中我們的外掛,即可發現右下角的彈窗 Hello World from Hello World!

3. 新增快捷鍵和右鍵選單

在我們的 package.json 中,新增如下程式碼

"contributes": {
        "commands": [
            {
                "command": "hello-world.helloWorld",
                "title": "Hello World"
            }
        ],
    "keybindings": [
      {
        "command": "hello-world.helloWorld",
        "key": "ctrl+f10",
        "mac": "cmd+f10",
        "when": "editorFocus"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "command": "hello-world.helloWorld",
          "group": "navigation", // 選單位於最上面
          "when": "explorerResourceIsFolder" // 只有是資料夾時才能喚起選單
        }
      ]
    }
    },

在資料夾區域右鍵,即可看到我們的選單命令了, 同時也可以看到快捷鍵。

至此,我們已經完成了一個簡單的 vscode 外掛。

4. 改造

修改檔案目錄如下

├─node_modules
├─src
│   main.js
├─test
│ .eslintrc.json
│ .gitignore
│ .vscodeignore
│ jsconfig.json
│ package-lock.json
│ package.json
│    READEME.md
│ vsc-extension-quickstart.md

修改 package.json 檔案

{
    "name": "hello-template",
    "displayName": "hello-template",
    "description": "hello world",
  "publisher": "retrychx",
    "version": "0.0.1",
    "engines": {
        "vscode": "^1.63.0"
    },
    "categories": [
        "Other"
    ],
    "activationEvents": [
        "onCommand:hello-template"
    ],
    "main": "./src/main.js",
    "contributes": {
        "commands": [
            {
                "command": "hello-template",
                "title": "Hello Template"
            }
        ],
    "keybindings": [
      {
        "command": "hello-template",
        "key": "ctrl+f10",
        "mac": "cmd+f10",
        "when": "editorFocus"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "command": "hello-template",
          "group": "navigation", 
          "when": "explorerResourceIsFolder"
        }
      ]
    }
    },
    "scripts": {
        "lint": "eslint .",
        "pretest": "npm run lint",
        "test": "node ./test/runTest.js"
    },
    "devDependencies": {
        "@types/vscode": "^1.63.0",
        "@types/glob": "^7.1.4",
        "@types/mocha": "^9.0.0",
        "@types/node": "14.x",
        "eslint": "^7.32.0",
        "glob": "^7.1.7",
        "mocha": "^9.1.1",
        "typescript": "^4.4.3",
        "@vscode/test-electron": "^1.6.2"
    }
}

修改 src/main.js 檔案

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "hello-world" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json
    let disposable = vscode.commands.registerCommand('hello-template', function () {
        // The code you place here will be executed every time your command is executed

        // Display a message box to the user
        vscode.window.showInformationMessage('test');
    });

    context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
function deactivate() {}

module.exports = {
    activate,
    deactivate
}

registerCommand 方法處,修改命令,和 package.json 中的 command 中保持一致,然後除錯執行我們的 vscode ,快捷鍵召喚出我們的外掛,可以看到我們的外掛名稱 Hello Template ,點選,即可看到跳出的彈窗

5. 新建模板字串

src/ 下面,我們新建 template.js 檔案,在裡面宣告我們要新建的模板。

  • route.js 模板

由於需要路由名稱和標題兩個變數,所以宣告瞭兩個變數

const routeTemplate = params => 
`
import List from './index'

export default [
  {
    path: '${params.path}',
    name: '${params.path}',
    meta: {
      title: '${params.title}'
    },
    component: List
  }
]
`
  • index.js 入口檔案模板
const indexTemplate = 
`
<template>
  <div></div>
</template>

<script>
import { ListSearch, ListTable } from './components'
import * as API from './api/index'
import utils from '@/utils'
export default {
  components: { ListSearch, ListTable },
  data() {
    return {
      },
    }
  },
  mounted() {
  },
  methods: {
  },
}
</script>

<style>

</style>
`

根據封裝的元件,所以可以依次新建不同的模板:configTemplateapiTemplatecomIndexTemplatesearchTemplatetableTemplate 等,匯出我們需要的模板

const config = {
  routeTemplate: routeTemplate,
  indexTemplate: indexTemplate,
  configTemplate: configTemplate,
  apiTemplate: apiTemplate,
  comIndexTemplate: comIndexTemplate,
  searchTemplate: searchTemplate,
  tableTemplate: tableTemplate
}

module.exports = config

6. 引入使用者變數

由於我們需要非同步處理,所以引入async

let disposable = vscode.commands.registerCommand('hello-template', async url => {
         // 設定輸入框提示
     const options = {
      prompt: '請輸入模板名稱',
      placeHolder: '模板名稱'
    }
    // 輸入模板名稱
    const templateName = await vscode.window.showInputBox(options)
    
    // 設定標題
    const optionsTitle = {
      prompt: '請輸入標題名稱',
      placeHolder: '標題名稱'
    }
    // 輸入模板名稱
    const templateTitle = await vscode.window.showInputBox(optionsTitle)

    // 設定路徑
    const optionsRoute = {
      prompt: '請輸入路徑名稱',
      placeHolder: '路徑名稱'
    }
    // 輸入路徑名稱
    const templateRoute = await vscode.window.showInputBox(optionsRoute)
    
    const params = {
      path: templateRoute,
      title: templateTitle
    }
    });

執行除錯,我們可以看到呼叫我們的外掛,可以看到出現了輸入框:

通過輸入名稱,我們可以拿到自己想要的變數。然後我們就可以呼叫 fspath 兩個模組就可以寫我們自己的檔案了。

由於為了保證,我們的建立檔案和資料夾的順序。

首先我們用了 existsSyncmkdirSync 來建立資料夾;然後我們再用 existsSyncwriteFileSync 來建立檔案,然後再最後,做個成功的提示即可:

vscode.window.showInformationMessage('模板建立成功')

至此,我們已經完成了所有的編碼。那麼我們就看一下最後的除錯結果。

在資料夾處右鍵,召喚出我們的外掛指令Hello Template

輸入對應的名稱之後,我們可以看到在右鍵的資料夾下,建立了我們想要的模板。

我們就可以節省很多重複的工作了。

7. 引入新功能

由於在開發過程中,後端給的開發文件,提供的介面都是來自mock的連線,這個時候就在想能不能解析mock的介面資料自動引入介面註釋。

const request = require('request')

const YAPIURL = 'https://mock.shizhuang-inc.com/api/interface/get'
const param = 'token' // 個人的token

function getYapi(id) {
  const url = `${YAPIURL}?id=${id}&token=${param}`
  return new Promise(async (resolve, reject) => {  
    request(url, function(error, response,body) {
      debugger
      if(error) {
        reject(error)
      }
      const bodyToJson = JSON.parse(body)
      // 介面id不存在
      if(!bodyToJson.data) {
        reject(null)
      }
      resolve({
        title: bodyToJson.data.title,
        path: bodyToJson.data.path
      })
    })
  })
}

module.exports = {
  getYapi
}
  • 新增右鍵選單

package.json 裡面

"menus": {
      "editor/context": [
        {
          "when": "resourceLangId == javascript", // 當檔案為js檔案的時候
          "command": "erp-addInterface",
          "group": "navigation"
        }
      ]
    }

main.js 中,註冊command事件

let addDisposable = vscode.commands.registerCommand('erp-addInterface', async url => {
     // 設定輸入框提示
     const options = {
      prompt: '請輸入介面Id',
      placeHolder: '介面Id'
    }
    // 輸入路徑名稱
    const apiTag = await vscode.window.showInputBox(options)
    if(!+apiTag) {
      vscode.window.showInformationMessage('輸入正確的介面Id')
      return
    }
    try {
      const res = await api.getYapi(+apiTag)
      const apiName = res.path ? res.path.split('/').pop() : ''
      res.name = apiName

      const interfaceTemplate = config.interfaceTemplate(res)

      await fs.appendFileSync(url.path, interfaceTemplate, 'utf8')

      vscode.window.showInformationMessage('介面新增成功')
    } catch (error) {
      if(!error) {
        vscode.window.showInformationMessage('介面Id不存在')
        return
      }
      vscode.window.showInformationMessage(error)
    }
  • 檢視效果

可以生成註釋和介面,方便快捷。

打包

無論是本地打包還是釋出到應用市場,我們都要藉助vsce 這個工具。

1. 安裝

npm install vsce -g

2. 打包

打包成 vsix 檔案

vsce package

發現報錯如下:

錯誤指出我們要修改 README.md 檔案,我們修改以下檔案,再次執行打包。

按照指示命令,一步步的執行,打包成功,看一下我們的專案目錄,可以看到我們的打包檔案。

3. 釋出

開啟發布市場 官方網站 , 建立自己的釋出賬號,然後記錄下自己的個人 token, 即可開始釋出。

vsce publish

輸入自己的賬號,和 token 之後就可以釋出了。等待幾分鐘後就可以在網頁看到自己的專案

例如筆者釋出的外掛 erp-template ,在外掛市場搜尋可以看到,我們自己的外掛了

好了,至此,vscode 外掛開發已經完成了。

總結

這裡僅僅是想到的一個開發場景,相對來說,只是提供一個開發思路,在開發過程中,可以多進行思考,做一些有趣的事情。

文/migor

關注得物技術,做最潮技術人!

相關文章