中台前端套件
MFK(mid frontend kits)

插件脚手架
yeoman generator-code
npm install -g yo generator-code
yo code

.vscode 配置工程调试文件
src/test 测试用例
extension.ts 主入口文件
.* 各种工具配置文件
package.json 依赖、命令配置
"commands": [
{
"command": "cyberx.open",
"title": "CyberX: 打开"
},
{
"command": "cyberx.close",
"title": "CyberX: 关闭"
},
{
"command": "cyberx.reload",
"title": "CyberX: 重载"
},
{
"command": "cyberx.tpl.createView",
"title": "CyberX: 新建组件"
},
{
"command": "cyberx.tpl.createForm",
"title": "CyberX: 创建表单"
},
{
"command": "cyberx.tpl.commit",
"title": "CyberX: 提交模板"
}
],
"menus": {
"explorer/context": [
{
"command": "cyberx.tpl.createView",
"group": "1_modification",
"when": "explorerResourceIsFolder"
},
{
"command": "cyberx.tpl.createForm",
"group": "1_modification",
"when": "explorerResourceIsFolder"
}
],
"editor/context": [
{
"command": "cyberx.tpl.commit",
"group": "1_modification",
"when": "editorHasSelection"
}
]
}
import * as vscode from 'vscode';
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "demo" is now active!');
let disposable = vscode.commands.registerCommand('demo.helloWorld', () => {
vscode.window.showInformationMessage('Hello World from demo!');
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate() {}
const { VueLoaderPlugin } = require('vue-loader');
chainWebpack: (config) => {
config.module
.rule('vue')
.test(/\.vue$/)
.use('vue-loader')
.loader('vue-loader');
config
.plugin('vue-loader-plugin')
.use(new VueLoaderPlugin());
}
import { Location, useLocation } from 'umi'
import { VueWrapper } from 'vuera'
import FormBuilder from "./formBuilder.vue"
export default function FormPage() {
const location:Location = useLocation();
const savePath = location.query ? location.query.savePath : '';
return (
<div>
<VueWrapper
component={FormBuilder}
savePath={savePath}
/>
</div>
)
}
构建改造

插件功能
插件 extension.ts 编译 [esbuild]
import esbuild from "esbuild";
// ... options
platform: "node",
entryPoints: [path.resolve("src", "extension.ts")],
external: ["vscode", "snowpack", "esbuild"],
outdir: path.resolve("out"),
plugins: [
svgrPlugin(),
sassPlugin(),
{
name: "extension-env-resolver",
setup(builder) {
builder.onResolve({ filter: /@esbuild-env/ }, () => {
return {
path: "@esbuild-env",
namespace: "@esbuild-env",
};
});
builder.onLoad({ filter: /@esbuild-env/ }, () => {
return {
contents: JSON.stringify(isDev ? devEnv : prodEnv),
loader: "json",
};
});
},
},
],
bundle: true,
if (isDev) {
esbuild.build({
...extensionCommonBuildOptions,
sourcemap: "both",
watch: {
onRebuild(error, result) {
if (error) trackMessage(error, console.error);
else trackMessage(result, console.log);
},
},
});
} else {
esbuild
.build({
...extensionCommonBuildOptions,
minify: true,
treeShaking: true,
})
.catch(console.error);
}
插件 extension.ts 编译 [esbuild]
webview交互

React 编译
const config = {
mount: {
src: { url: "/" },
},
plugins: [
"@snowpack/plugin-sass",
"@snowpack/plugin-react-refresh",
"snowpack-plugin-svgr",
],
packageOptions: {
/* ... */
},
optimize: {
sourcemap: "both",
},
devOptions: {
open: "none",
output: "stream",
hmr: true,
},
buildOptions: {
clean: true,
out: "../../out/ui",
baseUrl: "./",
},
};
export default config;
import type * as vscode from "vscode";
import * as path from "path";
export async function loadSnowpackConfig(context: vscode.ExtensionContext) {
const { loadConfiguration } = await import("snowpack");
const root = path.resolve(context.extensionPath, "src", "react-ui");
const configPath = path.resolve(
context.extensionPath,
"src",
"react-ui",
"snowpack.config.mjs"
);
return loadConfiguration({ root }, configPath);
}
snowpack.config.mjs
if (env.ENV === "dev") {
loadSnowpackConfig(context).then((config) => {
webviewManager.devServerConfig = {
port: config.devOptions.port,
hmrSocketPort: config.devOptions.hmrPort ?? config.devOptions.port,
};
vscode.commands.executeCommand(Commands.WebviewControl.Open).then(() => {
console.log("Successfully opened webview");
});
});
}
src/dev/snowpac.dev.ts
extension.ts
// package.json
"private": true,
"workspaces": [
"src/react-ui"
]
yarn workspaces
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/naming-convention": "warn",
"@typescript-eslint/semi": "warn",
"curly": "warn",
"eqeqeq": "warn",
"no-throw-literal": "warn",
"semi": "off"
},
"ignorePatterns": ["out", "dist", "**/*.d.ts"]
}
eslint config
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn format
yarn lint:fix
yarn lint:types
husky config
vscode 调试配置 task.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Snowpack Dev",
"type": "npm",
"script": "debug",
"options": {
"env": {
"TSC_WATCH": "Both"
}
},
"path": "src/react-ui/package.json",
"isBackground": true,
"problemMatcher": "$tsc-watch"
},
{
"label": "Install Extension Dependencies",
"type": "shell",
"command": "yarn",
"group": "none"
},
{
"label": "Install UI Dependencies",
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/src/react-ui"
}
}
]
}
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"sourceMaps": true,
"preLaunchTask": "${defaultBuildTask}"
},
{
// test code config
}
]
}
vscode 调试配置 launch.json
webview与插件通信
webview 接收信息
useEffect(() => {
const callback = ({ data }: any) => {
console.log("webview", data);
};
window.addEventListener("message", callback);
return () => {
window.removeEventListener("message", callback);
};
}, []);
window.vscodeAPI = acquireVsCodeApi();
const handleVueCmd = (type: string) => {
const data: PayloadInterface = mapCmd[type];
vscodeAPI.postMessage(data);
};
webview 发送信息
插件接收信息
插件发送信息
this.panel.webview.onDidReceiveMessage(
this.messageHandler
);
this.panel.webview.postMessage(json.serialize(result));
const webviewManager = new WebviewManager(context);
context.subscriptions.push(webviewManager);
extension.ts 实例化 webview 即 下方的 this.panel.webview
代码片段功能
const LIST_PATH =
env.ENV === "dev"
? path.resolve(__dirname, "../src/react-ui/src")
: path.resolve(__dirname, "../out/ui");
axios.get(`${GITLAB_API_URL}/projects/${PROJECT_ID}/repository/tree`, {
params: {
private_token: PRIVATE_TOKEN,
path: REPO_FILE_PATH,
ref: BRANCH,
},
})
...
writeFileSync(path.resolve(writePath, file.file_name), content, {
encoding: "utf8",
});
代码片段功能
代码片段提取
tpl.content.forEach((res) => {
if (res.name === 'config.json') {
const config = JSON.parse(
fs.readFileSync(res.fullname, { encoding: ENCODING })
);
Object.assign(item, config);
Object.assign(tmp[tplName], config);
} else if (res.name === 'Index.vue') {
const content = fs.readFileSync(res.fullname, {
encoding: ENCODING
});
tmp[tplName]['body'] = content.split('\r\n');
} else if (res.name.includes('preview')) {
const img = fs.readFileSync(res.fullname);
item.preview = `data:image/${
MIME_TYPE[res.extension]
};base64,${Buffer.from(img).toString('base64')}`;
}
});
list.push(item);
fs.writeFileSync(
path.resolve(__dirname, DIST_PATH, `${cateName}.snippets.json`),
JSON.stringify(tmp, null, 4),
{
encoding: ENCODING
}
);
fs.writeFileSync(
path.resolve(__dirname, LIST_PATH),
JSON.stringify(list, null, 4),
{
encoding: ENCODING
}
);

vscode.window
.showInformationMessage(
"是否重新打开编辑器使代码片段生效?",
"否",
"是"
)
.then((answer) => {
if (answer === "是") {
vscode.commands.executeCommand("workbench.action.reloadWindow");
} else {
return true;
}
});
import fs from 'fs';
import axios, { AxiosResponse } from 'axios';
import { resolve } from 'path';
import type { CommitTplPayload, SnippetPayload } from './request-interface';
const GITLAB_API_URL = "http://code.iot.chinamobile.com/api/v4";
const token = 'xxxxx'
async function queryFilesContentFromRepo(fileList: FileItem[]) {
const reqList = fileList.map(file => {
const url = `${GITLAB_API_URL}/projects/${file.pid}/repository/files/${encodeURIComponent(
file.src
)}`;
const options = {}
return axios.get(url, options);
});
return Promise.all(reqList).then((resList: AxiosResponse[]) => {
...
return filesContent;
});
}
// 脚手架拉取
const targz = require("tar.gz");
const resource = gitUrl + paramsStr;
const tplDir = this.templatePath(this.options.template);
const write = targz().createWriteStream(tplDir);
const read = request.get(resource);
/**
* @type others
* @name CyberGuide
* @title 流程图
* @description 边缘计算接入指导流程图,支持动效
* @code <CyberGuide></CyberGuide>
*/
const info = content.match(/\*\s@.*?(\r|\n)/gi);
let matchArr = data.match(/\*\s@(\w+)\s/);
const name = matchArr[1]; //注释前缀
let val = data.replace(/\*\s@\w+\s/, ""); //注释后半截
val = val.replace(/(\n|\r)/, "");
const iconPath = item.fullname.replace(
/\w+.(vue|js)/,
"icon.svg"
);
const img = fs.readFileSync(iconPath);
iconBase64 = `data:image/svg+xml;base64,${Buffer.from(
img
).toString("base64")}`;
obj.icon = iconBase64;
jsonContent[type].push(obj);
// commit为拼装好的snippets json文件
return axios.post(
`${GITLAB_API_URL}/projects/10017/repository/commits`,
commit,
{
headers: {
'PRIVATE-TOKEN': PRIVATE_TOKENS.Snippets
},
timeout: 0
})
脚手架功能
module.exports = class extends Generator {
constructor(args, opts) {
...
}
initializing() {
this.options.template = this._generateRandomString();
}
async prompting() {
this.answers = await this.prompt(...);
}
writing() {
this.spawnCommandSync("git", ["clone", ...]);
this.fs.copyTpl...
}
install() {
const runDir = this.answers.dir
? `${this.destinationPath()}/${this.answers.dir}`
: this.options.directory;
remove(`${runDir}/package.json.ejs`, () => {});
this.yarnInstall(null, {}, { cwd: this.answers.dir });
}
end() {
this.spawnCommand("npm", ["run", "serve:cms"], {
cwd: this.answers.dir
}),
}
后续计划
VSCode插件实现
By pokerone
VSCode插件实现
- 332