前言

在团队开发中,有时需要共享常用代码片段,例如通用列表页、通用详情页等。我想是否可以通过 VS Code 扩展来帮助团队高效地管理并使用这些片段。

本文将讨论如何开发一个 VS Code 扩展,将代码片段保存在 GitLab 仓库中,并在扩展中提供 Webview 显示、下载到项目中以及编辑管理的功能。

效果图

思路分析

  • 将代码片段存储在 GitLab 仓库,方便维护和管理。
  • 通过 Webview 显示代码片段列表,便于选择并提供操作。
  • 在 Webview 中提供增删和更新代码片段功能。

GitLab 仓库中管理代码片段

为了让代码片段管理更加规范化,我们需要设计一个合理的 GitLab 仓库结构。

考虑到有以下需求:

  • 代码片段支持多个版本。
  • 代码片段需要包含元数据(作者、更新时间、标签、描述等)。
  • 需要提供 API 供 VS Code 扩展使用,以便获取所有代码片段。

目录结构设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
scripts
├── build.js # 用于构建代码片段,生成 JSON 文件
src
├── code-snippet-1
│ ├── 1.0.0.tsx # 片段的代码内容
│ ├── index.json # 代码片段的元数据
│ ├── 1.0.0.json # 构建后的代码片段
├── code-snippet-2
│ ├── 1.0.0.tsx
│ ├── index.json
│ ├── 1.0.0.json
index.json # 所有代码片段列表,由 build.js 生成

片段的代码内容示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from "react";

class $TM_FILENAME_BASE$ extends Component<
I$TM_FILENAME_BASE$Props,
I$TM_FILENAME_BASE$State
> {
constructor(props: I$TM_FILENAME_BASE$Props) {
super(props);
this.state = {};
}

componentDidMount() {}

render() {
return <div></div>;
}
}

interface I$TM_FILENAME_BASE$Props {}

interface I$TM_FILENAME_BASE$State {}

export default $TM_FILENAME_BASE$;

代码片段元数据信息:

1
2
3
4
5
6
7
8
{
"name": "ud-react-class-component",
"scope": "typescriptreact",
"prefix": "ud-react-class-component",
"tags": ["react"],
"description": "React,class component,缩进2个空格,无分号。",
"author": "yaoworld"
}

扩展中 Webview 显示页面内容

这部分技术基本看官方文档就行,但还是有 2 个点需要注意。

Webview 加载远程内容

  • VS Code Webview 不支持直接设置 src,只能设置 HTML。可以通过以下方法动态加载内容:
1
2
3
4
5
6
7
8
9
10
this.webviewPanel.webview.html = await this.getWebViewContent(path);

async getWebViewContent(path: string) {
let url = 'http://127.0.0.1:1234'
let html = await rp(url)
html = html.replace(/(<link.*?href="|<script.(!>)*?src="|<img.*?src=")(.+?)"/g, (m: string, $1: string, $2: string, $3: string) => {
return $1 + (url + $3) + '"'
})
return html.replace('$currentPath', path)
}

Webview 不支持常规路由

经过测试目前是不支持的前端常规路由(不包含内存路由)的,除此之外还有不少功能都是有限制的。

要么通过多入口的方式解决。

要么使用类似内存路由的方式解决,如果加载时需要加载其他路径,可以参考下面方式解决。

1
2
3
4
5
<script>
window.my = {
currentPath: "$currentPath",
};
</script>
1
2
// vs code 再加载 html 内容给 webview 前进行替换
html.replace("$currentPath", path);

读取和写入本地代码片段

原理就是在当前项目的 .vscode 目录下 创建一个 xx.code-snippets 文件,内容格式是 JSON,里面存储着当前项目的所有代码片段。

读取代码片段,可以参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private read = () => {
if (vscode.workspace.rootPath == null) {
throw new Error('请先打开一个工作区,再运行此命令')
}
const filePath = path.join(vscode.workspace.rootPath, '.vscode', 'my.code-snippets')
if (!fs.existsSync(filePath)) {
const dir = path.join(vscode.workspace.rootPath, '.vscode')
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
fs.writeFileSync(filePath, '{}')
return {}
}
const json = fs.readFileSync(filePath).toString()
const snippets = JSON.parse(json)
return snippets
}

写入代码与读取类似,只需使用 fs.writeFileSync 方法更新 my.code-snippets 文件。

如何将普通代码转换为代码片段

因为代码片段中存在一些变量,如 ${TM_FILENAME_BASE},但这个语法在我们的 tsx 是不合法的。所以我们需要做一下转换,比如使用 $TM_FILENAME_BASE$ 来表达。

先通过代码进行变量写法转换后,再通过下面的 toSnippet 方法对代码内容进行转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
function toSnippet(code) {
const separatedSnippet = code
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.split("\n");
const separatedSnippetLength = separatedSnippet.length;

let snippet = separatedSnippet.map((line, index) => {
return index === separatedSnippetLength - 1 ? `"${line}"` : `"${line}",`;
});
snippet = snippet.join("\n");
return snippet;
}

转换后,再包装成如下格式就行了:

1
2
3
4
5
6
{
"body": [
"import React from 'react';",
"export default () => <div>Hello World</div>;"
]
}

结语

本文只介绍了些思路,具体的一些实现细节,需要自己看文档摸索下。