✨ feat(vscode): 代码阅读入口、文件树与预览编辑器

This commit is contained in:
cruldra committed 2026-09-24 21:36:00 +08:00
1 parent 4d34b8c715
commit 2b823a32ca
24 files changed
+768 -72

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
import type { Webview } from 'vscode'
import { randomBytes } from 'node:crypto'
import * as fs from 'node:fs'
import { Uri } from 'vscode'
export function makeNonce(): string {
return randomBytes(16).toString('base64').replace(/[+/=]/g, '')
}
/** vite 产物目录,也是 webview 唯一放行的本地资源目录(localResourceRoots)。 */
export function webviewUiRoot(extensionUri: Uri): Uri {
return Uri.joinPath(extensionUri, 'dist', 'webview-ui')
}
/**
* 读 dist/webview-ui 下的一个入口页面,改成能在 webview 里跑的样子:
* 资源路径改写成 webview URI、每个 `<script>` 带上 nonce、注入只放行这个 nonce 的 CSP。
* entry 是入口 html 文件名(例如 'index.html');调用方还要往页面里追加别的 `<script>` 时,把同一个 nonce 传进来。
*/
export function webviewHtml(webview: Webview, extensionUri: Uri, entry: string, nonce = makeNonce()): string {
const distRoot = webviewUiRoot(extensionUri)
let html = fs.readFileSync(Uri.joinPath(distRoot, entry).fsPath, 'utf-8')
// vite 产物的入口是固定名(无 hash),webview 按 URL 缓存资源——必须按文件 mtime 加 ?v= 版本号,
// 否则 reload 后仍会跑缓存里的旧入口脚本 / 样式
html = html.replace(/(src|href)="(\/[^"]+|[^"/][^"]*)"/g, (_m, attr, p) => {
const fileUri = Uri.joinPath(distRoot, p.replace(/^\.?\//, ''))
let version = ''
try {
version = `?v=${fs.statSync(fileUri.fsPath).mtimeMs}`
}
catch {
// 资源文件不存在则不加版本号
}
return `${attr}="${webview.asWebviewUri(fileUri)}${version}"`
})
html = html.replace(/<script(\s[^>]*)?>/g, (_m, attrs) => `<script nonce="${nonce}"${attrs ?? ''}>`)
const cspSource = webview.cspSource
const csp = `default-src 'none'; img-src ${cspSource} data:; style-src ${cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}'; font-src ${cspSource};`
return html.replace(
/<head>/,
`<head>\n <meta http-equiv="Content-Security-Policy" content="${csp}">`,
)
}