GoMind Viewer — VSCode 扩展
在 VSCode 中把 .md / .yaml / .yml / .xmind / .gomind 文件用 GoMind Viewer(Flutter Web 引擎)渲染成思维导图预览。
它是怎么工作的(30 秒看懂)
┌────────────┐ ┌──────────────────────────────┐ ┌────────────────┐
│ VSCode │ │ Extension Host (TypeScript) │ │ Viewer 引擎 │
│ Webview │◄──│ ViewerPanel + ViewerServer │──►│ Flutter Web │
│ (iframe) │ │ │ │ (wasm 产物) │
└────────────┘ └──────────────────────────────┘ └────────────────┘
ViewerServer(src/server.ts):扩展启动一个绑定 127.0.0.1 随机端口的本地 HTTP 服务器,托管 Viewer 的静态资源,并暴露一个 GET /open/<token> 端点。
- token 机制(
src/tokenRegistry.ts):预览某个文件时,扩展为这个文件的绝对路径铸一枚随机的 32 字节 token。Viewer iframe 只能通过 ?file=http://127.0.0.1:<port>/open/<token> 读取这一个文件,无法访问任意路径。
ViewerPanel(src/viewerPanel.ts):一个 WebviewPanel,内容是一个内嵌 iframe,src 指向 http://127.0.0.1:<port>/?file=<openUrl>&filename=<文件名>。文件保存时自动刷新(可配置)。
- 本地服务器需要正确的
Cross-Origin-Isolation 响应头(Flutter Wasm 需要 COOP/COEP),见 server.ts 的 crossOriginIsolationHeaders。
目录结构
vscode/
├── package.json # 扩展清单:命令、菜单、配置、脚本
├── tsconfig.json
├── .vscode/
│ ├── launch.json # F5 调试配置(Extension Development Host)
│ └── tasks.json # 编译任务(F5 前自动 npm run compile)
├── src/ # 扩展源码 (TypeScript)
│ ├── extension.ts # 入口:注册命令、服务器生命周期、自动刷新
│ ├── server.ts # 本地 HTTP 服务器 + /open/<token> 端点
│ ├── viewerPanel.ts # Webview 面板 + iframe 嵌入
│ ├── tokenRegistry.ts # token ↔ 文件路径 注册表
│ ├── devServer.ts # 独立运行服务器(不依赖 VSCode,npm run serve)
│ └── test/ # 集成测试(VS Code Test Runner)
├── scripts/
│ ├── fetch-viewer.sh # ★ 构建/拷贝 Viewer 产物到 media/viewer
│ └── serve.sh # 独立服务器启动脚本
├── media/viewer/ # ★ 打包进扩展的 Viewer 引擎产物(git 忽略,需生成)
└── out/ # tsc 编译输出(git 忽略)
前提条件
| 工具 |
用途 |
版本要求 |
| Node.js + npm |
扩展开发/打包 |
≥ 18 |
| Flutter SDK |
构建 Viewer(仅需重新生成产物时) |
支持 --wasm(≥3.22) |
| VSCode |
调试扩展 |
≥ 1.85 |
cd vscode
npm install # 安装扩展的 devDependencies(TypeScript, vsce 等)
快速开始(最重要的一步:生成 Viewer 产物)
产物来源链(scripts/fetch-viewer.sh 按优先级寻找,命中即停):
- 环境变量
GOMIND_VIEWER_BUILD 指向的已构建 build/web 目录
$VIEWER_SRC/build/web
../viewer/build/web(superproject 布局里 viewer 子模块的构建产物)
如果都没有,它会自动跑到 ../viewer 去执行 flutter build web --wasm --release。
方式 A:直接用现成的 build/web(推荐日常调试)
# 假设你已经构建过 viewer(见下方「Viewer 端构建」)
GOMIND_VIEWER_BUILD=/Users/changshuai/Codes/gomind/viewer/build/web npm run fetch-viewer
方式 B:让脚本自己去构建
cd vscode && npm run fetch-viewer
# 若 viewer 源码不在 ../viewer,先设置 VIEWER_SRC=/path/to/viewer
产物被裁剪(fetch-viewer.sh 后半段):
- 删除
canvaskit 非 wasm 变体(canvaskit.js/chromium/experimental_webparagraph/wimp)
- 删除
.symbols 调试符号
- 默认
GOMIND_WASM_ONLY=1 时删除 main.dart.js(dart2js 回退)
- 写入
media/viewer/viewer-build-info.json 记录构建来源
裁剪后 media/viewer 约 23MB(完整 59MB)。这是打进 VSIX 的那份产物。
media/viewer/ 在 .gitignore 中,不进 git。提交到远端的是源码 + 脚本,产物在打包 VSIX 时本地生成。
Viewer 端构建(改动 Viewer 引擎后)
Viewer 是 gomind_app 仓库的 viewer 分支(同一个 gomind_app.git 仓库)。改完 Viewer 代码后:
cd viewer # 即 gomind 仓库根下的 viewer 子模块
flutter pub get
flutter build web --wasm --release
# 产物在 build/web,然后回到 vscode 侧:
cd ../vscode && npm run fetch-viewer # 方式 B 会直接复用 ../viewer/build/web
编译扩展
cd vscode
npm run compile # tsc -p ./ → out/
npm run watch # 增量编译,改 TS 自动重编(调试时用)
调试(F5)
- 在
vscode/ 目录用 VSCode 打开。
- 按 F5(或 Run → Start Debugging)。
launch.json 会:
- 先跑
preLaunchTask: "${defaultBuildTask}"(tasks.json 里的 npm: compile)自动编译;
- 以
--extensionDevelopmentPath 启动一个 Extension Development Host(一个全新的 VSCode 窗口)。
- 在开发窗口里:
- 打开任意
.md / .yaml / .xmind / .gomind 文件;
- 右键 → 对应的格式查看项(如 View Markdown with GoMind);
- 或命令面板
Cmd+Shift+P → View Markdown with GoMind / View Yaml with GoMind / View XMind with GoMind / View GoMind with GoMind。
- 断点下在
src/*.ts 即可调试(out/**/*.js 有 sourcemap)。
常见坑:调试时扩展找不到 Viewer。原因是 media/viewer 还没生成。先跑 npm run fetch-viewer,或者设环境变量:
// .vscode/launch.json 里可加(可选)
"env": { "GOMIND_VIEWER_DIR": "/Users/changshuai/Codes/gomind/viewer/build/web" }
独立运行本地服务器(不打开 VSCode 调试 UI)
适合快速验证 Viewer 引擎本身、以及调试 iframe 内容:
npm run compile
npm run serve -- sample.md # 预览某个文件
# 输出:
# Viewer server: http://127.0.0.1:<port>
# Viewer URL: http://127.0.0.1:<port>/?file=...&filename=sample.md
# 用浏览器打开 Viewer URL 即可
不带文件参数则只启动服务器不注册 token:
npm run serve -- # 仅静态托管 media/viewer
运行集成测试
npm test
# = npm run compile && node ./out/test/runTest.js
- 测试会下载 Electron 测试环境(首次较慢),用
--disable-extensions 启动一个干净的 VSCode。
- 会创建一个临时目录生成
sample.md,验证:isOpenable、命令能打开 webview、服务器能托管 Viewer 并生成 ?file= iframe、refresh 会重新铸 token。
- 测试需要能找到 Viewer 产物(
runTest.ts 会依次找 ../viewer/build/web 和 media/viewer,或用 GOMIND_VIEWER_DIR 指定)。
打包并安装(提交给 VSCode / 分发给同事)
cd vscode
npm run fetch-viewer # 1. 确保 media/viewer 产物最新
npm run package # 2. vsce package(自动先跑 vscode:prepublish = compile)
# 生成 gomind-viewer-0.1.0.vsix
安装 VSIX:
code --install-extension gomind-viewer-0.1.0.vsix
# 或在 VSCode:Extensions 面板 → ⋯ → Install from VSIX...
发布前检查清单:
- [ ]
media/viewer/viewer-build-info.json 的 builtAt 是否为最新(确认产物新鲜)
- [ ]
npm run compile 无 TS 报错
- [ ]
npm test 通过
- [ ] 手动在 Extension Development Host 里预览过 md/yaml/xmind/gomind 四种格式
常用配置
package.json → contributes.configuration:
| 配置项 |
默认 |
说明 |
gomind.viewer.serverHost |
127.0.0.1 |
本地服务器绑定地址 |
gomind.viewer.enableAutoRefresh |
true |
保存文件时自动刷新预览 |
环境变量速查
| 变量 |
作用 |
GOMIND_VIEWER_DIR |
运行时指定 Viewer 目录(覆盖 media/viewer) |
GOMIND_VIEWER_BUILD |
fetch-viewer 优先使用的已构建 build/web |
VIEWER_SRC |
fetch-viewer 自动构建时使用的 Flutter 项目路径 |
GOMIND_WASM_ONLY |
1(默认)只保留 wasm 产物;0 保留 dart2js 回退 |
提交到 git
本扩展是 superproject 的一个 submodule(john/gomind-vscode),改动流程:
cd vscode
git add .
git commit -m "fix#NNN: ..."
git push -u origin main # 推送到 GitLab 远端
# 然后在 superproject 根目录:
git add vscode && git commit -m "chore: bump vscode submodule" && git push
.gitignore 已排除 node_modules/ out/ media/viewer/ .vscode-test/ *.vsix *.log,这些不进版本库。