在 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,这些不进版本库。