서두에
Webview 는 새로운 문을 열었습니다:
The webview API allows extensions to create fully customizable views within Visual Studio Code. Webviews can also be used to build complex user interfaces beyond what VS Code's native APIs support.
VS Code 플러그인이 HTML 을 렌더링하여 복잡한 UI 를 생성할 수 있게 하였으며, API 가 지원하는 것에 한정되지 않습니다. 이러한 유연성으로 플러그인에 더 많은 가능성이 생겼습니다:
This freedom makes webviews incredibly powerful, and opens up a whole new range of extension possibilities.
일.vscode.previewHtml 명령
초기에는 vscode.previewHtml 명령을 사용하여 HTML 콘텐츠를 렌더링했습니다:
// Render the html of the resource in an editor view.
vscode.commands.executeCommand(
'vscode.previewHtml',
uri,
viewColumn,
title
);
본질적으로iframe 입니다 (자세한 내용은 html preview part and command 참조). 내장 Markdown 미리보기 등의 기능을 지원하기 위해 사용되었습니다
이후보안 및 호환성 측면에서 문제가 발생했습니다:
However the vscode.previewHtml command suffered from some important security and compatibility issues that we determined could not be fixed without breaking existing users of the command.
이에 Webview API 로 대체하게 되었습니다:
The webview API is significantly easier to work with, correctly supports different filesystem setups, and webviews also offer many security benefits over htmlPreviews.
이.Webview API
previewHtml 과 비교하여, Webview 는 더 안전하지만 리소스 소비도 더 큽니다:
Webviews are resource heavy and run in a separate context from normal extensions.
실행 환경은 Electron 의 네이티브 Webview 태그 입니다. iframe 과 비교하여, 가장 큰 차이점은 Webview 가 독립된 프로세스에서 실행되며 보안 격리성이 더 강하다는 것입니다:
Unlike an iframe, the webview runs in a separate process than your app. It doesn't have the same permissions as your web page and all interactions between your app and embedded content will be asynchronous. This keeps your app safe from the embedded content.
한편, Webview 사용에는 성능 부담이 있으므로, 공식은再三에 걸쳐 "술고막용"을 강조하고 있습니다:
Webviews are pretty amazing, but they should also be used sparingly and only when VS Code's native API is inadequate.
또한 Webview 를 사용하기 전에 3 가지를 고려할 것을 권장합니다:
-
이 기능은 정말 VS Code 내에 배치해야 합니까? 독립적인 애플리케이션 또는 웹사이트로 만드는 것이 더 적합하지 않습니까?
-
Webview 는 목표 기능을 구현하는 유일한 방법입니까? 일반 플러그인 API 로 대체할 수 없습니까?
-
창조할 수 있는 사용자 가치가 Webview 가 소비하는 리소스에 합당합니까?
삼.구체적인 사용법
구체적으로는, vscode.window.createWebviewPanel 을 사용하여 Webview 를 생성합니다:
// 1. 생성하여 Webview 표시
const panel = vscode.window.createWebviewPanel(
// 該 webview 의 식별자, 임의의 문자열
'catCoding',
// webview 패널의 제목, 사용자에게 표시됨
'Cat Coding',
// webview 패널이 소재한 컬럼
vscode.ViewColumn.One,
// 기타 webview 옵션
{}
);
P.S. Webview 패널은 생성 후, webview.title 을 사용하여 탭 페이지 제목을 수정할 수 있습니다
다음으로 webview.html 을 사용하여 Webview 내에서 렌더링할 HTML 콘텐츠를 설정합니다:
// 2. webview 가 렌더링할 HTML 콘텐츠 설정
panel.webview.html = `<!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.previewHtml 과 유사하게, 지정된 HTML 콘텐츠는 최종적으로iframe 을 통해 로드됩니다. 다만, 이iframe 은 Webview 에 의해 렌더링됩니다. 따라서, 이전의 방식과 비교하여, 보안 문제를 해결하기 위한 Webview 환경이 한 층 추가되었을 뿐입니다
라이프사이클
Webview 패널은 생성 후, 2 개의 중요한 라이프사이클 이벤트가 있습니다:
-
숨김/복구:
onDidChangeViewState. 가시성 (webview.visible) 이 변경된 시점, 및 Webview 가 다른 컬럼으로 드래그 앤 드롭된 시점 (panel.viewColumn) 에 트리거됩니다. 일반적으로 상태 저장/복구에 사용됩니다 -
파기:
onDidDispose. 패널이 닫힐 때 트리거되며, timer 를 중지하는 등의 정리 작업을 완료하기 위해 사용됩니다
특히, Webview 가 백그라운드로 들어가면 콘텐츠가 파기되고, 다시 가시화 시 이러한 콘텐츠가 재생성됩니다:
The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab.
예를 들어, 사용자가 탭을 전환한 후, Webview 가 표시하고 있는 콘텐츠는 파기되고, 런타임 상태도 클리어됩니다. 사용자가 다시 전환하거나, 또는 플러그인이panel.reveal() 을 사용하여 Webview 를 사용자 눈앞으로 돌아오게 하면, Webview 콘텐츠가 재로드됩니다. 사용자에 의해 닫히거나, 또는 플러그인이panel.dispose() 을 사용하여 닫히면, Webview 및 그 콘텐츠는 모두 파기됩니다
상태 저장 및 복구
따라서, Webview 는 상태를 유지하는 메커니즘을 제공합니다:
// webview
vscode.getState({ ... })
vscode.setState({ ... })
Webview 콘텐츠 복구에 사용할 수 있습니다. 예를 들어:
// webview
const vscode = acquireVsCodeApi();
const counter = document.getElementById('lines-of-code-counter');
// 이전에 저장한 상태값을 꺼냄
const previousState = vscode.getState();
let count = previousState ? previousState.count : 0;
counter.textContent = count;
setInterval(() => {
counter.textContent = count++;
// 상태값이 업데이트되면 다시 씀
vscode.setState({ count });
}, 100);
P.S. 여기서, acquireVsCodeApi 는 Webview 환경에 주입되는 전역 함수로, VS Code 가 제공하는getState 등의 API 에 액세스하기 위해 사용됩니다
주의가 필요한 것은, setState() 를 통해 저장된 상태는Webview 패널이 닫힐 때 파기됩니다 (지속화 저장되지 않음):
The state is destroyed when the webview panel is destroyed.
지속화하여 유지하고 싶다면, WebviewPanelSerializer 인터페이스를 구현해야 합니다:
// package.json
// 1. package.json 에서 onWebviewPanel:viewType 플러그인 활성화 방식 선언
"activationEvents": [
...,
"onWebviewPanel:catCoding"
]
// extension.ts
// 2. WebviewPanelSerializer 인터페이스 구현
vscode.window.registerWebviewPanelSerializer('catCoding',
new class implements vscode.WebviewPanelSerializer {
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
// Webview 콘텐츠 복구, state 는 webview 중에서 setState 에 의해 저장된 상태
webviewPanel.webview.html = restoreMyWebview(state);
}
}
);
이렇게 하면, VS Code 는 재시작 후 자동으로 Webview 콘텐츠를 복구할 수 있습니다
수동 저장 복구 외에, 또 다른 간단한 방법은retainContextWhenHidden 옵션을 설정하는 것입니다 (createWebviewPanel 시 파라미터로 전달). Webview 가 불가시일 때도 콘텐츠를 유지하도록 요구합니다 (일시중지에 해당). 그러나 큰 성능 오버헤드를 가져오므로, 이 옵션 사용은 신중하게 권장합니다
통신
Webview 콘텐츠는 격리된 환경에서 실행되지만, VS Code 는 플러그인과 Webview 간에 메시지 메커니즘을 제공하여, 양방향 통신 을 구현할 수 있습니다:
// 플러그인 발송
webview.postMessage({ ... })
// webview 수신
window.addEventListener('message', event => { ... })
// webview 발송
const vscode = acquireVsCodeApi()
vscode.postMessage({ ... })
// 플러그인 수신
webview.onDidReceiveMessage(
message => { ... },
undefined,
context.subscriptions
);
따라서, Webview 상태의 저장 및 복구는 완전히 수동으로 구현할 수 있습니다. setState() 등의 API 가 요구를 충족하지 못하는 경우에도 대응할 수 있습니다
테마 적응
JS 를 주입하여 추가 API 를 제공하는 외에, VS Code 는 스타일 적응을 지원하기 위해, 몇 가지 class 및 CSS 변수를 사전 설정하고 있습니다
예를 들어, body 에는 3 개의 사전 설정된 class 값이 있습니다:
-
vscode-light: 라이트 테마 -
vscode-dark: 다크 테마 -
vscode-high-contrast: 하이 콘트라스트 테마
이 세 가지 상태를 사용하여 테마 적응을 완료할 수 있습니다. 예를 들어:
body.vscode-light {
color: black;
}
body.vscode-dark {
color: white;
}
body.vscode-high-contrast {
color: red;
}
또한, 사용자가 설정한 구체적인 색상값도 CSS 변수를 통해 공개되었습니다:
--vscode-editor-foreground 는 editor.foreground 에 대응
--vscode-editor-font-size 는 editor.fontSize 에 대응
사.디버깅
Webview 는 독립된 환경에서 실행되므로, DevTools 를 통해 직접 디버깅할 수 없습니다. 이를 위해, VS Code 는 2 개의 명령을 제공합니다:
-
Developer: Open Webview Developer Tools: 현재 가시한 Webview 의 DevTools 를 엽니다 -
Developer: Reload Webview: 모든 Webview 를 리로드하고, 그 내부 상태를 리셋하고, 로컬 리소스를 재읽습니다
Webview 용 DevTools 는, Toggle Developer Tools 명령으로 여는 DevTools 가 VS Code 자신의 UI 를 디버깅하는 것과 마찬가지로, Webview 콘텐츠를 디버깅할 수 있습니다
Webview 콘텐츠가 로컬 리소스를 로드하고 있는 경우, Reload Webview 명령을 사용하여 재로드할 수 있습니다. 플러그인을 재시작하거나 Webview 를 재오픈할 필요가 없습니다
오.보안 제한
이전의vscode.previewHtml 명령에서도, 현재의 Webview API 에서도, 대량의 보안 제한 이 존재합니다:
-
Webview 내에서는 점프가 지원되지 않습니다.
a태그를 클릭해도 반응이 없습니다. 플러그인을 통해 Webview 콘텐츠를 수정하여 우회적으로 점프를 구현할 것을 권장합니다 -
그래도
iframe환경에 제한됩니다 (iframe이 Webview 내에 배치되었을 뿐). 예를 들어,X-Frame-Options: SAMEORIGIN설정을 포함한 응답 헤더를 가진 페이지는 로드할 수 없습니다 (자세한 내용은 #76384, #70339 참조) -
Electron
webview태그의 몇 가지 보안 옵션이 개방되지 않았습니다. 예를 들어allow-modals로 인해,alert을 사용할 수 없습니다 (자세한 내용은 #67109) -
로컬 리소스 로드가 제한됩니다. 기본적으로 플러그인 디렉토리 및 열려 있는 워크스페이스 디렉토리への 액세스만 허용되며, 특정 API(
webview.asWebviewUri) 를 통해 변환하거나, 또는<base href="${mediaPath}">태그를 통해 로컬 리소스의 루트 경로를 설정해야 합니다 (자세한 내용은 #47631)
예를 들어, 동일 생성자 정책으로 인해, 몇 가지 리소스를iframe 통해 로드할 수 없습니다:
Refused to display 'https://code.visualstudio.com/api/extension-guides/webview' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
此类의 오류는 직접 캡처할 수 없습니다 (자세한 내용은 Catch error if iframe src fails to load). 그러나, iframe 통해 리소스를 로드하기 전에, 해당 리소스에 액세스를 시도하고, 액세스 가능함을 확인한 후 로드할 수 있습니다:
fetch(url).then(() => {
// iframe 통해 로드 가능
frames[0].src = url;
}, () => {
// iframe 통해 로드 불가, 표시함
});
육.정리
일견 유연하고 개방적이지만 실제로는 제한이 극히 많습니다. 현재 (2019/12/14), VS Code 의 Webview 능력의 위치는 HTML 렌더러에 불과하며, UI 확장 능력의 보완입니다:
You should think of the webview more as an html view (one that does not have any server or origin) rather than a webpage.
(#72900 에서 발췌. Webview API 작성자 자신의 발언입니다)
아직 댓글이 없습니다