寫在前面
為什么要開發這個擴展呢,是因為微信開發者工具自身不支持頁面引入組件的跳轉,人工根據引入組件路徑查看對應代碼的方式,效率偏低。就形如這樣的json文件,引入了多個組件,比如要查看

先看看效果,功能展示如下:按下ctrl+鼠標左鍵,就能實現下面5種跳轉功能。
1. app.json中的分包頁面跳轉

2. js文件中的wx.navigateTo,wx.redirectTo,wx.reLaunch跳轉
3. wxml文件中微信標簽bindTap屬性綁定的方法跳轉
4. wxml文件中微信wxs標簽src路徑跳轉
5. js文件中的ajax url跳轉
這個插件不僅可以在VSCode編輯器中使用,也可以在微信開發者工具中使用,效果一模一樣。在微信開發者工具中的使用方法如下:
1. 微信開發者工具工具欄--編輯-->打開編輯器擴展目錄-將VSCode的擴展復制進去,假如你已經安裝了某個擴展,這個擴展一般在C:\Users\xxx用戶名\.vscode\extensions路徑下。
2. 微信開發者工具工具欄--編輯-->管理編輯器擴展, 將VSCode擴展ID添加到擴展列表。擴展ID的查找方式見下圖。

3. 重啟微信開發者工具。
如何開發出這樣的VSCode 微信小程序擴展?
step1 安裝vscode擴展開發腳手架yo,generator-code和vsce
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install -g yo generator-code vsce
step2 用yo初始化擴展項目
yo code ? ========================================================================== We're constantly looking for ways to make yo better! May we anonymously report usage statistics to improve the tool over time? More info: https://github.com/yeoman/insight & http://yeoman.io ========================================================================== No _-----_ ╭──────────────────────────╮ | | │ Welcome to the Visual │ |--(o)--| │ Studio Code Extension │ `---------´ │ generator! │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? What type of extension do you want to create? New Extension (JavaScript) ? What's the name of your extension? wx-minipro-helper ? What's the identifier of your extension? wx-minipro-helper ? 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
step3 修改package.json文件
{ "name": "wx-minipro-helper", "displayName": "wx-minipro-helper", "description": "第一版本只實現了微信開發者工具不支持的 1.app.json內的頁面路徑跳轉; 2. 頁面下的json文件中引入的組件路徑跳轉; 3.頁面文件下的js文件中的引入外部工具庫跳轉; 4.wx.navigateTo,wx.redirectTo,wx.reLaunch跳轉; 5.wxml標簽中綁定的方法跳轉; 后續會添加自動補全,懸浮提示功能", "version": "0.0.3", "keywords": [ // 添加搜索關鍵字 "wx", "minipro", "helper" ], "publisher": "wph", // 添加發布者 "icon": "src/images/icon.png", // 添加圖標至少是128*128 "main": "src/extension", // 修改入口文件 "engines": { "vscode": "^1.5.0" // 依賴的vscode版本 }, "categories": [ "Other" ], "activationEvents": [ //啟動時觸發的命令 "*" ], "contributes": { "commands": [ { "command": "extension.wx-minipro-helper", "title": "wx-minipro-helper" } ] }, "scripts": { "lint": "eslint .", "pretest": "yarn run lint", "test": "node ./test/runTest.js" }, "devDependencies": { "@types/glob": "^7.1.3", "@types/mocha": "^8.0.0", "@types/node": "^12.11.7", "@types/vscode": "^1.5.0", "eslint": "^7.9.0", "fs": "^0.0.1-security", "glob": "^7.1.6", "mocha": "^8.1.3", "os": "^0.1.1", "path": "^0.12.7", "typescript": "^4.0.2", "vscode": "^1.1.37", "vscode-test": "^1.4.0" }, "bugs": { "url": "https://github.com/wph/wx-minipro-helper/issues" }, "repository": { "type": "gitee", "url": "https://gitee.com/getbetter/wx-minipro-helper.git" }, "homepage": "https://gitee.com/getbetter/wx-minipro-helper/blob/master/README.md" }
step4 寫擴展的功能,擴展入口extension.js內容如下
const vscode = require('vscode');
/**
* 插件被激活時觸發,所有代碼總入口
* @param {*} context 插件上下文
*/
exports.activate = function (context) {
console.log('擴展“wx-minipro-helper”已被激活!');
require('./jump-to-definition')(context); // 跳轉到定義
};
/**
* 插件被釋放時觸發
*/
exports.deactivate = function () {
console.log('擴展“wx-minipro-helper”已被釋放!');
};
extension.js引入的jump-to-definition文件,是跳轉功能的具體實現,我為這部分代碼寫了詳盡的注釋, 基本原理就是VSCode提供了光標點擊事件的一些屬性,如點擊了什么文件,點擊了那一行,點擊了文件中的什么文本,根據這些信息可以拿到跳轉路徑,然后調用vscode提供的跳轉api,就能實現定義跳轉功能
const vscode = require('vscode');
const path = require('path');
const fs = require('fs');
const util = require('./utils');
/**
* 查找文件定義的provider,匹配到了就return一個location,否則不做處理
* @param {*} document
* @param {*} position
* @param {*} token
*/
function provideDefinition(document, position, token) {
// 獲取工作目錄
const projectPath = util.getProjectPath(document);
// 獲取光標所在行的文檔路徑
const { fileName } = document;
// 獲取光標所在行的文檔夾路徑
const workDir = path.dirname(fileName);
// 獲取光標所在位置的雙引號范圍內的文本路徑,並去除空格,引號,逗號
let textPath = document.getText(document.getWordRangeAtPosition(position, /[\'\"](.*?)[\'\"]/g));
textPath = textPath.replace(/,|\s|\'|\"/gi, '');
// 獲取光標所在行的信息
const line = document.lineAt(position);
// 如果跳轉行存在import字樣,屏蔽掉這個工具的跳轉功能,因為會與開發編輯工具的跳轉功能沖突
if (line.text.includes('import')) return null;
// wxml文件中微信標簽上bindTap綁定方法跳轉, 因為在wxml文件中,獲取到的光標所在處的文本是整個文檔,所以要修改
// 光標所在行截取出來的字符串,沒有.或/等路徑符
if (/\.wxml$/.test(fileName) && !/[\/\.]/.test(textPath)) {
// const textLine = document.lineAt(position);
const wordRange = document.getWordRangeAtPosition(position, /[\w|\-]+\b/);
// const tag = (textLine.text.match(/(?<=<\/?)[\w|\-]+\b/) || [])[0];
const word = document.getText(wordRange);
// console.log(wordRange, word, tag, textPath);
// 在wxml文件對應的js文件中,查找方法名所在的行號和列數
const filePath = fileName.replace(/\.wxml$/, '.js');
const pos = util.findStrInFile({ filePath, str: word});
const { row, col } = pos;
// 判斷是否找到
if (row != 0 && col != 0) {
return new vscode.Location(vscode.Uri.file(filePath), new vscode.Position(row, col));
}
return [];
}
// 項目定制需求--ajax url跳轉,不通用
// 檢測光標所在行的字符串中是否有ajax('')字樣
const ajaxReg1 = new RegExp(`ajax\\(\\s*.*('|")${textPath}('|"),?`, 'gm');
// 檢測光標所在行的字符串中是否有yield call(ajax字樣
const ajaxReg2 = new RegExp(`yield call\\(ajax\\, ?('|")${textPath}('|"), ?`, 'gm');
const json = document.getText();
if (ajaxReg1.test(json) || ajaxReg2.test(json)) {
// webapp項目
const destPath1 = `${projectPath}/src/config/api.js`;
// 小程序項目
const destPath2 = `${projectPath}/config/api-config.js`;
let destPath = '';
if (fs.existsSync(destPath1)) {
destPath = destPath1;
} else if (fs.existsSync(destPath2)) {
destPath = destPath2;
}
// 文件存在
if (fs.existsSync(destPath)) {
try {
// 查找關鍵字在文件中的行號和列號
const pos = util.findStrInFile({ filePath:destPath, str: textPath });
const { row, col } = pos;
return new vscode.Location(vscode.Uri.file(destPath), new vscode.Position(row, col));
} catch (error) {
vscode.window.showErrorMessage('%c error:', 'color: #0e93e0;background: #aaefe5;', JSON.stringify(error));
return [];
}
}
return []
}
// 微信小程序app.json跳轉邏輯比較特殊,存在分包路徑,如果是分包路徑,要拼接子包root路徑部分
if (fileName.includes('app.json')) {
const json = JSON.parse(document.getText());
// 分包路徑判斷
let isSubPath = json.subpackages.some((item) => {
if (item.pages.includes(textPath)) {
// 如果是分包路徑,則要拼接分包的root路徑部分
textPath = `/${item.root}/${textPath}`;
return true;
}
return false;
});
// 如果是正常頁面路徑,要在頁面路徑前面加絕對路徑符號/
if (!isSubPath) {
textPath = `/${textPath}`;
}
}
// 如果沒有文件后綴,說明是json,或者js,jsx文件中的navigateTo跳轉,默認跳轉到對應頁面的wxml文件
const reg = /\.\w+$/;
let isNoSuffix = !reg.exec(textPath);
// 沒有文件后綴,添加默認的跳轉文件類型
if (isNoSuffix) {
textPath = `${textPath}.wxml`;
}
let jumpPath;
if (textPath.startsWith('/')) {
// 為絕對路徑時-直接拼接工程路徑+截取的路徑字符串
jumpPath = path.join(projectPath, textPath);
} else {
// 為相對路徑時-轉換成絕對路徑
jumpPath = path.resolve(workDir, textPath);
}
console.log('====== 進入 provideDefinition 方法 ======');
console.log('projectPath=%s ', projectPath); // 當前工程目錄
console.log('document:', document); // 文檔對象
console.log('position:', position); // 位置
// console.log('token=%o', token); // 分詞
console.log('line:', line); // 當前光標所在行
console.log('fileName=%s ', fileName); // 當前文件名
console.log('workDir=%s', workDir); // 當前文件所在目錄
console.log('textPath=%s', textPath); // 跳轉短路徑
console.log('jumpPath=%s ', jumpPath); // 跳轉路徑
// 路徑不存在則不跳轉
if (!jumpPath || !fs.existsSync(jumpPath)) {
return null;
} else {
return new vscode.Location(vscode.Uri.file(jumpPath), new vscode.Position(0, 0));
}
}
module.exports = function (context) {
// 注冊如何實現跳轉到定義,第一個參數表示僅對js,jsx,ts,tsx,json,wxml文件生效
context.subscriptions.push(
vscode.languages.registerDefinitionProvider(
{ pattern: '**/*.{ts,js,jsx,tsx,json,wxml}' },
{
provideDefinition,
}
)
);
};
step5 調試VSCode 擴展,按下F5, VSCode會啟動一個新的窗口,在新窗口調試功能,在舊窗口看調試日志,下圖的第一張圖是舊窗口,第二張圖是新窗口


step6 擴展開發完成后,就可以准備發布了。首先你需要注冊一個發布賬號 marketplace.visualstudio.com/manage/,填寫各種注冊信息,注冊完之后,安裝官方發布腳本,然后執行打包腳本命令
E:\study\wx-minipro-helper>vsce package INFO Detected presense of yarn.lock. Using 'yarn' instead of 'npm' (to override this pass '--no-yarn' on the command line). DONE Packaged: E:\study\wx-minipro-helper\wx-minipro-helper-0.0.3.vsix (10 files, 23.06KB)
step7 在vscode應用市場發布擴展 ,剛才生成的擴展wx-minipro-helper-0.0.3.vsix

擴展的更新方法

避坑指南
1. VSCode 擴展 的js文件,只要有一處報錯,調試控制台就沒有任何日志輸出,不好排查錯誤。新手這個時候很茫然,驚慌失措。不知道哪里出了問題。看了這篇文章之后,你要淡定一些。
2. package.json中的vscode版本很重要,不能太高,我用yo code生成的初始項目,vscode的版本配置是 "vscode": "^1.51.0",結果我發現這個擴展只能在VSCode編輯器中使用,不能在微信開發者工具中使用,
我猜測可能是微信開發者工具只支持vscode工具包的低級版本。我把vscode的版本改成下面的配置后,在微信開發者工具中也可以使用了。
"engines": { "vscode": "^1.5.0" // 依賴的vscode版本 },
3.在不同的文件類型中,document.getText(document.getWordRangeAtPosition(position)獲取到的光標所在處的字符串不一樣,在json文件中,能拿到雙引號之間的全部內容,在js,jsx,ts,tsx,wxml文件中,只能拿到光標所在處的哪個單詞,所以要用正則表達式,取出跳轉路徑。
4.安裝的同類插件太多了,插件之間會打架,出現如下這種情況,不進行跳轉,處理方法就是修改下載到本地的插件源代碼,屏蔽掉自己插件或別人插件的同類功能。

最后
本文實現的微信小程序擴展插件代碼托管在碼雲, 歡迎下載
這個擴展已經發布到vscode 應用擴展市場,輸入wx-minipro-helper就可以搜到

參考文章
[1] VSCode 插件開發全攻略




