【VSCode插件开发】集成 React 18(十)

发布于:2024-12-19 ⋅ 阅读:(9) ⋅ 点赞:(0)

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
在这里插入图片描述
最终页面如下:

在这里插入图片描述

源码

点击到githup

好书推荐

ReactJS实践入门
这本书是我React的启蒙书,跟着书中的教程一步一步的学习,现在已经很熟练的掌握React技术了。你想想起了解这本书的话,可点击查看

《ReactJS实践入门》将帮助读者学习ReactJS开发人员使用的专业术语,以及实践对于React编程新手和老手都有帮助的现代示例。《ReactJS实践入门》假定读者没有软件工程知识基础或相关经验,因此在介绍相关术语时会进行详细阐述。

对于刚接触JavaScript和前端开发的读者,包括那些初次接触编程的读者,《ReactJS实践入门》是一个很优秀的资源,适合初出茅庐的训练营毕业生,半路出家自学成才的程序员,以及具有WordPress、Drupal或其他编程语言基础且想要学习React的开发人员。对于资深JavaScript开发人员,《ReactJS实践入门》则可作为一本简明易懂的React指南,帮助他们快速上手。

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到