VSCode外掛開發全攻略(七)WebView
更多文章請戳VSCode外掛開發全攻略系列目錄導航。
什麼是Webview
大家都知道,整個VSCode編輯器就是一張大的網頁,其實,我們還可以在Visual Studio Code
中建立完全自定義的、可以間接和nodejs
通訊的特殊網頁(通過一個acquireVsCodeApi
特殊方法),這個網頁就叫WebView
。內建的Markdown
的預覽就是使用WebView
實現的。使用Webview
可以構建複雜的、支援本地檔案操作的使用者介面。
VSCode外掛的WebView類似於iframe的實現,但並不是真正的iframe(我猜底層應該還是基於iframe實現的,只不過上層包裝了一層),通過開發者工具可以看到:
demo
在我們的vscode-plugin-demo中,我寫了一個非常簡單、沒啥實際意義的Webview
示例僅供參考,在任意編輯器右鍵可以看到開啟Webview
的選單:
什麼時候適合使用WebView
雖然Webview令人很振奮,因為基於它我們可以隨意發揮不受限制,但必須注意還是要慎用,畢竟VSCode是很注重效能的,不能因為你一個外掛拖累了整個IDE,一般僅在原有API和功能以及互動方式無法滿足你時才需要考慮,另外,設計糟糕的Webview也很容易在VS Code
中讓人感覺不舒適,不能讓人家一看就覺得你這是一張網頁,好看的UI也很重要。
這是官網給出的建議,在使用webview之前請考慮以下事項:
- 這個功能真的需要放在
VSCode
中嗎?作為單獨的應用程式或網站會不會更好呢? - webview是實現這個功能的唯一方法嗎?可以使用常規VS Code API嗎?
- 您的webview是否會帶來足夠的使用者價值以證明其高資源成本?
正式開始WebView之旅
建立WebView
context.subscriptions.push(vscode.commands.registerCommand(`extension.demo.openWebview`, function (uri) {
// 建立webview
const panel = vscode.window.createWebviewPanel(
`testWebview`, // viewType
"WebView演示", // 檢視標題
vscode.ViewColumn.One, // 顯示在編輯器的哪個部位
{
enableScripts: true, // 啟用JS,預設禁用
retainContextWhenHidden: true, // webview被隱藏時保持狀態,避免被重置
}
);
panel.webview.html = `<html><body>你好,我是Webview</body></html>`
幾點說明:
- 預設情況下,在Web檢視中禁用
JavaScript
,但可以通過傳入enableScripts: true
選項輕鬆啟用; - 預設情況下當webview被隱藏時資源會被銷燬,通過
retainContextWhenHidden: true
會一直儲存,但會佔用較大記憶體開銷,僅在需要時開啟;
載入本地資源
出於安全考慮,Webview預設無法直接訪問本地資源,它在一個孤立的上下文中執行,想要載入本地圖片、js、css等必須通過特殊的vscode-resource:
協議,網頁裡面所有的靜態資源都要轉換成這種格式,否則無法被正常載入。
vscode-resource:
協議類似於file:
協議,但它只允許訪問特定的本地檔案。和file:
一樣,vscode-resource:
從磁碟載入絕對路徑的資源。
我簡單封裝了一個轉換方法:
/**
* 獲取某個擴充套件檔案相對於webview需要的一種特殊路徑格式
* 形如:vscode-resource:/Users/toonces/projects/vscode-cat-coding/media/cat.gif
* @param context 上下文
* @param relativePath 擴充套件中某個檔案相對於根目錄的路徑,如 images/test.jpg
*/
getExtensionFileVscodeResource: function(context, relativePath) {
const diskPath = vscode.Uri.file(path.join(context.extensionPath, relativePath));
return diskPath.with({ scheme: `vscode-resource` }).toString();
}
預設情況下,vscode-resource:
只能訪問以下位置中的資源:
- 擴充套件程式安裝目錄中的檔案。
- 使用者當前活動的工作區內。
- 當然,你還可以使用
dataURI
直接在Webview中嵌入資源,這種方式沒有限制;
從檔案載入HTML內容
預設不支援從檔案載入HTML,需要自己封裝程式碼,我簡單封裝了一個供大家參考:
/**
* 從某個HTML檔案讀取能被Webview載入的HTML內容
* @param {*} context 上下文
* @param {*} templatePath 相對於外掛根目錄的html檔案相對路徑
*/
function getWebViewContent(context, templatePath) {
const resourcePath = path.join(context.extensionPath, templatePath);
const dirPath = path.dirname(resourcePath);
let html = fs.readFileSync(resourcePath, `utf-8`);
// vscode不支援直接載入本地資源,需要替換成其專有路徑格式,這裡只是簡單的將樣式和JS的路徑替換
html = html.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => {
return $1 + vscode.Uri.file(path.resolve(dirPath, $2)).with({ scheme: `vscode-resource` }).toString() + `"`;
});
return html;
}
執行這段程式碼之後,會自動將HTML檔案中link
、href
、script
、img
的資源相對路徑全部替換成正確的vscode-resource:
絕對路徑,例如:
../../lib/vue-2.5.17/vue.js
變成
vscode-resource:/Users/test/workspace/vscode-plugin-demo/lib/vue-2.5.17/vue.js
使用方法如下:
panel.webview.html = getWebViewContent(context, `src/view/test-webview.html`);
訊息通訊
重頭戲來了,Webview
和普通網頁非常類似,不能直接呼叫任何VSCode
API,但是,它唯一特別之處就在於多了一個名叫acquireVsCodeApi
的方法,執行這個方法會返回一個超級閹割版的vscode
物件,這個物件裡面有且僅有如下3個可以和外掛通訊的API:
外掛和Webview
之間如何互相通訊呢?
外掛給Webview
傳送訊息(支援傳送任意可以被JSON
化的資料):
panel.webview.postMessage({text: `你好,我是小茗同學!`});
Webview
端接收:
window.addEventListener(`message`, event => {
const message = event.data;
console.log(`Webview接收到的訊息:`, message);
}
Webview
主動傳送訊息給外掛:
vscode.postMessage({text: `你好,我是Webview啊!`});
外掛接收:
panel.webview.onDidReceiveMessage(message => {
console.log(`外掛收到的訊息:`, message);
}, undefined, context.subscriptions);
簡單通訊封裝
為了雙方通訊方便,我把它們簡單封裝了一下,僅供參考,Webview端:
const callbacks = {}; // 存放所有的回撥函式
/**
* 呼叫vscode原生api
* @param data 可以是類似 {cmd: `xxx`, param1: `xxx`},也可以直接是 cmd 字串
* @param cb 可選的回撥函式
*/
function callVscode(data, cb) {
if (typeof data === `string`) {
data = { cmd: data };
}
if (cb) {
// 時間戳加上5位隨機數
const cbid = Date.now() + `` + Math.round(Math.random() * 100000);
// 將回撥函式分配一個隨機cbid然後存起來,後續需要執行的時候再撈起來
callbacks[cbid] = cb;
data.cbid = cbid;
}
vscode.postMessage(data);
}
window.addEventListener(`message`, event => {
const message = event.data;
switch (message.cmd) {
// 來自vscode的回撥
case `vscodeCallback`:
console.log(message.data);
(callbacks[message.cbid] || function () { })(message.data);
delete callbacks[message.cbid]; // 執行完回撥刪除
break;
default: break;
}
});
外掛端:
let global = { projectPath, panel};
panel.webview.onDidReceiveMessage(message => {
if (messageHandler[message.cmd]) {
// cmd表示要執行的方法名稱
messageHandler[message.cmd](global, message);
} else {
util.showError(`未找到名為 ${message.cmd} 的方法!`);
}
}, undefined, context.subscriptions);
/**
* 存放所有訊息回撥函式,根據 message.cmd 來決定呼叫哪個方法,
* 想呼叫什麼方法,就在這裡寫一個和cmd同名的方法實現即可
*/
const messageHandler = {
// 彈出提示
alert(global, message) {
util.showInfo(message.info);
},
// 顯示錯誤提示
error(global, message) {
util.showError(message.info);
},
// 回撥示例:獲取工程名
getProjectName(global, message) {
invokeCallback(global.panel, message, util.getProjectName(global.projectPath));
}
}
/**
* 執行回撥函式
* @param {*} panel
* @param {*} message
* @param {*} resp
*/
function invokeCallback(panel, message, resp) {
console.log(`回撥訊息:`, resp);
// 錯誤碼在400-600之間的,預設彈出錯誤提示
if (typeof resp == `object` && resp.code && resp.code >= 400 && resp.code < 600) {
util.showError(resp.message || `發生未知錯誤!`);
}
panel.webview.postMessage({cmd: `vscodeCallback`, cbid: message.cbid, data: resp});
}
按上述方法封裝之後,例如,Webview端想要執行名為openFileInVscode
命令只需要這樣:
callVscode({cmd: `openFileInVscode`, path: `package.json`}, (message) => {
this.alert(message);
});
然後在外掛端的messageHandler
實現openFileInVscode
方法即可,其它都不用管:
const messageHandler = {
// 省略其它方法
openFileInVscode(global, message) {
util.openFileInVscode(`${global.projectPath}/${message.path}`);
invokeCallback(global.panel, message, `開啟檔案成功!`);
}
};
以上封裝的比較隨便,只是給大家提供一個思路,有時間可以好好封裝一下。
主題適配
Webview
可以根據VS Code
的當前主題更改其外觀,原理是body上面新增當前主題名稱,主要有以下三種:
-
vscode-light
– 淺色主題; -
vscode-dark
-深色主題; -
vscode-high-contrast
– 高對比度主題;
所以我們可以通過自己寫樣式來適配不同主題:
/* 淺色主題 */
.body.vscode-light {
background: white;
color: black;
}
/* 深色主題 */
body.vscode-dark {
background: #252526;
color: white;
}
/* 高對比度主題 */
body.vscode-high-contrast {
background: white;
color: red;
}
深色主題效果:
生命週期
webview
由建立它的擴充套件程式所有,返回的panel
物件你必須自己儲存,如果你的擴充套件程式丟失了這個引用,那麼將無法再次重新訪問該webview
,即使Web檢視繼續顯示在vscode
中。
使用者也可以隨時關閉webview
皮膚。當使用者關閉webview皮膚時,webview本身將被銷燬,此時不能再使用panel引用,否則將會出現異常,可以通過監聽onDidDispose
事件在這裡面做一些銷燬操作。
可以通過panel.dispose()
方法主動關閉webview。
狀態保持
當webview移動到後臺又再次顯示時,webview中的任何狀態都將丟失。
解決此問題的最佳方法是使你的webview無狀態,通過訊息傳遞來儲存webview的狀態。
state
在webview的js中我們可以使用vscode.getState()
和vscode.setState()
方法來儲存和恢復JSON可序列化狀態物件。當webview被隱藏時,即使webview內容本身被破壞,這些狀態仍然會儲存。當然了,當webview被銷燬時,狀態將被銷燬。
序列化
通過註冊WebviewPanelSerializer
可以實現在VScode
重啟後自動恢復你的webview
,當然,序列化其實也是建立在getState
和setState
之上的。
註冊方法:vscode.window.registerWebviewPanelSerializer
retainContextWhenHidden
對於具有非常複雜的UI或狀態且無法快速儲存和恢復的webview
,我們可以直接使用retainContextWhenHidden
選項。設定retainContextWhenHidden: true
後即使webview被隱藏到後臺其狀態也不會丟失。
儘管retainContextWhenHidden
很有吸引力,但它需要很高的記憶體開銷,一般建議在實在沒辦法的時候才啟用。getState
和setState
是持久化的首選方式,因為它們的效能開銷要比retainContextWhenHidden
低得多。
除錯
注意,要除錯Webview不能直接把VSCode的開發者工具開啟,直接開啟就會和我們最前面的截圖看到的那樣,你只能看到一個<webview></webview>
標籤,看不到程式碼,要看程式碼需要按下Ctrl+Shift+P
然後執行開啟Webview開發工具
,英文版應該是 Open Webview Developer Tools
:
審查Webview:
這個時候需要特別注意錯誤日誌出現的位置,如果是Webview的錯誤,一般列印在前面說的這個開發者工具,但如果是外掛端的錯誤只會列印在整個VSCode的開發者工具裡。
糟糕,距離最開始接觸Webview
已經有一段時間了,本來有挺多想寫的,但是現在居然沒靈感了,額……坑爹啊
參考連結
https://code.visualstudio.com/docs/extensions/webview
相關文章
- VSCode外掛開發全攻略(二)HelloWordVSCode
- VSCode WebView外掛(擴充套件)開發實戰VSCodeWebView套件
- VSCode外掛開發全攻略(六)開發除錯技巧VSCode除錯
- VSCode外掛開發全攻略(九)常用API總結VSCodeAPI
- VSCode外掛開發全攻略(十)打包、釋出、升級VSCode
- VScode股票外掛開發VSCode
- HeyUI元件庫釋出vscode外掛,PS教程: 如何開發vscode外掛?UI元件VSCode
- Python開發工具:VSCode+外掛PythonVSCode
- 01-前端開發Vscode外掛配置前端VSCode
- VSCode 遠端開發外掛快速使用VSCode
- 前端開發值得擁有的 VSCode 外掛前端VSCode
- 新能力|雲開發 VSCode 外掛 Cloudbase ToolkitVSCodeCloud
- VScode外掛VSCode
- 27 個提升開發幸福度的 VsCode 外掛VSCode
- vscode外掛開發實踐與demo原始碼VSCode原始碼
- VSCode 必裝的 10 個高效開發外掛VSCode
- vscode常用外掛VSCode
- vscode外掛分享VSCode
- vscode外掛整理VSCode
- vscode外掛開發--快速插入圖片相關cssVSCodeCSS
- 雲開發 VSCode 外掛 Cloudbase Toolkit 的正確開啟方式VSCodeCloud
- VScode 好用的外掛VSCode
- VSCode 外掛測試VSCode
- VSCODE 外掛推薦VSCode
- vscode常用python外掛VSCodePython
- 使用 VSCode Remote 外掛VSCodeREM
- vscode 外掛配置指北VSCode
- VScode外掛推薦VSCode
- VSCode外掛之BeautifyVSCode
- vscode外掛使用包VSCode
- 從零到一開發vscode外掛變數翻譯VSCode變數
- 前端小糾結--提高開發效率VSCode外掛推薦前端VSCode
- 能讓你開發效率翻倍的 VSCode 外掛配置(中)VSCode
- 能讓你開發效率翻倍的 VSCode 外掛配置(上)VSCode
- 【10月精彩回顧】Github 支援腳註,Chrome外掛開發全攻略GithubChrome
- 從0到1開發一款自己的vscode外掛VSCode
- 每個開發者都應該使用的VSCode外掛 - tvkoushikVSCode
- Flutter 外掛 webview_flutter 使用指北FlutterWebView