likes
comments
collection
share

VSCode 插件开发-从零到WebView+React

作者站长头像
站长
· 阅读数 3

VSCode 插件 API 官方文档

VSCode 官方插件例子

VSCode 官方脚手架

VSCode 插件简介

VSCode 插件是 VSCode 为开发者提供的一种扩展其功能的手段,VSCode 的几乎每一个部分都可以通过插件来进行扩展。

具体有哪些能力可以看官方介绍

主要包含:

  • 通用能力
  • 主题
  • 声明性语言功能
  • 程序语言特性
  • 工作台扩展(比如:Webview)
  • 调试

VSCode 插件开发

使用官方脚手架创建项目

安装

npm install -g yo generator-code

运行

yo code

然后就出现了如下界面:

? ==========================================================================
We're constantly looking for ways to make yo better! 
May we anonymously report usage statistics to improve the tool over time? 
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== Yes

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y ` 

? What type of extension do you want to create? (Use arrow keys)
New Extension (TypeScript) 
  New Extension (JavaScript) 
  New Color Theme 
  New Language Support 
  New Code Snippets 
  New Keymap 
  New Extension Pack 
  New Language Pack (Localization) 
  New Web Extension (TypeScript) 
  New Notebook Renderer (TypeScript)

可以根据自己的需要进行选择,这里我选择了第一个,然后会提示输入插件名称、描述等信息,然后就会自动生成一个项目,如下图所示:

VSCode 插件开发-从零到WebView+React

这个工程创建出来是直接就可以使用的,按“F5”或者“运行->启动调试”,就可以运行起来插件了,第一次运行会进行编译,然后会拉起一个新的 VSCode 窗口,如下图所示:

VSCode 插件开发-从零到WebView+React

然后,按“Ctrl+Shift+P”或者“Command+Shift+P”,弹出如下命令窗,输入“Hello World”,如下图所示:

VSCode 插件开发-从零到WebView+React

最后,点击执行这个命令,就会弹出插件代码中写的弹窗,如下图所示:

VSCode 插件开发-从零到WebView+React

以上就是 VSCode 插件开发起步的过程。

Webview

Webview 功能是 VSCode 开放给开发者的一个 Web 页面功能,有了这个功能,我们就能在 VSCode 中写前端页面,并且这些页面可以使用一些 API 与 VSCode 进行交互。

Webview API | Visual Studio Code Extension API

编写一个简单的页面

下面这个是官方给的例子,打开一个🐱写代码的页面:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "my-vscode-extendsion" is now active!');

  let disposable = vscode.commands.registerCommand('my-vscode-extendsion.helloWorld', () => {
    vscode.window.showInformationMessage('Hello World from my-vscode-extendsion!');

    const panel = vscode.window.createWebviewPanel(
      'catCoding',
      'Cat Coding',
      vscode.ViewColumn.One,
      {
        retainContextWhenHidden: true, // 保证 Webview 所在页面进入后台时不被释放
        enableScripts: true, // 运行 JS 执行
      }
    );
    panel.webview.html = getWebviewContent();
  });

  context.subscriptions.push(disposable);
}

export function deactivate() { }

function getWebviewContent() {
  return `<!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Cat Coding</title>
      </head>
      <body>
        <img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" />
      </body>
    </html>`;
}

效果如下:

VSCode 插件开发-从零到WebView+React

Webview 调试

另外,我们可以调出开发者页面对页面进行调试,按“Ctrl+Shift+P”或者“Command+Shift+P”,弹出如下命令窗,输入“Open Webview Developer Tools” 可以打开 Webview 的控制台。如下图所示:

VSCode 插件开发-从零到WebView+React

数据交互

插件 -> Webview

插件中的代码:

panel.webview.postMessage({text: 'I\'m VSCode extension'});

Webview 中的代码:

window.addEventListener('message', (e) => {
  document.getElementById('test').innerHTML = e.data.text;
});

Webview -> 插件

Webview 中的代码:

const vscode = acquireVsCodeApi();
vscode.postMessage({
  text: "I'm Webview"
});

插件中的代码:

panel.webview.onDidReceiveMessage(data => {
  console.log(data.text);
});

完整代码

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "my-vscode-extendsion" is now active!');

  let disposable = vscode.commands.registerCommand('my-vscode-extendsion.helloWorld', () => {
    vscode.window.showInformationMessage('Hello World from my-vscode-extendsion!');

    const panel = vscode.window.createWebviewPanel(
      'catCoding',
      'Cat Coding',
      vscode.ViewColumn.One,
      {
        retainContextWhenHidden: true, // 保证 Webview 所在页面进入后台时不被释放
        enableScripts: true, // 运行 JS 执行
      }
    );
    panel.webview.html = getWebviewContent();
    panel.webview.postMessage({ text: 'I\'m VSCode extension' });

    panel.webview.onDidReceiveMessage(data => {
      console.log(data.text);
    });
  });

  context.subscriptions.push(disposable);
}

export function deactivate() { }

function getWebviewContent() {
  return `<!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Cat Coding</title>
      </head>
      <body>
	    <p id="test"></p>
        <script>
          window.addEventListener('message', e => {
            document.getElementById('test').innerHTML = e.data.text;
          });
          const vscode = acquireVsCodeApi();
          vscode.postMessage({
            text: "I'm Webview"
          });
        </script>
      </body>
    </html>`;
}

运行效果如下图所示:

VSCode 插件开发-从零到WebView+React

编写一个 React 页面

从上面的例子我们可以看出来,我想要在 Webview 环境中写个界面是非常难受的,因为 getWebviewContent 返回的是一个字符串,写个简单页面还行,要是功能复杂点儿,不上个 React 或者 Vue 还真是不习惯,也不方便。

这里我选择使用传统的 Webpack + React(tsx),接下来我们需要安装一堆东西。

安装react依赖

pnpm i react react-dom -S

安装react类型依赖

pnpm i @types/react @types/react-dom -D

创建index.html

先在 src 下创建一个 view 文件夹,再创建 index.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webview-react</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

创建App.tsx

import React from 'react';

function App() {
  return (
    <h2>Webview-react</h2>
  );
}
export default App;

创建index.tsx

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const root = document.getElementById('root');
if (root) {
  createRoot(root).render(<App />);
}

修改tsconfig.json配置

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2020",
    "lib": [
      "ES2020",
      "DOM",
      "DOM.Iterable"
    ],
    "sourceMap": true,
    "rootDir": "src",
    "strict": true,
    "jsx": "react-jsx", // react18 用 react-jsx,18之前的版本用 react
    "esModuleInterop": true
  },
  "exclude": ["src/test"]
}

Webpack依赖安装及配置

依赖安装:

pnpm i webpack webpack-cli -D

pnpm i babel-loader @babel/core @babel/preset-react @babel/preset-typescript -D

pnpm i html-webpack-plugin -D

pnpm i webpack-dev-server webpack-merge -D

插件项目创建的时候有一个 webpack.config.js,不过我们不去修改和使用它,另起一些配置文件。

webpack.base.js 公共配置:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: path.join(__dirname, './src/view/index.tsx'), // 入口文件
  output: {
    filename: 'static/js/[name].js',
    path: path.join(__dirname, './dist'),
    clean: true,
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-react',
              '@babel/preset-typescript'
            ]
          }
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.tsx', '.ts'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, './src/view/index.html'),
      inject: true, // 自动注入静态资源
    }),
  ],
};

webpack.dev.js 开发环境配置:

const path = require('path');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base.js');

module.exports = merge(baseConfig, {
  mode: 'development',
  devtool: 'source-map',
  devServer: {
    port: 3000,
    compress: false,
    hot: true,
    historyApiFallback: true,
    static: {
      directory: path.join(__dirname, './src/view/public'),
    }
  },
});

webpack.prod.js 生成环境配置:

const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base.js');
module.exports = merge(baseConfig, {
  mode: 'production',
});

extension.ts

import * as vscode from 'vscode';
import path from 'node:path';

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "my-vscode-extendsion" is now active!');

  let disposable = vscode.commands.registerCommand('my-vscode-extendsion.helloWorld', () => {
    vscode.window.showInformationMessage('Hello World from my-vscode-extendsion!');

    const panel = vscode.window.createWebviewPanel(
      'React',
      'React App',
      vscode.ViewColumn.One,
      {
        retainContextWhenHidden: true, // 保证 Webview 所在页面进入后台时不被释放
        enableScripts: true, // 运行 JS 执行
      }
    );

    const isProduction = context.extensionMode === vscode.ExtensionMode.Production;
    let srcUrl = '';
    if (isProduction) {
      const filePath = vscode.Uri.file(
        path.join(context.extensionPath, 'dist', 'static/js/main.js')
      );
      srcUrl = panel.webview.asWebviewUri(filePath).toString();
    } else {
      srcUrl = 'http://localhost:3000/static/js/main.js';
    }
    panel.webview.html = getWebviewContent(srcUrl);

    const updateWebview = () => {
      panel.webview.html = getWebviewContent(srcUrl);
    };
    updateWebview();
    const interval = setInterval(updateWebview, 1000);

    panel.onDidDispose(
      () => {
        clearInterval(interval);
      },
      null,
      context.subscriptions,
    );
  });
  

  context.subscriptions.push(disposable);
}

export function deactivate() { }

function getWebviewContent(srcUri: string) {
  return `<!doctype html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>webview-react</title>
    <script defer="defer" src="${srcUri}"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
  </html>`;
}

package.json 配置

"scripts": {
    "vscode:prepublish": "pnpm run build && pnpm run package",
    // ...
    "dev": "webpack-dev-server -c webpack.dev.js",
    "build": "webpack -c webpack.prod.js"
},

开发的时候 pnpm run dev 运行 webpack dev server,然后 F5 即可。

运行效果如下图所示:

VSCode 插件开发-从零到WebView+React

打包

首先是打包,插件打包成 vsix 格式。

安装 vsce

pnpm i vsce -g

打包生成 vsix 文件:

# pnpm 务必加 --no-dependencies
vsce package --no-dependencies

另外,打包之后最好配置一下 README.mdpackage.json 中的 repository 字段,以及一个 LICENSE 文件。

打包成功之后就可以直接安装使用了,安装方法如下图所示:

VSCode 插件开发-从零到WebView+React