我点击 vscode 菜单按钮打开了 github 仓库

132 阅读3分钟

open-in-github-button

// 核心源码
import { StatusBarAlignment, window } from 'vscode'

export function activate() {
  // 添加一个状态栏按钮
  const statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 0)
  // 按钮添加调用命令
  statusBar.command = 'openInGitHub.openProject'
  // 按钮 icon
  statusBar.text = '$(github)'
  statusBar.tooltip = 'Open in GitHub'
  statusBar.show()
}

export function deactivate() {
}

从以上极简的源码可以看出,关键源码在 statusBar.command = 'openInGithub.openProject',那么这个命令从哪里来的呢?答案是:open-in-github

open-in-github 源码初探

下面主要分析 open-in-github 的源码,以及如何调试该 vscode 插件源码 打开 open-in-github 的 package.json 文件,可以看到入口是 out/extension.js

Pasted image 20230618191910.png

注意:这是发布打包后的入口,先执行 package.json - script task 下的 vscode:prepublish (这一步是为后面源码调试做准备,src/out 会生成 extension.js 以及其 sourcemap),实际原始入口为 src/extension.ts,

// src/extension.ts

/* IMPORT */
import Utils from './utils';

/* ACTIVATE */
const activate = Utils.initCommands;

/* EXPORT */
export {activate};

可以看到,细节再 Utils.initCommands 里面

// src/utils.ts
import * as Commands from './commands';

const Utils = {
  initCommands ( context: vscode.ExtensionContext ) {
    // 根据 vscode 插件 id,获取其 package.json 下 contributes 定义
    const {commands} = vscode.extensions.getExtension ( 'fabiospampinato.vscode-open-in-github' ).packageJSON.contributes;
    // 注册 vscode 命令,并给命令订阅对应匹配的回调操作
    commands.forEach ( ({ command, title }) => {
      const commandName = _.last ( command.split ( '.' ) ) as string,
            handler = Commands[commandName],
            disposable = vscode.commands.registerCommand ( command, () => handler () );

      context.subscriptions.push ( disposable );
    });
    return Commands;
  },
  // more...
};

export default Utils;

packageJSON.contributes

"contributes": {
	"configuration": {/*...*/},
    "commands": [
      {
        "command": "openInGitHub.openProject",
        "title": "Open in GitHub: Project"
      },
      {
        "command": "openInGitHub.openFile",
        "title": "Open in GitHub: File"
      },
      // more...
    ]
  },

那么对应命令出发后,匹配什么样回调呢?我们根据上面 src/utils.ts 源码,可以顺藤摸瓜找到

// src/commands.ts

import URL from './url';

/* COMMANDS */
function openProject () {
  return URL.open ();
}

function openFile () {
  return URL.open ( true, false, 'blob' );
}
// more...

清晰了,openInGitHub.openProject 都应订阅的是 openProject 函数,里面调用了 URL.open()

源码调试,探索本质

梳理了 open-in-github 插件的启动入口以及其中一个命令的脉络,开始源码的调试探索;这里借助另一个 vscode 插件 - Debug Launcher,同样有 open-in-github 的作者 Fabio Spampinato 开发,可零配置地方便调试 vscode 插件。

Debug Launcher 使用方式

打开 package.json 文件,鼠标聚焦到文件任意一处,ctrl+shift+p 打开 vscode 快捷命令,输入 debug 选择 Debug Launcher: Auto,按 enter 回车。以 open-in-github 项目为例:

image.png

开始调试

首先,我们在 open-in-github 的源代码这几处打上断点

  • src/utils.ts

Pasted image 20230623135059.png

  • src/commands.ts

Pasted image 20230623135245.png

  • src/url.ts

Pasted image 20230623134831.png

下面我们按照以上 Debug Launcher 的使用方式,调用 Debug auto,然后选择在新打开的 vscode 窗口里,选择一个 github 库的文件夹,ctrl+shift+p 打开 vscode 快捷命令,输入 open in gihutb,点击下面红色框的选项即可定位到相应断点

Pasted image 20230623135717.png

如前文分析一样,initCommands 方法里注册命令并订阅对应匹配的回调操作

Pasted image 20230623140842.png

接着,F8/点击 Continue,断点来到 URL.open() 处,这时可以 F11/点击 Step into ,因为我们想看其中细节:

Pasted image 20230623142040.png

由截图可知,实际会调用一个 URL.get() 的异步方法,异步返回 github 网络地址后,使用 vscode 内置的方法,即可唤起浏览器打开对应的 github 仓库。继续 Step into 看看 URL.get() 是如何得知 github 地址的?

Pasted image 20230623142556.png

Soga,明显需要我们再钻入 Utils.repo 探秘,继续 Step into... 略过不重要的过程,最终找到了!真相就是 simple-git,这是一个使用 node.js 实现且轻量的 git 接口,可以解析 git 版本管理的系列信息,异常强大。

Pasted image 20230623142931.png

总结

最后,我们来做个简单整理归纳,open-in-github-button 实际只是给 vscode 注册了一个底部菜单按钮,调用命令是 open-in-gitutb 提供的 openInGitHub.openProject,而 open-in-github 内部的实现依赖于 simple-git 解析 git 版本管理信息而得到 gitRemotes 远程仓库地址,最终由 vscode 内置的 vscode.env.openExternal 唤起浏览器打开 github 仓库地址。通过对 open-in-github-buttonopen-in-github 的源码实现分析,不仅了解到了 vscode 插件的调试方式,还知悉其他很好的第三方库使用(如:simple-git/pify/mkdirp/find-up...),受益匪浅 🎉🎉🎉