目录
VS Code插件开发系列专栏
文章 |
---|
入门篇(一) |
通用功能(二) |
WebView面板(三) |
消息通信(四) |
状态栏(五) |
自定义侧边栏、视图(六) |
常见自定义命令(七) |
创建终端(八) |
自定义指令实现 git 命令(九) |
前言
在开发VSCode插件时可以使用流行的前端框架,比如Vue、React、Svelte等,本文讲解一下,如何集成React 18。页面显示主要是在Webview,那么我们做的就是将 React 页面正确嵌入到 Webview 中。
由于 React 项目通常会通过 Webpack 或 Vite 等工具进行构建,需要确保 Webview 正确引用构建后的资源,并处理 VSCode 自带的 vscode-resource 协议。
在 VSCode 中,vscode-resource 协议用于加载本地文件资源。因此,你需要通过 webview.asWebviewUri 将本地文件路径转换为 Webview 可以访问的 URL。
构建插件项目
1、安装新建项目需要依赖
npm install -g yo generator-code
2、新建项目
yo code
? What type of extension do you want to create? New Extension (TypeScript)
// 这里我们先选择第一项,New Extension (TypeScript),进入下一步
? What's the name of your extension? juejin
// 插件名称
? What's the identifier of your extension? juejin
// 插件标识符(唯一id),这个是你后面发布上去后插件的名称,一般和上一步名称一样
? What's the description of your extension?
// 插件描述
? Initialize a git repository? Yes
// 是否初始化git,选择yes
? Bundle the source code with webpack? Yes
// 是否使用webpack构建你的项目,这里默认No
? Which package manager to use? yarn
// 选择包管理器,这里有npm和yarn两个选项,我一般用yarn
上面的步骤跟本专栏开篇一样,详解可查看 入门篇(一)
3、安装必要的依赖
yarn add css-loader style-loader react react-dom
yarn add esbuild @types/react @types/react-dom -D
css-loader:用于处理 CSS 文件中的 @import 和 url(),并将它们转换为 require() 语句,以便将样式加载到 JavaScript 中。
style-loader:将 CSS 注入到页面的 style 标签中。
react 和 react-dom:React 核心库和 DOM 渲染库。
@types/react 和 @types/react-dom:为 TypeScript 提供 react 和 react-dom 的类型声明,以便 TypeScript 能正确地推断和检查 React 代码
我的全部依赖如下:
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "16.x",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"@types/vscode": "^1.96.0",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"@vscode/test-electron": "^2.3.0",
"dotenv-webpack": "^8.1.0",
"esbuild": "^0.24.0",
"eslint": "^8.39.0",
"glob": "^8.1.0",
"mocha": "^10.2.0",
"npm-run-all": "^4.1.5",
"ts-loader": "^9.4.2",
"typescript": "^5.0.4",
"webpack": "^5.81.0",
"webpack-cli": "^5.0.2"
},
"dependencies": {
"axios": "^1.7.9",
"css-loader": "^7.1.2",
"qs": "^6.13.1",
"react": "18.2",
"react-dom": "18.2",
"style-loader": "^4.0.0"
}
4、设置React的开发目录
在src下新建main文件夹,main文件下新建home文件夹,home下新建index.jsx。
index.tsx中:
import React from 'react';
import * as ReactDOM from 'react-dom/client';
// types.ts
interface Article {
id: number;
title: string;
body: string;
author: string;
date: string;
}
class App extends React.Component {
render() {
return (
<div>你好</div>
);
}
}
const container: HTMLElement | null = document.getElementById("root");
const root = ReactDOM.createRoot(container as HTMLElement);
root.render(<App />);
5、配置webpack.config.js
这里安装了dotenv-webpack
插件,主要为了可以使用.env
里的环境变量
yarn add dotenv-webpack -D
//@ts-check
'use strict';
const Dotenv = require('dotenv-webpack');
const path = require('path');
//@ts-check
/** @typedef {import('webpack').Configuration} WebpackConfig **/
/** @type WebpackConfig */
const extensionConfig = {
target: 'node', //目标环境
mode: 'development', //开发环境
entry: { //react项目入口文件
home: "./src/main/home/index.tsx"
},
output: { //react输出文件
path: path.resolve(__dirname, "views"),
filename: "[name].js"
},
externals: { //vscode 模块被设置为外部依赖,不会打包进最终的文件中,而是通过 require 动态加载。
vscode: 'commonjs vscode'
},
resolve: {//配置了模块的解析顺序
extensions: [".js", ".ts", ".tsx", ".json"]
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules: {
auto: /\.module\.\w+$/i,
},
},
},
]
}
]
},
devtool: 'nosources-source-map',//使用 nosources-source-map 生成源映射,这样即使在生产环境中也能进行调试,但不会泄漏源代码内容。
//启用了 Webpack 的基础设施日志,帮助调试和监控构建过程中的问题。
infrastructureLogging: {
level: "log", // enables logging required for problem matchers
},
// 使用 dotenv-webpack 插件来加载 .env 文件中的环境变量
plugins: [
new Dotenv()
]
};
module.exports = [ extensionConfig ];
6、设置入口文件
主要实现自定义指令、新建webview。
其中article面板的html直接是原生的html,home面板的html则是自定义的,这里是封装了一个webviewContent
方法来实现。React 18的集成也主要是在webviewContent
方法里实现
export function activate(context: vscode.ExtensionContext) {
let article = vscode.commands.registerCommand("juejin.article", () => {
// 创建webview
const panel = vscode.window.createWebviewPanel(
"juejinArticle", // 标识
"稀土掘金",//Webview 标题
vscode.ViewColumn.One,//Webview 所在的编辑器位置
{
enableScripts: true,// // 启用脚本
retainContextWhenHidden: true,/ 保持 webview 状态即使在隐藏时
}
);
//图标
panel.iconPath = vscode.Uri.file(
path.resolve(context.extensionPath, "./icon/juejin.svg")
);
panel.webview.html = "<div>功能正在开发中</div>";
});
let home = vscode.commands.registerCommand("juejin.home", () => {
new webviewContent(context.extensionPath, "home");
});
context.subscriptions.push(
helloWorld,
article,
home
);
}
7、配置package.json
主要有几个点:
- 配置插件的入口文件,为打包后的
extension.js
- 设置指令,以及快捷键
- 配置
scripts
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "juejin.helloWorld",
"title": "helloWorld",
"category": "juejin"
},
{
"command": "juejin.article",
"title": "article",
"category": "juejin"
},
{
"command": "juejin.home",
"title": "home",
"category": "juejin"
}
],
"keybindings": [
{
"command": "juejin.article",
"key": "ctrl+l",
"mac": "cmd+l"
}
]
},
我的package.json如下:
"scripts": {
"vscode:prepublish": "yarn run package",
"compile": "npm-run-all compile:*",
"compile:extension": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
"compile:views": "webpack --mode production",
"watch": "npm-run-all -p watch:*",
"watch:extension": "tsc -watch -p ./",
"watch:views": "webpack --watch --mode development",
"package": "webpack --mode production --devtool hidden-source-map",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "yarn run compile-tests && yarn run compile && yarn run lint",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js"
},
插件本身需要编译,我们的React项目也需要编译,所以使用npm-run-all
插件一键操作。
其中主要修改的是:
"compile": "npm-run-all compile:*",
"compile:extension": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
"compile:views": "webpack --mode production",
"watch": "npm-run-all -p watch:*",
"watch:extension": "tsc -watch -p ./",
"watch:views": "webpack --watch --mode development",
8、封装webviewContent
在src目录下新建一个webview.ts文件。
主要实现一下几点:
1、新建一个webview面板
2、面板的html特殊处理(getWebviewContent函数)
import * as vscode from "vscode";
import * as path from "path";
export type PageName = "home";
function getConfiguration() {
//defaultCategory这个可以不看
let defaultCategory =
// 获取当前工作区的配置,从配置中读取 juejin.post.default-category 的值。
vscode.workspace.getConfiguration().get("juejin.post.default-category") ||
""; // 增加未配置分类时设置前端为默认分类
return { defaultCategory };
}
export default class ViewLoader {
private readonly _panel: vscode.WebviewPanel | undefined;
private readonly _extensionPath: string;
constructor(extensionPath: string, pageName: PageName) {
this._extensionPath = extensionPath;
this._panel = vscode.window.createWebviewPanel(
'juejin',//面板的唯一标识符
'稀土掘金',//面板的标题。
vscode.ViewColumn.One,//面板展示的位置
{
enableScripts: true,
retainContextWhenHidden: true,
// 指定 Webview 中允许加载的本地资源路径,
localResourceRoots: [
vscode.Uri.file(path.join(extensionPath, 'views')) // 如果有 views 文件夹
],
}
);
//设置面板图标
this._panel.iconPath = vscode.Uri.file(
path.resolve(extensionPath, "./icon/juejin.svg")
);
const config = getConfiguration();
//设置 Webview 内容
this._panel.webview.html = this.getWebviewContent(pageName,this._panel);
const panel = this._panel;
// 处理 Webview 消息:
// 事件监听器用于接收来自 Webview 的消息
this._panel.webview.onDidReceiveMessage((message) => {
console.log(message, 'message')
});
}
}
getWebviewContent
逻辑如下:
这里我们需要将React路径转换成VSCode URI,再将URI转成vscode-resource,最后return一个html页面,将转换后的React项目路径赋给script的src。路径转换的重点:
- 将 URI 转换为 vscode-resource 方案,表示这是一个本地资源。
vscode.Uri.file(
path.join(this._extensionPath, "views", `${pageName}.js`)
);
- URI 转换为 vscode-resourc
const reactAppUri = panel.webview.asWebviewUri(reactAppPathOnDisk)
getWebviewContent全部代码如下:
// 生成 Webview 内容
private getWebviewContent(pageName: PageName, panel: any): string {
// 获取 React 应用的路径
// vscode.Uri.file 将文件路径转换为 VSCode URI。
const reactAppPathOnDisk = vscode.Uri.file(
path.join(this._extensionPath, "views", `${pageName}.js`)
);
// 将 URI 转换为 vscode-resource 方案,表示这是一个本地资源。
const reactAppUri = panel.webview.asWebviewUri(reactAppPathOnDisk)
return ` <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Webview</title>
</head>
<body>
<div id="root"></div>
<script src="${reactAppUri}"></script>
</body>
</html>`;
}
9、配置tsconfig.json
最后配置一下ts的规则
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": [
"dom",
"es6"
],
"jsx": "react-jsx",
"sourceMap": true,
"rootDir": "src",
"skipLibCheck": true, // 跳过库文件的类型检查(提高性能)
"esModuleInterop": true, // 启用 ES 模块的互操作性
"allowSyntheticDefaultImports": true,
"moduleResolution": "node", // // 模块解析方式
"strict": true, /* enable all strict type-checking options */
},
"exclude": [
"node_modules",
".vscode-test",
"./src/main/**/*.tsx",
"./src/main/**/*.ts",
"./src/main/**/*.css",
"./src/request/**/*.ts",
]
}
10、启动插件
command + shift + p 或者 crtl + shift + p查看设置的指令,选择 juejin:home
最终页面如下:
源码
好书推荐
ReactJS实践入门
这本书是我React的启蒙书,跟着书中的教程一步一步的学习,现在已经很熟练的掌握React技术了。你想想起了解这本书的话,可点击查看
《ReactJS实践入门》将帮助读者学习ReactJS开发人员使用的专业术语,以及实践对于React编程新手和老手都有帮助的现代示例。《ReactJS实践入门》假定读者没有软件工程知识基础或相关经验,因此在介绍相关术语时会进行详细阐述。
对于刚接触JavaScript和前端开发的读者,包括那些初次接触编程的读者,《ReactJS实践入门》是一个很优秀的资源,适合初出茅庐的训练营毕业生,半路出家自学成才的程序员,以及具有WordPress、Drupal或其他编程语言基础且想要学习React的开发人员。对于资深JavaScript开发人员,《ReactJS实践入门》则可作为一本简明易懂的React指南,帮助他们快速上手。