iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
Software Development

自己用的工具自己做! 30天玩轉VS Code Extension之旅系列 第 15

Day15 | Webview API (一)

大家好,我是韋恩,今天是第十五天,我們將開始了解如何在VSCode使用Webview。

Webview元件概覽


在VSCode提供的元件裡面,Webview是個強大又危險的存在。雖然VSCode本身由Electron這個工具所打造,所有的UI元件都是通過Web頁面渲染出來的,但在我們前面練習裡使用的VSCode元件,我們全都通過VSCode提供的API和設定產生的,沒有任何訪問瀏覽器的document物件並操作樣式的機會。這是由於VSCode的團隊考量到效能跟穩定性的問題做了限制,因而在大部分元件裡,我們唯有照api提供的設定才能客製化我們的元件。

但現在,有了WebView,一切都不一樣了。VSCode允許我們在WebView內加載獨立的html頁面,在webview裡,我們可以自由的訪問document,做出任何我們想要的自訂元件與互動方式。可惜WebView強大的同時,也是伴隨著風險,使用Webview會消耗並佔用更多系統資源。另外,客製化的元件和VSCode的樣式和行為不一致時,也容易讓使用者覺得怪異或不適應。

因此VSCode團隊建議,在使用WebView前多考慮這幾點:

  1. 這個要實現的功能是否真的需要使用VSCode Extension開發,並在VSCode使用? 如果直接開發一個獨立的網站或應用程序,會不會更好?

  2. 是否有常用的VSCode API或元件可以滿足這個需求,WebView是唯一實現功能的方式嗎?

  3. 你現在實現的功能帶來的效益與價值,是否足以匹配其佔用並消耗的系統資源?

沒錯,只是因為我們可以使用WebView的功能,並不代表我們一定需要這樣做。
每種技術都有適合的場景,符合情境的技術,才是最好的。

WebView API基本介紹


好了,大致了解使用WebView前須考量的幾個點後。讓我們來了解怎麼樣操作WebView API吧!

創建WebView的API一樣是位於VSCode API的window命名空間底下,因此,和前面一樣,先讓我們create一個WebView物件出來吧!

const webviewPanel = vscode.window.createWebviewPanel(
    'webviewId',
    'WebView Title',
    vscode.ViewColumn.One,
);

現在我們已經create一個webviewPanel的物件了,我們指定了這個WebviewPanel的idtitle還有在editor內顯示的位置。讓我們看一下這個物件有哪些可以使用的API:

好的,我們看到webviewPanel下面有active等狀態屬性、onDidDispose等生命週期方法、reveal操作webviewPanel的方法,最重要的還有webview這個物件。

讓我們繼續看webview下面有什麼方法吧!

可以看到,webview物件提供了安全載入外部連結的cspSource、使用local資源(assets)的asWebviewUri以及跟webview傳接訊息的onDidReceiveMessagepostMessage,以及最後且最常用的html方法,這個方法會讓我們指定webview的html內容或重新載入webview。

Day15: WebView練習


好啦,已經簡單了解API了,讓我們來直接操作並練習使用webview吧!

專案Contribution Points命令配置:

先讓我們設定今天練習要用的Command:

{
    ...
   "contributes": {
        ...
		"commands": [
            {
				"command": "day15-webview-api.openWebview",
				"title": "Day15: Open Webview"
			}
		]
        ...
	},
    ...
}

完成後,我們在src資料夾下面提供一個webview.html,這就是我們要顯示的頁面內容:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Webview Practice</title>
</head>
<body>
 <img src="https://i.imgur.com/hzK0JDS.jpg" width="300" />
</body>
</html>
  • 創建並呈現WebView:

好的,已經設置完Webview,在實務上,我們需要讀取這個webview.html檔案,並提供給extension的webview物件,因此我們需要一段nodejs檔案讀寫的程式:


import * as path from 'path';
import * as fs from 'fs';

function loadWebView(webviewPath: string) {
	const readPath = path.resolve(__dirname, webviewPath);
	return fs.readFileSync(readPath, { encoding: 'utf-8'});
}

然後,在extension.ts裡面,我們呼叫載入webview內容的function,指派給vscode的webview物件。

import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';

export function activate(context: vscode.ExtensionContext) {

	let disposable = vscode.commands.registerCommand('day15-webview-api.openWebview', () => {
		try {
        
			const webviewPanel = vscode.window.createWebviewPanel(
				'webviewId',
				'WebView Title',
				vscode.ViewColumn.One,
			);
           
			webviewPanel.webview.html = loadWebView('./webview.html');
      
			vscode.window.showInformationMessage('Show Webview at day15-webview-api!');
		} catch (error) {
				console.log(error);
		}
	});

	context.subscriptions.push(disposable);
}

export function deactivate() {}

好的,現在按F5啟動extension,並使用Day05: Open WebView!的命令吧!

按完後,我們會失敗,到debug console看一下命令,會發現Error: ENOENT: no such file or directory, open ${開啟檔案路徑}的錯誤,因為typescript的檔案編譯後,編譯完的檔案會在另一個資料夾。我們的webview.html並不是ts檔案,不會被typescript compiler搬運到目標資料夾。

讓我們在package.json的scripts裡的compile方法,加上cp指令複製我們的webview.html到compile檔案的目標資料夾out下面

...
scripts: {
    ...
    "compile": "tsc -p ./ && cp ./src/**.html ./out",
    ...
}
...

註: 此處筆者使用mac,因此使用cp指令做檔案複製。使用windows的讀者請改為使用COPY指令進行處理,或使用gulp、webpack等工具打包。

原則上VSCode套件在打包時會直接打包compile過的檔案,因此這裡用不同平台特定的指令做compile並不會影響最後於VSCode下載套件的使用者們。如需近一步壓縮html等檔案,可再另外學習打包工具的使用,這點我們會在後面的實戰練習進行說明。

然後在terminal使用

npm run compile

可以看到compile後的out資料夾下面也已經有了webview.html檔案了。

現在按F5後,執行Day15:Open Webview的命令,我們可以順利顯示Webview頁面。

  • Webview狀態與生命週期方法:

WebviewPanel上有一個Visable屬性,當使用者打開另外一個檔案時,WebviewPanel物件並不會銷毀,而是隨之進入背景(background)狀態,此時visiableactive屬性會是false,且webview會保存之前的狀態。當使用者重新回到Webview,WebviewPanel即會回到前景(foreground),呈現之前的頁面與狀態。

透過onDidChangeViewState生命週期方法,我們可以監聽WebviewPanel的狀態變化:

const webviewPanel = createWebViewPanel(...);

webviewPanel.onDidChangeViewState(e => {
    const panel = e.webviewPanel;
    const activeState = `ActiveState: ${panel.active}`;
    const visibleState = `VisibleState: ${panel.visible}`;
    const currentViewColumn = `CurrentVisibleColumn: ${panel.viewColumn}`;
    [activeState, visibleState, currentViewColumn].forEach(msg => {
        console.log(msg);
        vscode.window.showInformationMessage(msg);
    });
    },
    /**
     * 設置listen的callback方法的this指向,此處我們使用arrow function,設為null即可。
     */
    null,
    /**
     * 傳入context.subscription讓生命週期方法在extension關閉後可以dispose釋放監聽佔用的資源
     */
    context.subscriptions
);

WebViewPanel另外提供了onDidDispose讓我們在此階段dispose過程中我們額外使用的資源。比如說我們使用了setInterval這個方法每隔2秒執行一段程式,就可以在這個階段使用clearInterval方法清除我們註冊的timer。

const timerId = setInterval(() => {
		doSomething();
}, 2000);

webviewPanel.onDidDispose(() => {
    clearInterval(timerId);
}, null, context.subscriptions);

結語


好啦,今天,我們對WebView有了初步的了解,並克服了一些實務上的困難,開啟了一個基本的WebView。

明天會繼續VSCode Webview元件的介紹與實際練習,我們明天見,謝謝大家。

本日參考文件



上一篇
Day14 | OutputChannel,輸出Extension的訊息
下一篇
Day16 | WebView API (二)
系列文
自己用的工具自己做! 30天玩轉VS Code Extension之旅36

尚未有邦友留言

立即登入留言