手摸手开发一款vscode 代码提示插件 - 进阶 Webviews 加载本地资源

213 阅读3分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

书接上文

Webviews

webview API为开发者提供了完全自定义视图的能力,用来支撑原生vscode api 不能支撑的场景,可以把 Webview 当成特殊的一种iframe,具有渲染全部html的能力,他通过消息机制与插件通信,提供了高可定制型和潜力

我应该用吗?

webview很nice,但是计算机世界没有银弹,webview,十分占用资源,不要滥用webview,要多加思考,慎重慎重

API

为了整这个webView 的api,咱们继续在原来的项目开发,打开一个页面静态页面,从命令行调试 z-ui.start

{
	"name": "z-ui",
	"displayName": "",
	"description": "",
	"version": "0.0.1",
	"engines": {
		"vscode": "^1.64.0"
	},
	"categories": [
		"Other"
	],
	"activationEvents": [
		"onCommand:z-ui.helloWorld",
		"onLanguage:vue"
	],
	"main": "./extension.js",
	"contributes": {
		"commands": [
			{
				"command": "z-ui.start",
				"title": "start 页面",
				"category": "z-ui"
			}
		],
		"snippets": [
			{
				"language": "vue-html",
				"path": "./snippets/ui-tag.json"
			}
		]
	},
	"scripts": {
		"lint": "eslint .",
		"pretest": "yarn run lint",
		"test": "node ./test/runTest.js"
	},
	"devDependencies": {
		"@types/vscode": "^1.64.0",
		"@types/glob": "^7.2.0",
		"@types/mocha": "^9.0.0",
		"@types/node": "14.x",
		"eslint": "^8.6.0",
		"glob": "^7.2.0",
		"mocha": "^9.1.3",
		"typescript": "^4.5.4",
		"@vscode/test-electron": "^2.0.3"
	}
}

实现catCoding.start命令

// @ts-nocheck
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
const utils = require('./src/utils')
const attributes = require('./src/attributes')
const tags = require('./src/tags')
const fs = require('fs')

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
//获取静态文件
function getWebview() {
    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.0">
		<title>Document</title>
	</head>
	<body>
		<img src="https://ftp.bmp.ovh/imgs/2022/02/8de1be3916ac782a.jpeg" />
	</body>
	</html>`
}
/**
 * @description:激活事件
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
	function provideCompletionItemsAttrValue(document, position, token, context) {
		try {
			const tag = utils.getTag(document, position)
			const attr = utils.getPreAttr(document, position)
			const { options: single } = attributes[attr] || {}
			
			if (single) {
				return single.map(dep => {
					// vscode.CompletionItemKind 表示提示的类型
					return new vscode.CompletionItem(dep, vscode.CompletionItemKind.Text);
				})
			}

			const attrKey = `${tag}/${attr}`
			const { options } = attributes[attrKey] || {}
			if (options) {
				return options.map(dep => {
					return new vscode.CompletionItem(dep, vscode.CompletionItemKind.Text);
				})
			}
		} catch (error) {
			console.log(error)
		}
	}
	function provideCompletionItemsAttr(document, position, token, context) {
		try {
			const tag = utils.getTag(document, position)
			const { attributes } = tags[tag] || {}
			if (attributes) {
				return attributes.map(dep => {
					return new vscode.CompletionItem(dep, 14);
				})
			}
		} catch (error) {
			console.log(error)
		}
	}

	let completionAttrValue = vscode.languages.registerCompletionItemProvider(['vue'], { provideCompletionItems: provideCompletionItemsAttrValue }, '', ':', '<', '"', "'", '/', '@', '(', '>', '{');
	let completionAttr = vscode.languages.registerCompletionItemProvider(['vue'], { provideCompletionItems: provideCompletionItemsAttr }, ' ',);
	
	context.subscriptions.push(vscode.commands.registerCommand('z-ui.start', () => {
        // 创建并显示新的webview
        const panel = vscode.window.createWebviewPanel(
            'catCoding', // 只供内部使用,这个webview的标识
            "Cat Coding", // 给用户显示的面板标题
            vscode.ViewColumn.One, // 给新的webview面板一个编辑器视图
			{
				enableScripts: true, // 启用JS,默认禁用
				retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
			}
        );
		// 设置HTML内容
        panel.webview.html = getWebview();
        panel.onDidDispose(() => {
            // 当面板关闭时,触发一些操作
        }, null, context.subscriptions)
    }));
	
	context.subscriptions.push(completionAttrValue);
	context.subscriptions.push(completionAttr);
}

// this method is called when your extension is deactivated
function deactivate() { }

module.exports = {
	activate,
	deactivate
}

展示

image.png

生命周期

webview从属于创建他们的插件,因为webview是一个文件视图,用户关闭了,资源就释放了.

  • onDidDispose 事件在webview被销毁时触发,我们在这个事件结束之后更新并释放webview资源。

移动和可见性

.visible属性告诉你当前webview面板是否是可见的。

	let currentPanel;
	context.subscriptions.push(vscode.commands.registerCommand('z-ui.start', () => {
        // 创建并显示新的webview
		const columnToShowIn = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
		if(currentPanel){
			currentPanel.reveal(columnToShowIn);
		}else{
			 currentPanel = vscode.window.createWebviewPanel(
				'start', // 只供内部使用,这个webview的标识
				"start", // 给用户显示的面板标题
				vscode.ViewColumn.One, // 给新的webview面板一个编辑器视图
				{
					enableScripts: true, // 启用JS,默认禁用
					retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
				}
			);
			// 设置HTML内容
			currentPanel.webview.html = getWebview();
		}

    }));

读取本地资源

webview运行的换进跟vscode 是隔离开的,要是使用资源要使用特殊协议 vscode-resource:协议,使用localResourceRoots 显示资源目录

			 currentPanel = vscode.window.createWebviewPanel(
				'start', // 只供内部使用,这个webview的标识
				"start", // 给用户显示的面板标题
				vscode.ViewColumn.One, // 给新的webview面板一个编辑器视图
				{
					enableScripts: true, // 启用JS,默认禁用
					retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
					localResourceRoots: [
						vscode.Uri.file(path.join(extensionPath, ''))
					]
				}
			);
			const diskPath = vscode.Uri.file(path.join(context.extensionPath, "/resources/img/WX20220214-213950.png"));
			const imgSrc = diskPath.with({ scheme: 'vscode-resource' }).toString();
			// 设置HTML内容
			currentPanel.webview.html = getWebview(imgSrc);