0.背景0.1 VSCode ExtensionVSCode(全称:Visual Studio Code)是一款由微软开发且跨平台的免费源代码编辑器。该软件支持语法高亮、代码自动补全(又称 IntelliSense)、代码重构、查看定义功能,并且内置了命令行工具和 Git 版本控制系统。用户可以更改主题和键盘快捷方式实现个性化设置,也可以通过内置的扩展程序商店安装扩展以拓展软件功能。 得益于海量的优秀插件,VSCode可以用于各种语言、应用、场景的开发。在工作中,因为开发语言的特殊性,希望开发一款插件。因为VSCode UI是基于chromium,插件开发可以使用JavaScript或者微软推荐的TypeScript。 具体从零开始的插件开发流程,微软官方已经提供了很详细的教程:https://code.visualstudio.com/api/get-started/your-first-extension ,也有很多基于此的中文教程。这里不再从零介绍。 Webview API允许插件创建一个完全自定义的视图。比如自带的Markdown插件使用webview来渲染Markdown预览内容。Webview还可以用来创建VSCode原生不提供的复杂的用户接口。 具体代码更改,可以从https://github.com/sunhaox/vscode_extension_react_template 查看。
0.2 ReactReact是一个用于构建用户界面的JavaScript库,最初由Facebook于2011年开发,并于2013年5月开源。它起源于Facebook内部项目,用于改进Newsfeed和其他应用,如Instagram。React的设计目的是通过声明式设计、组件化和高效更新DOM来简化交互式UI的开发。 所以,我们的目的是,创建一个VSCode插件工程,并配置基于TypeScript的React工程,用于Webview的开发。
1.创建工程1.1 VSCode插件工程创建微软官方有详细的从零构建的文档,这里只列举用到的指令,不再做详细介绍。 安装过node.js和Yeoman后,开始创建工程。
必要的组件安装完成后,会问是否需要用VSCode打开工程,选择是,即可在vscode里编写、运行和调试插件了。
1.2 创建TypeScript的React工程通过终端,进入刚才创建好的VSCode插件目录下,通过脚手架创建React工程:
cd vscode_extension_react_template
npx create-react-app webview --template typescript
等待配置完成。看到“Happy Hacking!”证明配置成功,我们可以进入webview下,通过npm run start 来验证React工程。
1.3 修改工程以适配React修改tsconfig.json,在分析vscode插件代码时,不包含webview内容,避免误报。
在webview工程里新建一个script文件夹,新建一个build-react-no-split.js文件。我们需要这个脚本帮我们生成react的最终结果,并修改命名,方便VSCode插件中调用。
#!/usr/bin/env node
/**
* A script that overrides some of the create-react-app build script configurations
* in order to disable code splitting/chunking and rename the output build files so
* they have no hash. (Reference: https://mtm.dev/disable-code-splitting-create-react-app).
*
* This is crucial for getting React webview code to run because VS Code expects a
* single (consistently named) JavaScript and CSS file when configuring webviews.
*/
const rewire = require("rewire");
const defaults = rewire("react-scripts/scripts/build.js");
const config = defaults.__get__("config");
// Disable code splitting
config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};
// Disable code chunks
config.optimization.runtimeChunk = false;
// Rename main.{hash}.js to main.js
config.output.filename = "static/js/[name].js";
// Rename main.{hash}.css to main.css
config.plugins[5].options.filename = "static/css/[name].css";
config.plugins[5].options.moduleFilename = () => "static/css/main.css";
然后需要修改webview的package.json,在编译的时候调用脚本:
最后,在VSCode的插件工程里,修改package.json,方便我们在编译打包插件的时候一起生成webview内容。
2.插件里调用webview2.1 启动webview在插件里,通过createWebviewPanle() 创建webview,并对其进行配置:
const panel = vscode.window.createWebviewPanel(
'vscode-extension-react-template',
'Webview Demo',
vscode.ViewColumn.One,
{
enableFindWidget: true,
enableScripts: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "out"),
vscode.Uri.joinPath(context.extensionUri, "webview/build")
],
retainContextWhenHidden: true
}
);
const lightIconPath = getFileUri(panel.webview, context.extensionPath, ["media", "icon-light.svg"]);
const darkIconPath = getFileUri(panel.webview, context.extensionPath, ["media", "icon-dark.svg"]);
panel.iconPath = {
light: lightIconPath,
dark: darkIconPath
};
const stylesUri = getUri(panel.webview, context.extensionUri, [
"webview",
"build",
"static",
"css",
"main.css",
]);
const scriptUri = getUri(panel.webview, context.extensionUri, [
"webview",
"build",
"static",
"js",
"main.js",
]);
const nonce = getNonce();
panel.webview.html=/*html*/ `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' ; style-src 'unsafe-inline' ${panel.webview.cspSource}; script-src 'nonce-${nonce}';">
<link rel="stylesheet" type="text/css" href="${stylesUri}">
<title>Hello World</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>
`;
这时,在wevbidw目录下,执行npm run build 指令,编译react工程并重命名。 之后VSCode里按F5 ,在新的调试窗口里,按CTRL+SHIFT+P ,执行Hello World 指令,即可在vscode里看到我们之前创建的react webview内容。
2.2 webview和插件的通信接下来,简单修改一下react工程,实现向插件发送消息。 首先引入@types/vscode-webview 库,提供了webview和插件通信的接口。 然后额外引入一个@vscode/webview-ui-toolkit 库,这个UI库提供了和VSCode风格相同的组件,并不是必须的。 修改react的App.tsx:
function App() {
return (
<div className="App">
<VSCodeButton onClick={handleBtnClick}>
Send Message To Extension
</VSCodeButton>
</div>
);
}
function handleBtnClick() {
const message: MessageFromWebview = {
command: COMMAND.testMessageFromWebview,
data: {
message: "test message"
},
};
vscode.postMessage(message);
}
之后我们再插件侧增加消息接收代码,在创建完panel后添加以下代码:
// handle the message from webview
panel.webview.onDidReceiveMessage(
(message: Message) => {
const command = message.command;
switch (command) {
case COMMAND.testMessageFromWebview:
vscode.window.showInformationMessage(message.data.message);
return;
}
}
);
因为修改了react代码,需要重新在webview目录下,运行npm run build 编译。 此时,webview页面有一个按钮,点击后发送消息到插件侧,插件通过vscode接口在窗口右下角弹出一个提示窗口,显示webview发来的消息内容。
2.3 插件和webview的通信接下来,我们实现插件侧向webview发送消息的功能。 先在插件的package.json里添加一条新的命令,然后在extension.ts里实现消息发送。
context.subscriptions.push(vscode.commands.registerCommand('vscode_extension_react_template.sendMessage', () => {
const message: MessageFromExtension = {
command: COMMAND.testMessageFromExtension,
data: {
message: "The message from extension."
}
};
_panel?.webview.postMessage(message);
}));
之后修改React的App.tsx,增加一个VSCodeTextArea组件,用于显示接收到的插件发来的消息内容。
function App() {
window.addEventListener('message', handleMessage);
const [message, setMessage] = useState('');
function handleMessage(event: MessageEvent) {
const message = event.data as Message;
switch (message.command) {
case COMMAND.testMessageFromExtension:
setMessage(message.data.message);
return;
}
}
return (
<div className="App">
<VSCodeTextArea placeholder='will show the received message' readOnly value={message}>
</VSCodeTextArea>
<br/>
<VSCodeButton onClick={handleBtnClick}>
Send Message To Extension
</VSCodeButton>
</div>
);
}
这时,通过CTRL+SHIFT+P 执行命令Send Message To Webview ,可以看到webview里的textArea内容发生更改。
3.发布准备做完上述工作后,可以开始我们的插件和webview开发了。最后我们还需要一些额外的修改,来保证打包、发布的时候,只包含我们需要的文件。 我们使用vsce 工具来打包发布,我们在插件的package.json文件里可以看到有vscode:prepublish 字段,vsce 工具会先调用该字段定义的脚本,完成预处理。这里,除了常规的插件的重新编译外,我们还需要工具完成React的编译优化,所以我们需要修改该字段:
"scripts": {
"install_webview": "cd webview && npm install",
"install_all": "npm install && npm run install_webview",
"build_webview": "cd webview && npm run build",
"build_all": "npm run compile && npm run build_webview",
"vscode:prepublish": "npm run build_all",
"build": "npm run build_all",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src --ext ts",
"test": "vscode-test"
},
修改vscode:prepublish 和build 字段内容。 完成编译后,工具会根据.vscodeignore 里的内容,将指定内容外的文件打包至插件的安装包内。Webview 目录下很多都是React的源文件和过程文件,我们只需要webview/build/static/ 里的最终编译产物。所以,在.vscodeigore 里增加:
webview/src/**
webview/scripts/**
webview/public/**
webview/node_modules/**
webview/*.json
webview/*.md
webview/.gitignore
webview/*.json
webview/*.ico
webview/index.html
webview/robots.txt
至此,我们的包含React的VSCode插件工程就完全配置好了。
|