本文字数:14404字
预计阅读时间:37 分钟
IDE
中完成,大家在日常开发过程中,多多少少会有些自己的特殊定制需求去提升开发效率,比如写shell
脚本、浏览器插件等,在Visual Studio Code (VSCode)
中我们也能开发一些插件去满足日常工作需要。VSCode
中罗列当前的模板项目,预览后选择特定模板进行项目初始化,并且将一些个性化基础配置通过表单形式进行填写并渲染,避免遗漏。而且在开发过程能在VSCode
中直观的展示当前有哪些组件和工具函数可以使用,然后通过点点点操作实现组件的添加和快速使用。VSCode
插件能做什么?VSCode
插件?VSCode
中如何嵌入webview
?VSCode
中如何配置国际化?VSCode
插件中如何新建项目、新建页面、组件...?VSCode
插件可分为下面几大类:VSCode
提供哪些能力去实现上一章所提到的效果?VSCode
本身是使用Electron
开发的,那他也支持对应的能力。 VSCode
UI
webview
HTML/CSS/JS
VSCode
提供了各种 API
,允许您将自己的组件添加到工作台。Azure
应用服务扩展添加了一个视图容器NPM
扩展在 Explorer
视图中添加了一个树视图 Markdown
扩展在编辑器组中的其他编辑器旁边添加了一个 WebviewVSCodeVim
log
rfc
vetur
.vue
VSCode
MD
Git
.md
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
命令进行调试插件:VSCode
集成通过ice
生成的webview
web
目录初始化项目mkdir 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
vscode
VSCode
中的Webview
本质就是一个iframe
,所以是可以在其中执行脚本,但是VSCode
默认禁用JavaScript
,所以需要配置enableScripts=true
开启此功能。getHtmlForWebview
获取 webview
的内容。icejs
进行构建项目,yarn build
后的目录结构为index.html
、css/index.css
、js/index.js
,如果开启MPA
,则还有vendor.css/js
。getNonce
生成一个随机数,设置到script
的 nonce
属性,作用是在加密通信中使用一次随机数避免重复攻击,保证不同的消息与该秘钥加密的秘钥流不同。此代码拷贝自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": "选择模板创建应用"
}
"contributes": {
"commands": [
{
"command": "project-creator.create-project.start",
"title": "%projectCreator.create-project.commands.start.title%"
}
]
}
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, // 隐藏时保留上下文
},
);
devops
解决方案VSCode
插件所提供的能力介绍,我们完全可以将前端研发全链路
的基建集成到我们日常编码IDE
中,并且提供可视化的操作界面,让我们能安心在IDE
中进行开发调试,从一定程度减少我们开发过程到处检索而分心低效的问题。VSCode
插件的前端研发工具集,通过 GUI
操作、物料组装、代码辅助等功能让前端开发更加简单。AppWorks
做个性化改造以便满足部门内部使用。icejs
、Rax
类型项目支持友好,但由于我们部门中后台项目技术选型为umijs
,在使用AppWorks
时面板内容显得有点冗余。slave
项目中不少配置是期望在初始化模板时就自动配置好。npm
,自定义物料的方式也期望能保留我们当前发包结构微前端子应用
的场景ask-for-vscode.js
文件,则根据配置生成表单publicPath
、basePath
、mountElementId
、id
...模板选择、填写配置
这些交互功能放在展示层webview
中实现,而将获取模板、拷贝模板并渲染
这些功能交由业务层VSCode
实现。AppWorks
中“捆绑”组内高频使用插件,实现安装一个插件时可以安装一系列插件。公共配置项、国际化、创建项目和创建物料的核心逻辑...
放入packages
中使用lerna
做管理并在插件中使用。配置平台
中做统一配置;项目模板存放在Gitlab
做版本管理;组件库放在私有npm
做管理。focus create projectName核心流程
webview
页面配置中心
拉取所有“项目模板”列表Gitlab
端打的tag
ask-for-vscode.js
文件publicPath
、basePath
、mountElementId
、id
...ncp
把代码拷贝到本地临时目录,然后根据 4.2 填写的内容渲染变量在ejs
模板,最后通过metalsmith
遍历所有文件做插入修改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: {},
},
});
FocusWorks: Generate Page by Blocks
唤起新建页面
的页面生成页面
并输入页面名称和路由配置umi
类型项目中新增一个列表页
webview
页面配置中心
拉取所有“组件”列表demo
示例umi
,没有再判断是否有React
,没有再判断是否有Vue
react-sortable-hoc
支持拖拽布局npm
中下载具体组件tgz
到本地临时目录并解压src/demo
内容拷贝到第4步
中所填写的页面地址的components
目录下PageName/index.tsx
中插入引用组件的代码config/route.ts
文件则不需要配置路由,进入第7步
config/route.ts
文件内容,并插入一条路由配置第5步
中下载到临时目录的文件面包屑+筛选项+操作栏+列表+分页
页面react-sortable-hoc
来支持组件的拖拽布局。生成页面
并配置路由:
src/pages/PageName/components/
目录下src/pages/PageName/index.tsx
页面入口模板,并写入组件引用代码src/pages/PageName/index.tsx
文件.temp-block
pages/PageName/components/
目录下@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步中填写的页面名称、路由、父级页面
做处理。config/routes.ts
文件内容并使用@babel/parser.parse
将代码解析为AST
@babel/traverse
遍历数组
末尾config/routes.ts
文件config/routes.ts
文件内容并使用@babel/parser.parse
将代码解析为AST
@babel/traverse
遍历第6.1步
的AST
判断获取所有路由配置的数组
形式config/routes.ts
文件,此处为对umi
类型项目处理,使用@babel/*
做代码替换演示。FocusWorks: Import Component
或在编辑器右上方标题菜单栏中点击“+”唤起新建组件
的页面webview
contributes.menus.editor/title
中扩展编辑器标题菜单栏webview
contributes.menus.editor/title
中扩展编辑器标题菜单栏:jsx
文件中提供新建组件
的功能:FAW
中捆绑的插件的核心实现原理。JavaScript/React/TypeScript
代码自动补全// 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"
},
}
.md
文件中字数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();
}
// 下方为伪代码
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、还可以扩展到色号字符处展示对应色值
.vue
文件的词法高亮、代码补全、错误诊断、格式化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