一、背景
在项目开发中,不同的团队负责不同的业务,但是不同的业务之间仍然会有代码高度相似的地方,比如组件库引用、常用的日志格式、新建一个页面时的初始化代码。这些代码很大可能是高度相似甚至完全一样的,尽管vscode提供了本地代码片段,但这仍然是单兵作战,无法提高整个团队的效率。基于此,我司前端团队开发了vscode代码片段插件,这样,能够让整个团队共享代码片段,极大地提高了开发效率。

二、VSCode 插件简介


1. 界面布局

图片
得益于vscode开放的生态,从UI到编辑体验,几乎所有部分均可以自定义或者通过插件来进行功能拓展。

2. 插件能做什么

自定义颜色、图标主题

自定义WebView

自定义新的编程语言

自定义跳转、自动补全、悬浮提示

自定义左侧功能面板


3. 插件的开发流程

node环境 (也可以使用yarn)

npm install -g yo generator-code

生成初始项目文件(跟随提示配置即可完成初始化)

yo code

# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? HelloWorld
### Press
s the identifier of your extension? helloworld
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? Yes
# ? Bundle the source code with webpack? No
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Open with `code`

初始化之后的项目中内置一个Hello World demo, 用vscode打开项目文件,在项目内按F5键会新开一个拓展宿主窗口显示当前插件效果。当代码有更新时,运行command + shift + P调出命令窗口,输入 Reload Window, 点击结果Developer: Reload Window可同步最新修改。

初始化后目录结构

.
├── CHANGELOG.md
├── README.md
├── package.json // 插件配置
├── src
│   ├── extension.ts // 插件主程序
│   └── test // 测试文件
│       ├── runTest.ts
│       └── suite
│           ├── extension.test.ts
│           └── index.ts
├── tree.md
├── tsconfig.json
├── vsc-extension-quickstart.md
├── webpack.config.js
└── yarn.lock
在生成的目录结构中:src/extension.tspackage.json是插件的核心

4. 插件发布

发布到应用市场

在线发布参考官方文档Publishing Extensions



本地打包

安装vsce(Visual Studio Code Extensions)
npm install -g vsce
打包(注:确认package.json中有publisher属性)
vsce package
打包完成后会在项目根目录生成.vsix文件,即可在插件安装目录进行离线安装
图片


三、代码片段插件


1. 主要功能

代码片段插件主要是解决团队开发时不同项目之间相同逻辑块的快速生成,同时需要支持新的代码皮片段的开发。所以在考虑插件功能时,保留了本地模式,当用户想要测试开发时,可以开启本地模式,这样,所有的修改都只会作用在本地,不会影响线上;其余默认使用线上模式,所有用户共享一份远程代码片段文件,保持大家的代码片段都是一致的。

期望最终能够通过command + shift + P呼出菜单:
图片


对应的功能如下:

Create My Snippet(创建代码片段):根据选中的文字生成创建代码片段模板,补全信息,本地模式下写入本地文件,重启vscode,非本地模式时写入远端文件,重启vscode。


Reload Snippet File(重新加载代码片段):本地模式下,直接重启vscode;非本地模式时,下载远端文件,写入本地文件(覆盖),完成与远端同步。


Show My Snippet(显示所有代码片段):本地模式下,显示本地代码片段,非本地模式下,显示远端代码片段。


2. 工作原理

图片
红线为非本地模式下工作流程,黑线为本地模式下工作流程

3. 配置插件(均在package.json中完成)

首先配置支持文件,支持文件表明需要在哪些语言(即文件后缀)上激活该插件。

首先在categories中增加Snippets值,代表代码片段。

 "categories": [
    "Snippets",
    "Other"
 ]


在activationEvents中增加对应的语言文件

 "activationEvents": [
    "onLanguage:typescript",
    "onLanguage:typescriptreact",
    "onLanguage:javascript",
    "onLanguage:javascriptreact"
 ]


contributes中指定该代码片段插件所在文件位置(插件目录内)

  "contributes": {
    // 代码片段
    "snippets": [
      {
        "language""javascript",
        "path""./snippet/snippet.json"
      },
      {
        "language""typescript",
        "path""./snippet/snippet.json"
      },
      {
        "language""typescriptreact",
        "path""./snippet/snippet.json"
      },
      {
        "language""javascriptreact",
        "path""./snippet/snippet.json"
      }
    ]
  }
至此,该插件已经能够在对应文件中激活了,下面再完善功能:

我们需要支持创建(Create My Snippet)、更新(Reload Snippet File)、显示(Show My Snippet)代码片段3个命令

在activationEvents中增加对应的语言文件( onCommand:{插件名}.{命令名} )

 "activationEvents": [
    "onCommand:vscode-extension-snippet.Reload Snippet File",
    "onCommand:vscode-extension-snippet.Create My Snippet",
    "onCommand:vscode-extension-snippet.Show My Snippet",
    "onLanguage:typescript",
    "onLanguage:typescriptreact",
    "onLanguage:javascript",
    "onLanguage:javascriptreact"
 ]


contributes中指定该代码片段插件vscode命令列表中显示名字

"contributes": {
    "commands": [
      {
        "command""vscode-extension-snippet.Reload Snippet File",
        "title""Reload Snippet File 重新加载代码片段资源",
        "category""Snippet"
      },
      {
        "command""vscode-extension-snippet.Create My Snippet",
        "title""Create My Snippet 创建代码片段",
        "category""Snippet"
      },
      {
        "command""vscode-extension-snippet.Show My Snippet",
        "title""Show My Snippet 显示代码片段",
        "category""Snippet"
      }
    ],
}


我们还可以为该代码片段增加右键菜单,方便用户更快速调用命令,在contributes中增加对应代码

"contributes": {
    "menus": {
      "editor/context": [
        {
          "when""editorFocus",
          "command""vscode-extension-snippet.Create My Snippet",
          "group""1_modification"
        },
        {
          "when""editorFocus",
          "command""vscode-extension-snippet.Show My Snippet",
          "group""1_modification"
        }
      ]
    }
}
※ 其中when表示激活右键菜单时机,这里设置为在编辑器聚焦时;goup为命令命令在右键菜单中位置分组和顺序(详见 contributes.menus )
在我们完成这些配置之后,package.json部分配置基本完成。

主程序开发(extension.ts)


每一个插件的主程序里面主要有两个函数activatedeactivate,分别为激活时和插件取消激活时操作,我们要在插件激活的时候将命令绑定在context.subscriptions中,这样在对应命令执行的时候会调用对应的方法
import * as vscode from 'vscode';
import reloadSnippetFunc from './commands/reloadSnippet'
import createSnippetFunc from './commands/createSnippet'
import showSnippetFunc from './commands/showSnippet'
import { showInfo } from './utils';

export function activate(context: vscode.ExtensionContext) {
  // 注册指令
  const reloadSnippet = vscode.commands.registerCommand('vscode-extension-snippet.Reload Snippet File',() => reloadSnippetFunc(context))
  const createSnippet = vscode.commands.registerCommand('vscode-extension-snippet.Create My Snippet',() => createSnippetFunc(context))
  const showSnippet = vscode.commands.registerCommand('vscode-extension-snippet.Show My Snippet',() => showSnippetFunc(context))

  context.subscriptions.push(reloadSnippet);
  context.subscriptions.push(createSnippet);
  context.subscriptions.push(showSnippet);

  showInfo('代码片段插件已启动')
}

// this method is called when your extension is deactivated
export function deactivate() {}
至此,在实现内部代码之后,插件已经可以使用了

四、代码片段语法


除了简单的快速填充代码片段之外,vscode的代码片段插件还支持特定的语法,这样就能够对其进行适当的配置,在生成代码片段的时候再提速

下面介绍几种最常用语法
注:tab指键盘tab键

tabStops(按tab时焦点停留位置) $1, $2 ... 为tab键依次停留位置 $0为最后停留位置并退出代码片段

# example 基础for循环
"basic for": {
    "prefix""bfor",
    "body" : "for (const ${1} of ${2:array}) {\n $0 \n}"
}
图片


placeholders(占位符):在tab位置填入默认值

# example 带占位符的for循环
"placeholder for": {
    "prefix""pfor",
    "body" : "for (const ${1:ele} of ${2:array}) {\n $0 \n}"
}
图片


choice(下拉选择):在tab位置展示下拉菜单

# example 带下拉菜单的for循环
"test for": {
    "prefix""tfor",
    "body" : "for (const ${1|ele,key,item|} of ${2:array}) {\n $0 \n}"
}
图片


内置变量

格式为$name 或 ${name:默认值}
常见vscode内置变量:VSCode variables

举例:
快速生成基本React页面:
"React Function Comp": {
    "prefix""trfc",
    "body": [
      "import React, { useEffect } from 'react'",
      "",
      "type IProps = {}",
      "",
      "const ${TM_FILENAME/(.*).js/$1/i} = (props: IProps) => {",
      " return (",
      "
null
"
,
      " )",
      "}",
      "",
      "export default ${TM_FILENAME/(.*).js/$1/i}",
      ""
    ],
    "description""react 函数式组件快速命令"
 }
图片

在注释中使用日期变量:
"作者和时间注释": {
    "prefix""zs-Author & Time",
    "body": [
      "/**",
      " * Created by laiye on $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE",
      " */",
      "$0"
    ],
    "description""添加作者和时间注释"
}
图片


变量转换

变量转化能够让你在变量的值被插入之前,得到一个修改插入值的机会
语法:${var_name/regular_expression/format_string/options}
  1. var_name:变量名;
  2. regular_expression:正则表达式(同js正则);
  3. format_string:格式串;
  4. options:正则表达式匹配选项(i,g...)。
语法完整文档:VSCode Grammar

例如:
生成react文件时,将文件名变量首字母大写:
  "React Function Comp": {
    "prefix""trfc",
    "body": [
      "import React, { useEffect } from 'react'",
      "",
      "type IProps = {}",
      "",
      "const ${TM_FILENAME/(.*).js/${1:/capitalize}/i} = (props: IProps) => {",
      " return (",
      "
null
"
,
      " )",
      "}",
      "",
      "export default ${TM_FILENAME/(.*).js/${1:/capitalize}/i}",
      ""
    ],
    "description""react 函数式组件快速命令"
 }
图片

获取变量结果时,可以用条件判断最终显示内容:
"format_string if":{
    "prefix""tstr",
    "body""${TM_FILENAME/verify.js(x)?/${1:?jsx:tsx}/}",
}

假设我们有一个「verify.js」文件,我们有这么一条 snippet: "body": "${TM_FILENAME/verify\\.j(s)?/${1:?jsx:tsx}/}"。整个模式串匹配成功,括号中的s也成功,则写入jsx,否则写入tsx
图片


占位符转换(tab切换光标时,对占位符进行格式化)

语法:${int/regular_expression/format_string/options}

举例:react 中使用useState
const [ ${1}, set${1/(.*)/${1:/capitalize}/} ] = useState($2)

图片


注意: 在js中字符串形式的正则表达式匹配一个反斜杠要用四个反斜杠'\\\\', 第一个斜杠是js字符串的转义符,第二个斜杠是斜杠本身,第三个斜杠是js字符串的转义符,第四个斜杠是斜杠本身。将第二、四个反斜杠转为正则中的斜杠后,前面一个为正则中的转义符,将后者变为普通符号。字符串形式的正则表达式里的斜杠也是特殊符号,若要当普通符号使用,也需要转义,用“\\”标示。因为js中反斜扛为特殊符号(转义字符),js字符串里面表示斜杠需要一次转义:“\\”。


五、总结

在项目开发过程中,将许多反复使用的逻辑抽象成代码片段,能够极大的提升开发效率。当然,受限于篇幅问题,这里用到的vscode插件功能还是只是其中很小的一部分,在vscode开放的环境中,插件还大有可为。


参考文档

  1. https://code.visualstudio.com/docs/editor/userdefinedsnippets

  2. https://www.xieqiangqiang.com/p/20210206e8c2/

  3. https://blog.csdn.net/maokelong95/article/details/54379046#fn:jsexp

  4. https://juejin.cn/post/6844903869424599053

  5. https://chinese.freecodecamp.org/news/definitive-guide-to-snippets-visual-studio-code/

  6. https://www.jb51.net/article/106984.htm


本文作者:赵鹏