<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
Table of Contents generated with DocToc

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

前端工程化-VSCode插件集成脚手架和组件库

cover.png

前言

转载自搜狐-前端工程化-VSCode插件集成脚手架和组件库

<!-- VSCode是微软出的一款轻量级代码编辑器,免费而且功能强大,对JavaScriptNodeJs支持非常友好。并且提供插件机制,VSCode很多强大功能都是基于插件实现,比如代码格式化、代码智能补全等。 -->

我们程序员每天的产出大部分都是在IDE中完成,大家在日常开发过程中,多多少少会有些自己的特殊定制需求去提升开发效率,比如写shell脚本、浏览器插件等,在Visual Studio Code (VSCode)中我们也能开发一些插件去满足日常工作需要。

比如现在业务要新开发一个项目,设计稿风格和之前系统类似。那我第一想法肯定是去拷贝已有项目的代码(或者使用组内抽象的模板),然后稍作修改满足当前需求。但如果是新同学往往需要经历咨询已有项目/模板相关人员->开通各种权限->复用部分代码并做个性化修改->借助组件库、工具库进入业务功能开发,这个过程有一定沟通和时间成本。

所以我期望能有一个更直观的方式让新同学了解组内有哪些基建并投入使用,比如能直接在VSCode中罗列当前的模板项目,预览后选择特定模板进行项目初始化,并且将一些个性化基础配置通过表单形式进行填写并渲染,避免遗漏。而且在开发过程能在VSCode中直观的展示当前有哪些组件和工具函数可以使用,然后通过点点点操作实现组件的添加和快速使用。

本文也将带着下面几个问题去讲解开发VSCode插件的过程

  • VSCode插件能做什么?
  • 如何开发一款VSCode插件?
  • VSCode中如何嵌入webview
  • VSCode中如何配置国际化?
  • VSCode插件中如何新建项目、新建页面、组件...?

VSCode插件能做什么

目前常用 VSCode 插件可分为下面几大类

  • 语言类插件

    • 语法高亮(Vetur)
    • 代码自动补全(TabNine)
    • 代码片段(JS JSX Snippets)
  • 工具类插件

    • 可视化搭建页面(面向开发者的低代码)(AppWorks)
    • 时间管理(WakaTime)
    • Git管理(Git Graph)
    • TODO(TODO Tree)
  • 娱乐类插件

    • 听音乐(VSC Netease Music)
    • 炒股(韭菜盒子)
    • 玩游戏(小霸王)

VSCode可扩展能力

本章大部分内容在官网中已有说明,此处做一个简单讲解

VSCode 提供哪些能力去实现上一章所提到的效果?

基于Electron能力

VSCode本身是使用Electron开发的,那他也支持对应的能力。

  • 支持读取本地文件
  • 支持发送接受跨域请求
  • 支持创建本地服务器
  • 持久化存储本地数据

可扩展能力

  • 使用颜色或文件图标主题更改 VSCode 外观
  • UI 中添加自定义组件和视图
  • 创建 webview 以显示使用 HTML/CSS/JS 构建的自定义网页
  • 支持一种新的编程语言
  • 支持调试特定运行时

扩展工作台

vscode-workbench-contribution.png

VSCode 提供了各种 API,允许您将自己的组件添加到 工作台

  • 活动栏(Tree View Container):Azure 应用服务扩展添加了一个视图容器
  • 侧边栏(Tree View):内置的 NPM 扩展在 Explorer 视图中添加了一个树视图
  • 编辑器组(Webview):内置的 Markdown 扩展在编辑器组中的其他编辑器旁边添加了一个 Webview
  • 状态栏(Status Bar):VSCodeVim 扩展在状态栏中添加了一个状态栏项

扩展编辑器

  • 基于正则编辑页面中的内容

    • 例如:删掉当前页面所有注释或log
  • 自定义跳转、自动补全、悬浮提示

    • 例如:输入rfc自动补全代码
  • 对特定后缀名文件的解析和编辑

    • 例如:借助插件vetur解析.vue文件
  • 增强 VSCode 内置的 MD 预览和 Git 工具

    • 例如:美化预览.md文件

限制

于此同时,也存在一些限制,比如插件不能访 问VSCode UI 的 DOM 节点(如果强行改动,VSCode 会提示自身损坏)

开发插件

首先对VSCode插件能力有个大概认识,然后从HelloWorld初始化项目去入门,再去集成Webview

由于VSCode本身是使用Electron开发的,且Electron是基于Chromium,渲染进程是使用Web页面作为 UI 显示。那在VSCode中也能集成webview

初始化项目

npm i -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 <Enter> to choose default for all options below ###

? What'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`

页面关键结构如下

.
├── package.json # 插件配置
├── src
│   ├── extension.ts # 入口文件
├── tsconfig.json

package.json关键内容如下

{
  // 扩展的激活事件
  "activationEvents": [
    "onCommand:extension.sayHello"
  ],
  // 入口文件
  "main": "./src/extension",
  // 贡献点,vscode插件大部分功能配置都在这里
  "contributes": {
    "commands": [
      {
        "command": "extension.sayHello",
        "title": "Hello World"
      }
    ]
  }
}

src/extension.ts关键内容如下

const vscode = require('vscode');
// 插件被激活时触发,所有代码总入口
exports.activate = function(context) {
  // 注册命令 与`package.json`中`contributes.commands`
  context.subscriptions.push(vscode.commands.registerCommand('extension.sayHello', function () {
    vscode.window.showInformationMessage('Hello World!');
  }));
};
// 插件被释放时触发
exports.deactivate = function() {};

然后在编辑器中按 F5 即可打开新的窗口在命令面板中(⌘⇧P)运行 Hello World 命令进行调试插件

hello-word.gif

集成Webview

下方示例为在VSCode集成通过ice生成的webview

  1. 创建web目录初始化项目

    mkdir web
    cd web
    yarn create ice
    # or
    yarn create @umijs/umi-app

集成umijsoricejs,下面项目将以 ice 为例

  1. 配置package.json注册激活事件

    {
      "activationEvents": ["onCommand:project-creator.create-project.start"],
      "contributes": {
     "commands": {
       "command": "project-creator.create-project.start",
       "title": "创建项目webview"
     }
      }
    }
  • activationEvents字段值为数组,通过onCommand注册激活事件project-creator.create-project.start,而project-creator.create-project.start将在contributes.commands中定义
  • contributes字段可以配置扩展VSCode各种能力,比如commands命令configuration配置...

    • commands中的command将在src/extension.ts中进行注册事件回调
  1. 配置src/extension.ts创建webview的具体逻辑
  • 注册命令project-creator.create-project.start
  • 创建 webview 面板projectCreatorWebviewPanel

    • 如果有,则直接展示
    • 如果没有,则新建
  • 配置基本配置

    • 标题
    • 启用JavaScript 脚本
    • 隐藏时保留上下文
    • 图标
  • 设置 webview 面板内容
  • 提供 webviewvscode 交互

code-vscode-in-webview.png

VSCode中的Webview本质就是一个iframe,所以是可以在其中执行脚本,但是VSCode默认禁用JavaScript,所以需要配置enableScripts=true开启此功能。

提供Webview内容

通过getHtmlForWebview获取 webview 的内容。

由于使用icejs进行构建项目,yarn build后的目录结构为index.html、css/index.css、js/index.js,如果开启MPA,则还有vendor.css/js

如果使用其他框架比如 umijs,则采取不同的处理方式即可。

code-webview-content.png

其中通过getNonce生成一个随机数,设置到scriptnonce属性,作用是在加密通信中使用一次随机数避免重复攻击,保证不同的消息与该秘钥加密的秘钥流不同。此代码拷贝自VSCode提供的官网示例

Webview和VSCode通信

一个很常见的场景,我们在webview中通过调接口获取数据,然后渲染页面。但是在vscode webview中是不允许发送ajax请求,所有请求都是跨域(因为webview本身没有host),所以需要在VScode中进行真实的接口请求。

此过程则变为在Webview端使用vscode.postMessage,然后在VScode中使用webview.onDidReceiveMessage接收到消息后做相应处理。

将交互过程封装成connectServicecallService进行统一注册和调用。

  • 可以在VSCode端创建Webview时绑定connectService,在其中监听webview接收到的消息,然后调用VSCodeapi能力,将执行结果返回给Webview
  • Webview中调用callService,然后将事件和参数传递给connectService处理,也将处理结果传给回调函数。

vscode-webview.png

code-connectService.png

options中提供当前页面需要使用的所有服务services的定义,然后再接收到调用事件时,通过const api = services && services[service] && services[service][method]获取到具体的方法,并将参数进行传递,一定程度去抹平API的差异,减少重复代码量。

code-callService.png

国际化

VSCode的国际化主要有三部分组成

  • 配置项国际化
  • VScode代码国际化
  • Webview代码国际化
配置项国际化

我们可以在package.json中配置VSCode的配置项,这些配置项的国际化是约定在package.nls.jsonpackage.nls.zh-cn.json这些文件中。

比如可以在package.nls.json中配置插件英文名称

{
  "projectCreator.create-project.commands.start.title": "Select Scaffold to Create Application"
}

package.nls.zh-cn.json中配置插件中文名称

{
  "projectCreator.create-project.commands.start.title": "选择模板创建应用"
}

然后在package.json中使用

"contributes": {
  "commands": [
    {
      "command": "project-creator.create-project.start",
      "title": "%projectCreator.create-project.commands.start.title%"
    }
  ]
}
VScode代码国际化

国际化的解决思路都一样

code-i18n-vscode.png

在代码中进行注册,并且可以通过vscode.env.language获取VSCode当前语言环境。

import * as vscode from 'vscode';
import I18nService from './i18n';
import * as zhCNTextMap from './locales/zh-CN.json'; // { "webViewTitle": "Create Project" }
import * as enUSTextMap from './locales/en-US.json'; // { "webViewTitle": "创建项目" }

// 注册语言表
const i18n = new I18nService();
i18n.registry('zh-cn', zhCNTextMap);
i18n.registry('en', enUSTextMap);

// 设置使用的语言
i18n.setLocal(vscode.env.language);

export default i18n;

然后在代码中进行使用

projectCreatorWebviewPanel = vscode.window.createWebviewPanel(
  'project-creator', // webview 标识,只供内部使用
  i18n.format('webViewTitle'), // 标题
  vscode.ViewColumn.One, // 新开一个编辑器视图
  {
    enableScripts: true, // 启用 JavaScript 脚本
    retainContextWhenHidden: true, // 隐藏时保留上下文
  },
);
Webview代码国际化

Webview中我们采用icejs搭建项目,那就可以使用react-intl来配置国际化。

code-i18n-webview-def.png

然后在代码中进行使用
code-i18n-webview-use.png

VSCode插件集成基建

前端同学在开发过程中一般会经历但不限如下过程

  • 开发准备阶段:需求评审,查阅外部或组内知识库、开发规范
  • 编码&联调阶段:按需求场景根据外部或组内脚手架、组件库、工具库...进行编码调试
  • 调式优化阶段:数据埋点、性能优化、自动化测试...
  • 构建部署阶段:大部分企业都有自研的devops解决方案
  • 上线后数据采集&分析阶段:进行性能监控、报警、数据分析...
  • 技术沉淀:对上述过程进行复盘、总结、抽象,进入下一轮需求开发

当我们进入一个新团队时,往往期望能对团队内部的前端研发全链路有一个基本认识,进而可以快速进入开发或投身到感兴趣的技术建设。

当我们开发一个新项目时,往往期望能参考老项目看是否能复用部分,进而减少不必要的重复性工作。

基于上面章节对VSCode插件所提供的能力介绍,我们完全可以将前端研发全链路的基建集成到我们日常编码IDE中,并且提供可视化的操作界面,让我们能安心在IDE中进行开发调试,从一定程度减少我们开发过程到处检索而分心低效的问题。

AppWorks

AppWorks是一款基于VSCode插件的前端研发工具集,通过 GUI 操作、物料组装、代码辅助等功能让前端开发更加简单。

不过由于下面几个原因,我们决定基于AppWorks做个性化改造以便满足部门内部使用。

  • 他对icejsRax类型项目支持友好,但由于我们部门中后台项目技术选型为umijs,在使用AppWorks时面板内容显得有点冗余。
  • 并且我们项目使用微前端架构,在slave项目中不少配置是期望在初始化模板时就自动配置好。
  • 物料方面我们有自己一套组件库并且放在私有npm,自定义物料的方式也期望能保留我们当前发包结构

    • 物料:分为组件(component)、区块(block)和项目(scaffold)三种类型

基于上述考虑,我们做了二次开发并产出了FAW,下面将从使用效果去揭秘他的核心逻辑实现。

FAW使用效果

下面示例为新建一个微前端子应用的场景

  • 1 通过点击侧边栏激活创建项目流程
  • 2 选择具体模板后点击下一步
  • 3 输入项目名称、模板版本
  • 4 如果模板提供ask-for-vscode.js文件,则根据配置生成表单

    • 主要是配置publicPathbasePathmountElementIdid...
  • 5 表单填写完毕后点击完成
  • 6 生成项目后在当前窗口打开新项目,即可进入开发

faw-create-project.gif

FAW整体架构

根据开发插件章节,可以将模板选择、填写配置这些交互功能放在展示层webview中实现,而将获取模板、拷贝模板并渲染这些功能交由业务层VSCode实现。

于此同时可以在入口AppWorks中“捆绑”组内高频使用插件,实现安装一个插件时可以安装一系列插件。

并且将一些公共配置项、国际化、创建项目和创建物料的核心逻辑...放入packages中使用lerna做管理并在插件中使用。

物料基本信息放在配置平台中做统一配置;项目模板存放在Gitlab做版本管理;组件库放在私有npm做管理。

faw-structure.png

FAW新建项目

逻辑类似前端工程化-打造企业通用脚手架-focus create projectName核心流程

核心流程

faw-create-project.png

  • 1 点击“创建应用”,唤起webview页面
  • 2 从配置中心拉取所有“项目模板”列表
  • 3 选择“具体模板”后,拉取所有版本(版本默认约定为在Gitlab端打的tag
  • 4 选择“具体版本”后,判断当前模板是否提供ask-for-vscode.js文件
  • 4.1 如果没提供则对本模板本版本做本地缓存,方便下次使用。则进入第6步
  • 4.2 如果提供则根据配置项渲染为表单供开发者填写

    • 配置项一般为publicPathbasePathmountElementIdid...
  • 5 通过ncp把代码拷贝到本地临时目录,然后根据 4.2 填写的内容渲染变量在ejs模板,最后通过metalsmith遍历所有文件做插入修改
  • 6 打开新窗口并启动当前项目
  • 7 完成,开始进入代码编写
核心代码实现

其中第2步定义模板物料的结构,然后在配置平台维护一个json存放所有模板

code-create-project-2.png

第3步中选择具体模板后拉取所有版本,主要借助Gitlab提供的开放能力 https://docs.gitlab.com/ee/api/api_resources.html

code-create-project-3.png

第4步中选择具体版本后,拉取对应代码,并判断是否存在ask-for-vscode.js文件并解析其内容
code-create-project-4.png

因为require需要以require(/Users/${filename}.js)的形式导入绝对路径+变量,然而我们模板的名字以及配置都名为变量,故获取不到。

// 此方式可行 ✅
const code = require('/Users/careteen/Desktop/admin-umi-template/ask-for-vscode.js')
// 此方式不可行 ❌
const templateName = 'admin-umi-template'
const configName = 'ask-for-vscode'
const args = require(`/Users/careteen/Desktop/${templateName}/${configName}.js`)

所以此处采用readFileSyncnew Function(code)()的方式获取js文件内容。其中内容如下:

// 需要根据用户填写修改的字段
const requiredPrompts = [
  {
    type: 'input',
    name: 'repoNameEn',
    message: 'please input repo English Name ? (e.g. `smart-phone`.focus.cn)',
  },
  {
    type: 'input',
    name: 'repoNameEnCamel',
    message: 'please input repo English Camel Name ?(e.g. smart-case.focus.cn/`smartPhone`)',
  },
  {
    type: 'input',
    name: 'repoNameZh',
    message: 'please input repo Chinese Name ?(e.g. `智能话机`)',
  },
];
return {
  requiredPrompts,
};

用这部分内容渲染成表单,然后再根据用户输入内容渲染ejs模板,比如配置文件config/config.ts

// 模板 👉
export default defineConfig({
  title: '<%=repoNameZh%>',
  manifest: {
    basePath: '/<%=repoNameEnCamel%>/',
  },
  base: '/<%=repoNameEnCamel%>/',
  outputPath,
  publicPath: '/<%=repoNameEnCamel%>/',
  mountElementId: '<%=repoNameEnCamel%>',
  qiankun: {
    slave: {},
  },
});
// 渲染后 👇
export default defineConfig({
  title: '智能话机',
  manifest: {
    basePath: '/smartPhone/',
  },
  base: '/smartPhone/',
  outputPath,
  publicPath: '/smartPhone/',
  mountElementId: 'smartPhone',
  qiankun: {
    slave: {},
  },
});

第5步中进行拷贝和修改插入

code-create-project-5.png

最后项目生成成功后在窗口中打开新生成的项目

code-create-project-6.png

FAW新建物料

  • 1 通过命令FocusWorks: Generate Page by Blocks唤起新建页面的页面
  • 2 在面板右侧添加组件,可以在左侧进行拖拽布局
  • 3 点击生成页面并输入页面名称和路由配置
  • 4 点击完成后生成页面

faw-create-material.gif

核心流程

faw-create-material.png

  • 0 下面示例为在umi类型项目中新增一个列表页
  • 1 命令行唤起webview页面
  • 2 判断当前工作区的项目类型,然后从配置中心拉取所有“组件”列表
  • 2.1 需要维护多套组件库并提供demo示例
  • 2.2 先从依赖项中判断是否有umi,没有再判断是否有React,没有再判断是否有Vue
  • 3 添加组件后借助react-sortable-hoc支持拖拽布局
  • 3.1 只支持纵向排列,因为组件粒度都较大,横向不好布局
  • 4 填写页面名称(PageName)和路由配置(pageName)
  • 5 从npm中下载具体组件tgz到本地临时目录并解压
  • 5.1 然后将src/demo内容拷贝到第4步中所填写的页面地址的components目录下
  • 5.2 并在PageName/index.tsx中插入引用组件的代码
  • 6 判断是否需要处理路由配置
  • 6.1 如果没获取到config/route.ts文件则不需要配置路由,进入第7步
  • 6.2 如果需要配置,则会读取config/route.ts文件内容,并插入一条路由配置
  • 7 删除第5步中下载到临时目录的文件
  • 8 完成
核心代码实现

第2步中需要判断当前项目类型,好准确的获取对应的组件库列表。

code-create-material-2.png

页面物料的结构如下,粒度一般较大,比如中后台最常见的面包屑+筛选项+操作栏+列表+分页页面

code-create-material-2-1.png

第3步使用react-sortable-hoc来支持组件的拖拽布局。

code-create-material-3.png
第5步当点击完成时,生成页面配置路由

code-create-material-5.png

生成页面的流程如下

  • 5.1 将组件下载到本地src/pages/PageName/components/目录下
  • 5.2 准备src/pages/PageName/index.tsx页面入口模板,并写入组件引用代码
  • 5.3 生成src/pages/PageName/index.tsx文件

code-create-material-5-1.png

5.1将组件下载到本地src/pages/PageName/components/目录下

  • 5.1.0 准备组件库
  • 5.1.1 先下载到本地临时目录.temp-block
  • 5.1.2 将组件拷贝到当前项目的pages/PageName/components/目录下
  • 5.1.3 删除临时目录的文件
  • 5.1.4 自动安装组件的依赖

code-create-material-5-1-00.png

5.1.0 准备组件@focus/pro-concise-table,组件demo存放在@focus/pro-concise-table/src/demos/index.tsx

code-create-material-5-1-0.png

5.1.1先下载到本地临时目录.temp-block

code-create-material-5-1-1.png

其中获取组件压缩包地址主要使用package-json实现,下载tgz并解压内容则借助request-promise、zlib、tar

code-create-material-5-1-1-1.png

5.1.2 将组件拷贝到当前项目的pages/PageName/components/目录下。

code-create-material-5-1-2.png

5.1.4 自动安装组件的依赖

code-create-material-5-1-4.png

第6步如果需要配置路由,在创建路由时需要判断当前项目类型umi/react/vue,下面的逻辑主要是处理umi类型项目

code-create-material-6.png

创建umi类型项目路由核心逻辑主要是根据第第4步中填写的页面名称、路由、父级页面做处理。

  • 6.1 读取项目config/routes.ts文件内容并使用@babel/parser.parse将代码解析为AST
  • 6.2 借助@babel/traverse遍历第6.1步AST判断获取所有路由配置的数组形式
  • 6.3 将新增的路由信息拼接到第6.2步数组末尾
  • 6.4 对路由处理后重新覆盖config/routes.ts文件

code-create-material-6-0.png

6.1 读取项目config/routes.ts文件内容并使用@babel/parser.parse将代码解析为AST

code-create-material-6-1.png

6.2 借助@babel/traverse遍历第6.1步AST判断获取所有路由配置的数组形式

code-create-material-6-2.png

6.4 对路由处理后重新覆盖config/routes.ts文件,此处为对umi类型项目处理,使用@babel/*做代码替换演示。

code-create-material-6-4.png

FAW新建组件

  • 1 通过命令FocusWorks: Import Component或在编辑器右上方标题菜单栏中点击“+”唤起新建组件的页面
  • 2 将鼠标放置在期望新增组件的地方,点击组件的“添加”
  • 3 即可插入组件信息,并自动拷贝组件demo、安装依赖

faw-create-material.gif

实现的思路大部分同FAW新建物料,下面将重点介绍不一样的地方

  • 1 如果当前有激活的文件,则在右侧唤起webview
  • 2 可以在contributes.menus.editor/title中扩展编辑器标题菜单栏
  • 3 在鼠标光标处插入组件代码

1如果当前有激活的文件,则在右侧唤起webview

code-create-component-0.png

2可以在contributes.menus.editor/title中扩展编辑器标题菜单栏
code-create-component-2.png

只在jsx文件中提供新建组件的功能

code-create-component-2-1.png

3在鼠标光标处插入组件信息

code-create-component-3.png

小结

此章节介绍了我们部门以现有“智慧案场业务的微前端架构场景”(可插拔式的数据中台可能会接入若干子产品)为出发点,在此开发过程中前端组所高频使用和持续迭代的脚手架和组件库,为了让各个子产品线能快速和低成本接入,我们尝试在 IDE 中将他们进行了集成和实现。

目前这一套 IDE 插件支撑了我们15+个“宝宝”子产品的项目初始化工作,为各个业务线同学接入前期避免了大量繁琐的配置操作;也为大家开发过程提供可扩展能力:在使用公共页面和组件时可以拿来即用,也可以快速封装各自高频物料供所有人选择使用;

faw-advantage.png

FAW 的定位主要是前期老同学贡献模板和组件,对新同学特别友好,过程中新老同学一起共建,服务于整个团队。

于此同时我们捆绑了组内都在使用的其他提效插件供大家一键安装,避免新同学和组内在开发过程表现不一致的问题。

常见插件实现原理

下面简单介绍几个FAW中捆绑的插件的核心实现原理。

JSSnippet

作用:实现JavaScript/React/TypeScript代码自动补全

仓库:VS Code ES7+ React/Redux/React-Native/JS snippets

核心实现:

// package.json
{
  "contributes": {
    "snippets": [
      {
        "language": "javascript",
        "path": "./snippets/snippets.code-snippets"
      }
    ]
  }
}
// ./snippets/snippets.code-snippets.json
{
  "typescriptReactFunctionalComponent": {
    "key": "typescriptReactFunctionalComponent",
    "prefix": "tsrfc",
    "body": [
      "import React from 'react'",
      "",
      "type Props = {}",
      "",
      "export default function ${1:${TM_FILENAME_BASE}}({}: Props) {",
      "  return (",
      "    <div>${1:first}</div>",
      "  )",
      "}"
    ],
    "description": "Creates a React Functional Component with ES7 module system and TypeScript interface",
    "scope": "typescript,typescriptreact,javascript,javascriptreact"
  },
}

WordCount

作用:实时计算.md文件中字数

仓库:https://github.com/microsoft/vscode-wordcount

核心实现:

import { window } from 'vscode'

const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
let doc = window.activeTextEditor.document;

// 只处理`.md`文件
if (doc.languageId === 'markdown') {
  let docContent = doc.getText();
  // 将边界的空格删掉
  docContent = docContent.replace(/(< ([^>]+)<)/g, '').replace(/\s+/g, ' ');
  docContent = docContent.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  let wordCount = 0;
  if (docContent !== '') {
      // 获取单词数
      wordCount = docContent.split(' ').length;
  }    
  // 将当前文件的字数在左下角状态栏展示,其中`$(pencil)`是vscode提供的图标
  statusBarItem.text = `$(pencil) ${wordCount} Words`;
  // 在状态栏展示单词数
  statusBarItem.show();
}

TODOTree

作用:实现特定文本高亮

地址:https://github.com/Gruntfuggly/todo-tree

核心代码:

// 下方为伪代码
const documentHighlights = {}
const tag = 'todo'
// 1、使用正则全局匹配todo、fixme、hack...的坐标位置
// const regex = (//|#|<!--|;|/\\*|^|^[ \\t]*(-|\\d+.))\\s*($TAGS)/
while( ( match = regex.exec( editor.document.getText() ) ) !== null ) {
  const range = new vscode.Range( startPos, endPos )
  const decorationOptions = {
    range,
    backgroundColor: 'green',
    color: '#fff'
  }
  // 2、通过 createTextEditorDecorationType 构建文本装饰类型对象
  documentHighlights[tag] = vscode.window.createTextEditorDecorationType( decorationOptions )
}
// 3、在编辑器中设置文本装饰定义
editor.setDecorations( decoration, documentHighlights[ tag ] )
// 4、`TODO`文本高亮展示
// TODO

// 5、还可以扩展到色号字符处展示对应色值

Vetur

作用:实现.vue文件的词法高亮、代码补全、错误诊断、格式化

仓库:https://github.com/vuejs/vetur

LSP文档:https://www.bookstack.cn/read/VS-Code-Extension-Doc-ZH/docs-l...

总结

上述核心代码存放在 https://github.com/careteenL/faw

我们首先了解了 vscode 提供哪些能力,我们能做什么。

再通过官方脚手架初始化一个项目去入门,由于 vscode 基于 electron,基于 chrome,我们能在 vscode 中集成 webview 去丰富页面。

但是由于在 webview 中不能调用接口,所以需要在vscode端进行接口调用,所以需要 vscode 和 webview 通信。

国际化的配置思路都类似。

然后基于公司内部的现状:我们期望有一个 GUI 去实现创建项目和新增页面组件。去二次开发一款 vscode 插件。

架构设计时根据功能将插件拆分,提供一个主入口,安装时自动安装相关插件。主入口还可以捆绑其他组内高频使用的插件。

然后将页面交互都交由 webview去做,核心逻辑(调接口、渲染项目、新建文件)还是交由vscode做;并采用lerna将公用逻辑进行封装,方便各个插件调用;数据部分存储在公司内部gitlab和私有npm,然后采用配置平台管理数据大json。

新建项目时和脚手架@focus/cli的逻辑基本一致,区别在于对读取提供的ask-for-vscode.js文件内容时采用fs.readFile + new Function(code)的方式进行 hack。

新建页面时需要维护一套组件库,并存放在私有npm中,然后根据用户选择去下载对应组件的tgz压缩包,然后进行解析,再拷贝到当前项目的pages/components目录下,最后还需在routes.ts文件中插入一条路由。

在代码指定位置新增组件时,和新建页面思路类似,区别在于需要获取当前鼠标的光标位置。

工欲善其事,必先利其器。我们可以在 IDE 中去集成组内的基建,能让开发同学更直观的了解和使用;并将重复性的工作抽象封装进 VSCode 插件,支撑我们15+个“宝宝”子产品的项目接入和开发,为大家开发提效。

引用


careteenL
293 声望16 粉丝