lit-and-vscode-extensions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLit and VSCode Extensions
Lit与VSCode扩展开发
In this article I will go over how to set up a Lit web component and use it to create a VSCode extension.
TLDR You can find the final source here.
本文将介绍如何搭建Lit Web组件并使用它创建VSCode扩展。
太长不看 你可以在这里找到最终源码:链接。
Prerequisites
前置条件
- Vscode
- Node >= 16
- Typescript
- VSCode
- Node >= 16
- Typescript
Getting Started
开始上手
We can start off by navigating in terminal to the location of the project and run the following:
npm init @vitejs/app --template lit-tsThen enter a project name and now open the project in vscode and install the dependencies:
lit-vscode-extensioncd lit-vscode-extension
npm i -D @types/node
code .Update the with the following:
vite.config.tsimport { defineConfig } from "vite";
import { resolve } from "path";
export default defineConfig({
base: "/lit-vscode-extension/",
build: {
outDir: "build",
rollupOptions: {
external: /^vscode/,
input: {
main: resolve(__dirname, "index.html"),
},
output: {
entryFileNames: "[name].js",
},
},
},
});Open and update it with the following:
package.json{
"name": "lit-vscode-extension",
"description": "Lit VSCode Extension Example",
"version": "0.0.1",
"publisher": "rodydavis",
"private": true,
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/rodydavis/lit-vscode-extension"
},
"engines": {
"vscode": "^1.47.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:lit.start",
"onCommand:lit.reset",
"onWebviewPanel:lit"
],
"main": "./build/extension.js",
"contributes": {
"commands": [
{
"command": "lit.start",
"title": "Open Plugin",
"category": "lit"
},
{
"command": "lit.reset",
"title": "Reset",
"category": "lit"
}
]
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"ext": "tsc src/extension.ts --outdir build --skipLibCheck --module commonjs",
"compile": "npm run build && npm run ext",
"serve": "vite preview"
},
"dependencies": {
"lit": "^2.0.0-rc.2"
},
"devDependencies": {
"@types/node": "^15.12.4",
"@types/vscode": "^1.57.0",
"typescript": "^4.2.3",
"vite": "^2.3.5"
}
}After the is updated run .
package.jsonnpm i我们可以先在终端中导航到项目所在位置,然后运行以下命令:
npm init @vitejs/app --template lit-ts然后输入项目名称,现在在VSCode中打开项目并安装依赖:
lit-vscode-extensioncd lit-vscode-extension
npm i -D @types/node
code .更新文件内容如下:
vite.config.tsimport { defineConfig } from "vite";
import { resolve } from "path";
export default defineConfig({
base: "/lit-vscode-extension/",
build: {
outDir: "build",
rollupOptions: {
external: /^vscode/,
input: {
main: resolve(__dirname, "index.html"),
},
output: {
entryFileNames: "[name].js",
},
},
},
});打开文件并更新内容如下:
package.json{
"name": "lit-vscode-extension",
"description": "Lit VSCode Extension Example",
"version": "0.0.1",
"publisher": "rodydavis",
"private": true,
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/rodydavis/lit-vscode-extension"
},
"engines": {
"vscode": "^1.47.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:lit.start",
"onCommand:lit.reset",
"onWebviewPanel:lit"
],
"main": "./build/extension.js",
"contributes": {
"commands": [
{
"command": "lit.start",
"title": "Open Plugin",
"category": "lit"
},
{
"command": "lit.reset",
"title": "Reset",
"category": "lit"
}
]
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"ext": "tsc src/extension.ts --outdir build --skipLibCheck --module commonjs",
"compile": "npm run build && npm run ext",
"serve": "vite preview"
},
"dependencies": {
"lit": "^2.0.0-rc.2"
},
"devDependencies": {
"@types/node": "^15.12.4",
"@types/vscode": "^1.57.0",
"typescript": "^4.2.3",
"vite": "^2.3.5"
}
}更新完后运行安装依赖。
package.jsonnpm iTemplate
模板配置
Open up the and update it with the following:
index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lit Example</title>
<script type="module" src="/src/my-element.ts"></script>
</head>
<body>
<my-element>
<p>This is child content</p>
</my-element>
</body>
</html>打开文件并更新内容如下:
index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lit Example</title>
<script type="module" src="/src/my-element.ts"></script>
</head>
<body>
<my-element>
<p>This is child content</p>
</my-element>
</body>
</html>Web Component
Web组件开发
Open up and update it with the following:
src/my-element.tsimport { html, css, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@customElement("my-element")
export class MyElement extends LitElement {
static styles = css`
:host {
display: block;
border: solid 1px gray;
padding: 16px;
max-width: 800px;
}
`;
@property() name = "World";
@state() count = 0;
render() {
return html`
<h1>Hello, ${this.name}!</h1>
<button @click=${() => this.modify(1)} part="button">
Click Count: ${this.count}
</button>
<slot></slot>
`;
}
modify(val: number) {
this.count += val;
}
reset() {
this.count = 0;
}
async firstUpdated() {
window.addEventListener(
"message",
(e: any) => {
const message = e.data;
const { command } = message;
if (command === "reset") {
this.reset();
}
},
false
);
}
}Here we are just modifying the example to include a message listener for communicating with the vscode extension, methods for updating the count, and updating the render method.
VSCode communicates with the plugin via post messages because the UI will be loaded in an .
iframeNow let's write the extension code in . First start by adding top level declarations that will be referenced multiple times.
src/extension.tsimport * as vscode from "vscode";
const WEB_DIR: string = "build";
const WEB_SCRIPT: string = "main.js";
const TITLE: string = "Lit Example";
const TAG: string = "my-element";
const possible = [
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz",
"0123456789",
].join("");
function getNonce() {
let text = "";
for (let i = 0; i < 32; i++) {
const char = possible.charAt(Math.floor(Math.random() * possible.length));
text += char;
}
return text;
}We also are creating a method that will be used for making sure the scripts we load are the ones we passed in.
getNonceNow create a that will contain the ui for our plugin:
Panelclass Panel {
public static currentPanel: Panel | undefined;
public static readonly viewType = "litExample";
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionUri: vscode.Uri) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (Panel.currentPanel) {
Panel.currentPanel.panel.reveal(column);
return;
}
const panel = vscode.window.createWebviewPanel(
Panel.viewType,
TITLE,
column || vscode.ViewColumn.One,
getWebviewOptions(extensionUri)
);
Panel.currentPanel = new Panel(panel, extensionUri);
}
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
Panel.currentPanel = new Panel(panel, extensionUri);
}
private constructor(
public readonly panel: vscode.WebviewPanel,
public readonly extensionUri: vscode.Uri
) {
this._update();
this.panel.onDidDispose(() => this.dispose(), null, this._disposables);
this.panel.onDidChangeViewState(
(_) => {
if (this.panel.visible) {
this._update();
}
},
null,
this._disposables
);
this.panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case "alert":
vscode.window.showErrorMessage(message.text);
return;
}
},
null,
this._disposables
);
}
public sendMessage(command: string) {
this.panel.webview.postMessage({ command: command });
}
public dispose() {
Panel.currentPanel = undefined;
this.panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _update() {
const webview = this.panel.webview;
webview.html = this._getHtmlForWebview(webview);
}
private _getHtmlForWebview(webview: vscode.Webview) {
const scriptPathOnDisk = vscode.Uri.joinPath(
this.extensionUri,
WEB_DIR,
WEB_SCRIPT
);
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
const nonce = getNonce();
const slot = "<p>This is child content</p>";
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${TITLE}</title>
</head>
<body class="vscode-light">
<${TAG} nonce="${nonce}" >
${slot}
</${TAG}>
<script nonce="${nonce}" type="module" src="${scriptUri}"></script>
</body>
</html>`;
}
}Notice how we are recreating the html and not using the. This allows us to use the component for a deployed website but also the extension with separate app logic code.index.html
Everything else is just boilerplate for managing the panel state and disposing when it is finished.
Now we can add the methods for loading our plugin and listening for the commands:
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("lit.start", () => {
Panel.createOrShow(context.extensionUri);
})
);
context.subscriptions.push(
vscode.commands.registerCommand("lit.reset", () => {
if (Panel.currentPanel) {
Panel.currentPanel.sendMessage("reset");
}
})
);
if (vscode.window.registerWebviewPanelSerializer) {
vscode.window.registerWebviewPanelSerializer(Panel.viewType, {
async deserializeWebviewPanel(
webviewPanel: vscode.WebviewPanel,
state: any
) {
console.log(`Received state: ${state}`);
webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
Panel.revive(webviewPanel, context.extensionUri);
},
});
}
}
function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
return {
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, WEB_DIR)],
};
}Here we are loading the extension and passing messages when the command. We are also returning the options which is set to so we can run the injected js.
lit.resetgetWebviewOptionsenableScriptstrueThe final code should look like the follow:
import * as vscode from "vscode";
const WEB_DIR: string = "build";
const WEB_SCRIPT: string = "main.js";
const TITLE: string = "Lit Example";
const TAG: string = "my-element";
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("lit.start", () => {
Panel.createOrShow(context.extensionUri);
})
);
context.subscriptions.push(
vscode.commands.registerCommand("lit.reset", () => {
if (Panel.currentPanel) {
Panel.currentPanel.sendMessage("reset");
}
})
);
if (vscode.window.registerWebviewPanelSerializer) {
vscode.window.registerWebviewPanelSerializer(Panel.viewType, {
async deserializeWebviewPanel(
webviewPanel: vscode.WebviewPanel,
state: any
) {
console.log(`Received state: ${state}`);
webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
Panel.revive(webviewPanel, context.extensionUri);
},
});
}
}
function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
return {
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, WEB_DIR)],
};
}
class Panel {
public static currentPanel: Panel | undefined;
public static readonly viewType = "litExample";
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionUri: vscode.Uri) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (Panel.currentPanel) {
Panel.currentPanel.panel.reveal(column);
return;
}
const panel = vscode.window.createWebviewPanel(
Panel.viewType,
TITLE,
column || vscode.ViewColumn.One,
getWebviewOptions(extensionUri)
);
Panel.currentPanel = new Panel(panel, extensionUri);
}
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
Panel.currentPanel = new Panel(panel, extensionUri);
}
private constructor(
public readonly panel: vscode.WebviewPanel,
public readonly extensionUri: vscode.Uri
) {
this._update();
this.panel.onDidDispose(() => this.dispose(), null, this._disposables);
this.panel.onDidChangeViewState(
(_) => {
if (this.panel.visible) {
this._update();
}
},
null,
this._disposables
);
this.panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case "alert":
vscode.window.showErrorMessage(message.text);
return;
}
},
null,
this._disposables
);
}
public sendMessage(command: string) {
this.panel.webview.postMessage({ command: command });
}
public dispose() {
Panel.currentPanel = undefined;
this.panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _update() {
const webview = this.panel.webview;
webview.html = this._getHtmlForWebview(webview);
}
private _getHtmlForWebview(webview: vscode.Webview) {
const scriptPathOnDisk = vscode.Uri.joinPath(
this.extensionUri,
WEB_DIR,
WEB_SCRIPT
);
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
const nonce = getNonce();
const slot = "<p>This is child content</p>";
const htmlSource = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${TITLE}</title>
</head>
<body class="vscode-light">
<${TAG} nonce="${nonce}" >
${slot}
</${TAG}>
<script nonce="${nonce}" type="module" src="${scriptUri}"></script>
</body>
</html>`;
return htmlSource;
}
}
const possible = [
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz",
"0123456789",
].join("");
function getNonce() {
let text = "";
for (let i = 0; i < 32; i++) {
const char = possible.charAt(Math.floor(Math.random() * possible.length));
text += char;
}
return text;
}打开文件并更新内容如下:
src/my-element.tsimport { html, css, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@customElement("my-element")
export class MyElement extends LitElement {
static styles = css`
:host {
display: block;
border: solid 1px gray;
padding: 16px;
max-width: 800px;
}
`;
@property() name = "World";
@state() count = 0;
render() {
return html`
<h1>Hello, ${this.name}!</h1>
<button @click=${() => this.modify(1)} part="button">
Click Count: ${this.count}
</button>
<slot></slot>
`;
}
modify(val: number) {
this.count += val;
}
reset() {
this.count = 0;
}
async firstUpdated() {
window.addEventListener(
"message",
(e: any) => {
const message = e.data;
const { command } = message;
if (command === "reset") {
this.reset();
}
},
false
);
}
}我们对示例代码进行了修改,添加了用于与VSCode扩展通信的消息监听器、更新计数的方法,并修改了渲染逻辑。
VSCode通过postMessage与插件通信,因为UI会加载在中。
iframe现在我们在中编写扩展代码。首先添加会被多次引用的顶层声明:
src/extension.tsimport * as vscode from "vscode";
const WEB_DIR: string = "build";
const WEB_SCRIPT: string = "main.js";
const TITLE: string = "Lit Example";
const TAG: string = "my-element";
const possible = [
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz",
"0123456789",
].join("");
function getNonce() {
let text = "";
for (let i = 0; i < 32; i++) {
const char = possible.charAt(Math.floor(Math.random() * possible.length));
text += char;
}
return text;
}我们还创建了方法,用于确保加载的脚本是我们指定的脚本。
getNonce现在创建一个包含插件UI的类:
Panelclass Panel {
public static currentPanel: Panel | undefined;
public static readonly viewType = "litExample";
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionUri: vscode.Uri) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (Panel.currentPanel) {
Panel.currentPanel.panel.reveal(column);
return;
}
const panel = vscode.window.createWebviewPanel(
Panel.viewType,
TITLE,
column || vscode.ViewColumn.One,
getWebviewOptions(extensionUri)
);
Panel.currentPanel = new Panel(panel, extensionUri);
}
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
Panel.currentPanel = new Panel(panel, extensionUri);
}
private constructor(
public readonly panel: vscode.WebviewPanel,
public readonly extensionUri: vscode.Uri
) {
this._update();
this.panel.onDidDispose(() => this.dispose(), null, this._disposables);
this.panel.onDidChangeViewState(
(_) => {
if (this.panel.visible) {
this._update();
}
},
null,
this._disposables
);
this.panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case "alert":
vscode.window.showErrorMessage(message.text);
return;
}
},
null,
this._disposables
);
}
public sendMessage(command: string) {
this.panel.webview.postMessage({ command: command });
}
public dispose() {
Panel.currentPanel = undefined;
this.panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _update() {
const webview = this.panel.webview;
webview.html = this._getHtmlForWebview(webview);
}
private _getHtmlForWebview(webview: vscode.Webview) {
const scriptPathOnDisk = vscode.Uri.joinPath(
this.extensionUri,
WEB_DIR,
WEB_SCRIPT
);
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
const nonce = getNonce();
const slot = "<p>This is child content</p>";
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${TITLE}</title>
</head>
<body class="vscode-light">
<${TAG} nonce="${nonce}" >
${slot}
</${TAG}>
<script nonce="${nonce}" type="module" src="${scriptUri}"></script>
</body>
</html>`;
}
}注意我们是重新创建HTML而非使用,这样可以让组件同时用于部署网站和扩展,且二者的应用逻辑相互独立。index.html
其余代码都是用于管理面板状态和在结束时清理资源的模板代码。
现在我们添加加载插件和监听命令的方法:
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("lit.start", () => {
Panel.createOrShow(context.extensionUri);
})
);
context.subscriptions.push(
vscode.commands.registerCommand("lit.reset", () => {
if (Panel.currentPanel) {
Panel.currentPanel.sendMessage("reset");
}
})
);
if (vscode.window.registerWebviewPanelSerializer) {
vscode.window.registerWebviewPanelSerializer(Panel.viewType, {
async deserializeWebviewPanel(
webviewPanel: vscode.WebviewPanel,
state: any
) {
console.log(`Received state: ${state}`);
webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
Panel.revive(webviewPanel, context.extensionUri);
},
});
}
}
function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
return {
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, WEB_DIR)],
};
}这里我们加载扩展并在触发命令时发送消息。我们还返回了配置,其中设为,这样我们就能运行注入的JS代码。
lit.resetgetWebviewOptionsenableScriptstrue最终代码如下:
import * as vscode from "vscode";
const WEB_DIR: string = "build";
const WEB_SCRIPT: string = "main.js";
const TITLE: string = "Lit Example";
const TAG: string = "my-element";
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("lit.start", () => {
Panel.createOrShow(context.extensionUri);
})
);
context.subscriptions.push(
vscode.commands.registerCommand("lit.reset", () => {
if (Panel.currentPanel) {
Panel.currentPanel.sendMessage("reset");
}
})
);
if (vscode.window.registerWebviewPanelSerializer) {
vscode.window.registerWebviewPanelSerializer(Panel.viewType, {
async deserializeWebviewPanel(
webviewPanel: vscode.WebviewPanel,
state: any
) {
console.log(`Received state: ${state}`);
webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
Panel.revive(webviewPanel, context.extensionUri);
},
});
}
}
function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
return {
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, WEB_DIR)],
};
}
class Panel {
public static currentPanel: Panel | undefined;
public static readonly viewType = "litExample";
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionUri: vscode.Uri) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (Panel.currentPanel) {
Panel.currentPanel.panel.reveal(column);
return;
}
const panel = vscode.window.createWebviewPanel(
Panel.viewType,
TITLE,
column || vscode.ViewColumn.One,
getWebviewOptions(extensionUri)
);
Panel.currentPanel = new Panel(panel, extensionUri);
}
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
Panel.currentPanel = new Panel(panel, extensionUri);
}
private constructor(
public readonly panel: vscode.WebviewPanel,
public readonly extensionUri: vscode.Uri
) {
this._update();
this.panel.onDidDispose(() => this.dispose(), null, this._disposables);
this.panel.onDidChangeViewState(
(_) => {
if (this.panel.visible) {
this._update();
}
},
null,
this._disposables
);
this.panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case "alert":
vscode.window.showErrorMessage(message.text);
return;
}
},
null,
this._disposables
);
}
public sendMessage(command: string) {
this.panel.webview.postMessage({ command: command });
}
public dispose() {
Panel.currentPanel = undefined;
this.panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _update() {
const webview = this.panel.webview;
webview.html = this._getHtmlForWebview(webview);
}
private _getHtmlForWebview(webview: vscode.Webview) {
const scriptPathOnDisk = vscode.Uri.joinPath(
this.extensionUri,
WEB_DIR,
WEB_SCRIPT
);
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
const nonce = getNonce();
const slot = "<p>This is child content</p>";
const htmlSource = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${TITLE}</title>
</head>
<body class="vscode-light">
<${TAG} nonce="${nonce}" >
${slot}
</${TAG}>
<script nonce="${nonce}" type="module" src="${scriptUri}"></script>
</body>
</html>`;
return htmlSource;
}
}
const possible = [
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz",
"0123456789",
].join("");
function getNonce() {
let text = "";
for (let i = 0; i < 32; i++) {
const char = possible.charAt(Math.floor(Math.random() * possible.length));
text += char;
}
return text;
}Running
运行扩展
Make sure to install the dependencies by running .
npm iTo build the extension run .
npm run compileTo open the extension and debug hit .
F5To run the commands to open the extension run or :
lit: open pluginlit: resetTo debug the extension when it is open run from the command pallet.
Developer: Open Webview Developer Tools确保通过运行安装了所有依赖。
npm i运行构建扩展。
npm run compile按下打开扩展并进行调试。
F5运行以下命令打开扩展: 或 :
lit: open pluginlit: reset扩展打开后,运行命令调试扩展:
Developer: Open Webview Developer Tools