common-intellisense
Version:
195 lines (180 loc) • 6.34 kB
text/typescript
import fsp from 'node:fs/promises'
import path from 'node:path'
import * as vscode from 'vscode'
export interface Options {
title?: string
scripts?: string | (string | { enforce: 'pre' | 'post', src: string })[]
styles?: string | string[]
onMessage?: (data: any) => void
viewColumn?: vscode.ViewColumn
retainContextWhenHidden?: boolean
html?: string
}
export class CreateWebview {
private webviewView: any
private _deferScript = ''
private _extensionUri: vscode.Uri
private _extensionPath: string
private props: Record<string, any> = {}
private scriptsPromises: Promise<string>[] = []
private viewColumn: vscode.ViewColumn
private _title: string
private _scripts: string | (string | { enforce: 'pre' | 'post', src: string })[]
private _styles: string | string[]
private retainContextWhenHidden: boolean
private onMessage?: (data: any) => void
constructor(
private readonly _extension: vscode.ExtensionContext,
options: Options,
) {
this._extensionUri = _extension.extensionUri
this._extensionPath = _extension.extensionPath
this._title = options.title || 'webview'
this._scripts = options.scripts || ''
this._styles = options.styles || ''
this.viewColumn = options.viewColumn || vscode.ViewColumn.One
this.retainContextWhenHidden = options.retainContextWhenHidden === undefined ? true : options.retainContextWhenHidden
this.onMessage = options.onMessage
}
public setProps(props: Record<string, any>) {
this.props = { ...this.props, ...props }
}
public async create(html: string, callback: (data: any) => void = () => { }) {
const webviewView = vscode.window.createWebviewPanel(
this._title, // 视图的声明方式
this._title, // 选项卡标题
this.viewColumn, // 在编辑器中显示的视图位置
{
enableScripts: true, // 启用JS,否则内容将被视为静态HTML
localResourceRoots: [this._extensionUri],
retainContextWhenHidden: this.retainContextWhenHidden,
},
)
this.webviewView = webviewView
webviewView.webview.html = await this._getHtmlForWebview(
webviewView.webview,
html,
)
webviewView.webview.onDidReceiveMessage((data: any) => {
callback(data)
this.onMessage?.(data)
})
}
public async createWithHTMLUrl(htmlUrl: string, callback: (data: any) => void = () => { }) {
const url = path.resolve(this._extensionPath, htmlUrl)
const html = await fsp.readFile(url, 'utf-8')
const webviewView = vscode.window.createWebviewPanel(
this._title, // 视图的声明方式
this._title, // 选项卡标题
this.viewColumn, // 在编辑器中显示的视图位置
{
enableScripts: true, // 启用JS,否则内容将被视为静态HTML
localResourceRoots: [this._extensionUri],
retainContextWhenHidden: this.retainContextWhenHidden,
},
)
this.webviewView = webviewView
webviewView.webview.html = html.replace(/(?:src|href)="([/.][^"]*)"/g, (_, u) => _.replace(u, webviewView.webview.asWebviewUri(vscode.Uri.file(
path.join(this._extensionPath, 'media', u),
)).toString())).replace(/(<\/head>)/, '<script>\n const vscode = acquireVsCodeApi();\n </script>\n $1')
webviewView.webview.onDidReceiveMessage((data: any) => {
callback(data)
this.onMessage?.(data)
})
}
public isActive() {
try {
if (this.webviewView)
return this.webviewView.active
}
catch (error) {
console.error(error)
}
return false
}
public destory() {
if (this.webviewView)
this.webviewView.dispose()
}
public deferScript(scripts: string | string[]) {
this._deferScript
= typeof scripts === 'string' ? scripts : scripts.join('\n')
}
public deferScriptUri(scriptUri: string | string[]) {
try {
const uris = typeof scriptUri === 'string' ? [scriptUri] : scriptUri
this.scriptsPromises.push(...uris.map(uri => fsp.readFile(
`${this._extensionUri.path}/media/${uri}`
, 'utf-8',
)))
}
catch (error: any) {
throw new Error(error.message)
}
}
public postMessage(data: any) {
if (this.webviewView)
this.webviewView.webview.postMessage(data)
}
private async _getHtmlForWebview(webview: vscode.Webview, html: string) {
const outerUriReg = /^https:\/\//
const styles = this._styles
? (Array.isArray(this._styles) ? this._styles : [this._styles])
.map((style) => {
const styleUri = outerUriReg.test(style)
? style
: webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'media', style),
)
return `<link href="${styleUri}" rel="stylesheet">`
})
.join('\n')
: ''
const preScripts: string[] = []
const postScripts: string[] = []
const scripts = Array.isArray(this._scripts)
? this._scripts
: [this._scripts]
scripts.forEach((script) => {
let isPre = false
if (typeof script !== 'string') {
isPre = script.enforce === 'pre'
script = script.src
}
const scriptUri = outerUriReg.test(script)
? script
: webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'media', script),
)
const _script = script.startsWith('<script')
? script
: `<script src="${scriptUri}"></script>`
if (isPre)
preScripts.push(_script)
else
postScripts.push(_script)
})
const scriptsUri = await Promise.all(this.scriptsPromises).then(scripts =>
scripts.map(script => `<script>${script.replace('webviewThis', JSON.stringify(this.props))}</script>`),
)
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${this._title}</title>
${styles}
<script>
const vscode = acquireVsCodeApi();
</script>
${preScripts.length ? preScripts.join('\n') : ''}
</head>
<body>
${html}
</body>
${postScripts.join('\n')}
${this._deferScript}
${scriptsUri.join('\n')}
</html>`
}
}