其他
前端工程化-VSCode插件集成脚手架和组件库
The following article is from 搜狐技术产品 Author 博点Careteen
目录
VSCode插件能做什么? VSCode可扩展能力有哪些? 如何开发一个VSCode插件? VSCode插件如何集成基建的脚手架和组件库?(FAW保姆级教程) 前端常见插件的实现原理分析?
前言
IDE
中完成,大家在日常开发过程中,多多少少会有些自己的特殊定制需求去提升开发效率,比如写shell
脚本、浏览器插件等,在Visual Studio Code (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
提供了各种 API
,允许您将自己的组件添加到工作台。活动栏(Tree View Container): Azure
应用服务扩展添加了一个视图容器侧边栏(Tree View):内置的 NPM
扩展在Explorer
视图中添加了一个树视图编辑器组(Webview):内置的 Markdown
扩展在编辑器组中的其他编辑器旁边添加了一个 Webview状态栏(Status Bar): VSCodeVim
扩展在状态栏中添加了一个状态栏项
扩展编辑器
基于正则编辑页面中的内容 例如:删掉当前页面所有注释或 log
自定义跳转、自动补全、悬浮提示 例如:输入 rfc
自动补全代码对特定后缀名文件的解析和编辑 例如:借助插件 vetur
解析.vue
文件增强 VSCode
内置的MD
预览和Git
工具例如:美化预览 .md
文件
限制
开发插件
初始化项目
? 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
关键内容如下:// 插件被激活时触发,所有代码总入口
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
命令进行调试插件:集成Webview
VSCode
集成通过ice
生成的webview
web
目录初始化项目cd web
yarn create ice
# or
yarn create @umijs/umi-app
package.json
注册激活事件"activationEvents": ["onCommand:project-creator.create-project.start"],
"contributes": {
"commands": {
"command": "project-creator.create-project.start",
"title": "创建项目webview"
}
}
}
activationEvents
字段值为数组,通过onCommand
注册激活事件project-cre
ator.create-project.start
,而project-creator.create-project.start
将在contributes.commands
中定义contributes
字段可以配置扩展VSCode
各种能力,比如commands命令
、configuration配置
...commands
中的command
将在src/extension.ts
中进行注册事件回调
注册命令 project-creator.create-project.start
创建 webview
面板projectCreatorWebviewPanel
如果有,则直接展示 如果没有,则新建 配置基本配置 标题 启用 JavaScript
脚本隐藏时保留上下文 图标 设置 webview 面板内容 提供 webview
和vscode
交互
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,则采取不同的处理方式即可。
getNonce
生成一个随机数,设置到script
的 nonce
属性,作用是在加密通信中使用一次随机数避免重复攻击,保证不同的消息与该秘钥加密的秘钥流不同。此代码拷贝自VSCode提供的官网示例。Webview和VSCode通信
webview
中通过调接口获取数据,然后渲染页面。但是在vscode webview
中是不允许发送ajax
请求,所有请求都是跨域(因为webview
本身没有host
),所以需要在VScode
中进行真实的接口请求。Webview
端使用vscode.postMessage
,然后在VScode
中使用webview.onDidReceiveMessage
接收到消息后做相应处理。connectService和callService
进行统一注册和调用。可以在 VSCode
端创建Webview
时绑定connectService
,在其中监听webview
接收到的消息,然后调用VSCode
中api
能力,将执行结果返回给Webview
端在 Webview
中调用callService
,然后将事件和参数传递给connectService
处理,也将处理结果传给回调函数。
国际化
VSCode
的国际化主要有三部分组成:配置项国际化 VScode
代码国际化Webview
代码国际化
配置项国际化
"projectCreator.create-project.commands.start.title": "Select Scaffold to Create Application"
}
"projectCreator.create-project.commands.start.title": "选择模板创建应用"
}
"commands": [
{
"command": "project-creator.create-project.start",
"title": "%projectCreator.create-project.commands.start.title%"
}
]
}
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;
'project-creator', // webview 标识,只供内部使用
i18n.format('webViewTitle'), // 标题
vscode.ViewColumn.One, // 新开一个编辑器视图
{
enableScripts: true, // 启用 JavaScript 脚本
retainContextWhenHidden: true, // 隐藏时保留上下文
},
);
Webview代码国际化
然后在代码中进行使用:
VSCode插件集成基建
开发准备阶段:需求评审,查阅外部或组内知识库、开发规范 编码&联调阶段:按需求场景根据外部或组内脚手架、组件库、工具库...进行编码调试 调式优化阶段:数据埋点、性能优化、自动化测试... 构建部署阶段:大部分企业都有自研的 devops
解决方案上线后数据采集&分析阶段:进行性能监控、报警、数据分析... 技术沉淀:对上述过程进行复盘、总结、抽象,进入下一轮需求开发
VSCode
插件所提供的能力介绍,我们完全可以将前端研发全链路
的基建集成到我们日常编码IDE
中,并且提供可视化的操作界面,让我们能安心在IDE
中进行开发调试,从一定程度减少我们开发过程到处检索而分心低效的问题。AppWorks
AppWorks是一款基于VSCode
插件的前端研发工具集,通过 GUI
操作、物料组装、代码辅助等功能让前端开发更加简单。不过由于下面几个原因,我们决定基于AppWorks
做个性化改造以便满足部门内部使用。他对 icejs
、Rax
类型项目支持友好,但由于我们部门中后台项目技术选型为umijs
,在使用AppWorks
时面板内容显得有点冗余。并且我们项目使用微前端架构,在 slave
项目中不少配置是期望在初始化模板时就自动配置好。物料方面我们有自己一套组件库并且放在私有 npm
,自定义物料的方式也期望能保留我们当前发包结构物料:分为组件(component)、区块(block)和项目(scaffold)三种类型
基于上述考虑,我们做了二次开发并产出了FAW,下面将从使用效果去揭秘他的核心逻辑实现。
FAW使用效果
下面示例为新建一个微前端子应用
的场景1. 通过点击侧边栏激活创建项目流程 2. 选择具体模板后点击下一步 3. 输入项目名称、模板版本 4. 如果模板提供 ask-for-vscode.js
文件,则根据配置生成表单主要是配置 publicPath
、basePath
、mountElementId
、id
...5. 表单填写完毕后点击完成 6. 生成项目后在当前窗口打开新项目,即可进入开发
FAW整体架构
根据开发插件章节,可以将模板选择、填写配置
这些交互功能放在展示层webview
中实现,而将获取模板、拷贝模板并渲染
这些功能交由业务层VSCode
实现。于此同时可以在入口AppWorks
中“捆绑”组内高频使用插件,实现安装一个插件时可以安装一系列插件。并且将一些公共配置项、国际化、创建项目和创建物料的核心逻辑...
放入packages
中使用lerna
做管理并在插件中使用。物料基本信息放在配置平台
中做统一配置;项目模板存放在Gitlab
做版本管理;组件库放在私有npm
做管理。FAW新建项目
逻辑类似前端工程化-打造企业通用脚手架-focus create projectName核心流程
核心流程
1. 点击“创建应用”,唤起 webview
页面2. 从 配置中心
拉取所有“项目模板”列表3. 选择“具体模板”后,拉取所有版本(版本默认约定为在 Gitlab
端打的tag
4. 选择“具体版本”后,判断当前模板是否提供 ask-for-vscode.js
文件4.1 如果没提供则对本模板本版本做本地缓存,方便下次使用。则进入第6步 4.2 如果提供则根据配置项渲染为表单供开发者填写 配置项一般为 publicPath
、basePath
、mountElementId
、id
...5. 通过 ncp
把代码拷贝到本地临时目录,然后根据 4.2 填写的内容渲染变量在ejs
模板,最后通过metalsmith
遍历所有文件做插入修改6. 打开新窗口并启动当前项目 7. 完成,开始进入代码编写核心代码实现
核心代码实现
其中第2步定义模板物料的结构,然后在配置平台维护一个json
存放所有模板。Gitlab
提供的开放能力 https://docs.gitlab.com/ee/api/api_resources.html。ask-for-vscode.js
文件并解析其内容: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`)
readFileSync
和new 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: {},
},
});
最后项目生成成功后在窗口中打开新生成的项目:
FAW新建物料
1. 通过命令 FocusWorks: Generate Page by Blocks
唤起新建页面
的页面2. 在面板右侧添加组件,可以在左侧进行拖拽布局 3.点击 生成页面
并输入页面名称和路由配置4. 点击完成后生成页面
核心流程
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步中需要判断当前项目类型,好准确的获取对应的组件库列表。面包屑+筛选项+操作栏+列表+分页
页面react-sortable-hoc
来支持组件的拖拽布局。生成页面
并配置路由:
5.1 将组件下载到本地 src/pages/PageName/components/
目录下5.2 准备 src/pages/PageName/index.tsx
页面入口模板,并写入组件引用代码5.3 生成 src/pages/PageName/index.tsx
文件
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 自动安装组件的依赖
@focus/pro-concise-table,
组件demo
存放在@focus/pro-concise-table/src/demos/index.tsx
.temp-block
。package-json
实现,下载tgz
并解压内容则借助request-promise、zlib、tar
pages/PageName/components/
目录下umi/react/vue
,下面的逻辑主要是处理umi
类型项目:umi
类型项目路由核心逻辑主要是根据第第4步中填写的页面名称、路由、父级页面
做处理。6.1 读取项目 config/routes.ts
文件内容并使用@babel/parser.parse
将代码解析为AST
6.2 借助 @babel/traverse
遍历6.3 将新增的路由信息拼接到 第6.2步的 数组
末尾6.4 对路由处理后重新覆盖 config/routes.ts
文件
config/routes.ts
文件内容并使用@babel/parser.parse
将代码解析为AST
@babel/traverse
遍历第6.1步
的AST
判断获取所有路由配置的数组
形式config/routes.ts
文件,此处为对umi
类型项目处理,使用@babel/*
做代码替换演示。FAW新建组件
1. 通过命令 FocusWorks: Import Component
或在编辑器右上方标题菜单栏中点击“+”唤起新建组件
的页面2. 将鼠标放置在期望新增组件的地方,点击组件的“添加” 3. 即可插入组件信息,并自动拷贝组件demo、安装依赖
1. 如果当前有激活的文件,则在右侧唤起 webview
2. 可以在 contributes.menus.editor/title
中扩展编辑器标题菜单栏3. 在鼠标光标处插入组件代码
webview
contributes.menus.editor/title
中扩展编辑器标题菜单栏:jsx
文件中提供新建组件
的功能:小结
常见插件实现原理
FAW
中捆绑的插件的核心实现原理。JSSnippet
JavaScript/React/TypeScript
代码自动补全{
"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
文件中字数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
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/careteenL/faw
ask-for-vscode.js
文件内容时采用fs.readFile + new Function(code)
的方式进行 hack。tgz
压缩包,然后进行解析,再拷贝到当前项目的pages/components
目录下,最后还需在routes.ts
文件中插入一条路由。引用
VSCode官网:
https://code.visualstudio.com/api
VSCode插件开发全攻略配套demo:
https://github.com/sxei/vscode-plugin-demo
前端工程化-打造企业通用脚手架:https://mp.weixin.qq.com/s/efzaiX8r2htJ1NvN26ZU8g
基于 VSCode 插件的前端研发工具
AppWorks:
https://github.com/apptools-lab/AppWorks
示例代码存放仓库:
https://github.com/careteenL/faw
前端工程化-打造企业通用脚手架:https://mp.weixin.qq.com/s/efzaiX8r2htJ1NvN26ZU8g