开头介绍

Mintlify 作为下一代技术文档平台,以其现代化的 MDX 支持和简洁易用的界面,深受技术写作者的喜爱。

我在写作的过程中面临的一个关键痛点:需要频繁在编辑器和浏览器之间切换来预览文档效果。这些琐碎的操作严重影响了写作流畅度。

为了解决这个问题,我在 FlashMintlify 插件中实现了一个强大的功能:VSCode 内预览 Mintlify 项目站点。这个功能让技术写作真正回归本质——专注于思考如何进行内容架构,而不是被技术细节打断写作思路。

演示和下载

功能点详解

用户痛点分析

在使用 Mintlify 进行技术写作时,开发者通常需要:

  1. 在终端运行 mint dev 启动本地开发服务器
  2. 在浏览器中打开 localhost:3000 查看效果
  3. 每次修改文档后切换到浏览器查看变化

这种工作流程不仅效率低下,还容易打断写作思路。

功能设计思路

FlashMintlify 的预览功能设计围绕以下核心原则:

  1. 一键预览:通过编辑器标题栏的预览按钮,一键打开当前文档的预览
  2. 多种预览模式:支持编辑器内预览、全屏预览和浏览器预览三种模式
  3. 智能路径解析:自动将 MDX 文件路径转换为 Mintlify 的路由路径
  4. 实时同步:文档内容变化时自动刷新预览
  5. 可视化配置:提供友好的配置界面管理预览选项

架构图解

整体架构流程

beside
fullscreen
browser
用户打开 MDX 文档
点击编辑器标题栏预览按钮
执行 flashMintlify.preview.open 命令
获取当前文档路径
构建内部路径
读取预览配置
预览模式?
PreviewPanel.createOrShow
ViewColumn.Beside
PreviewPanel.createOrShow
ViewColumn.Active
vscode.env.openExternal
在系统浏览器打开
创建 WebviewPanel
设置 iframe 指向 mint dev 服务器
在 VSCode 内显示预览
用户点击预览设置
打开 PreviewSettingsPanel
可视化编辑端口和模式
保存到 workspace 配置
实时更新预览面板

时序交互图

用户 VSCode编辑器 命令处理器 PreviewPanel WebviewPanel Mint Dev服务器 在 MDX 文件中点击预览按钮 触发 flashMintlify.preview.open 获取当前文档路径和配置 构建目标 URL createOrShow(url, viewColumn) 创建 webview 面板 通过 iframe 加载页面 返回渲染内容 在编辑器中显示预览 vscode.env.openExternal(url) 在系统浏览器打开 alt [预览模式: beside/fullscreen] [预览模式: browser] 当文档内容发生变化时 文档保存事件 自动刷新预览内容 更新 iframe src 用户 VSCode编辑器 命令处理器 PreviewPanel WebviewPanel Mint Dev服务器

类设计架构

PreviewPanel
+static currentPanel PreviewPanel
-_panel vscode.WebviewPanel
-_extensionUri vscode.Uri
+static createOrShow(extensionUri, url, viewColumn)
+update(url string)
+dispose()
PreviewSettingsPanel
+static currentPanel PreviewSettingsPanel
+static createOrShow(extensionUri)
+handleSave(data SettingsData)
+getFields() : SettingsField[]
+getTitle() : string
+getWebviewScript() : string
SettingsPanel
#_panel vscode.WebviewPanel
#_extensionUri vscode.Uri
+updateContent(fields SettingsField[])
+dispose()
#getBaseWebviewScript() : string
Extension
+activate(context)
+registerPreviewCommands()
+checkDocsJsonExists() : boolean
WebviewPanel
Configuration

代码实现

核心预览命令实现

const openPreviewCommand = vscode.commands.registerCommand('flashMintlify.preview.open', async () => {
  const editor = vscode.window.activeTextEditor;
  if (!editor) {
    vscode.window.showErrorMessage('No active editor found');
    return;
  }
  
  const doc = editor.document;
  const filePath = doc.uri.fsPath;
  
  // 构建内部路径
  const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
  if (!root) {
    vscode.window.showErrorMessage('No workspace folder found');
    return;
  }
  
  // 将文件路径转换为 Mintlify 路由路径
  const rel = path.relative(root, filePath)
    .replace(/\\/g, '/')
    .replace(/\.(md|mdx)$/i, '');
  
  // 特殊处理:根目录的 index.mdx 应该渲染为 /,而不是 /index
  const internalPath = rel === 'index' ? '/' : '/' + rel;
  
  // 读取预览配置
  const cfg = vscode.workspace.getConfiguration('flashMintlify');
  const configuredPort = cfg.get<number>('preview.port', 3000);
  const mode = cfg.get<'beside' | 'fullscreen' | 'browser'>('preview.mode', 'browser');
  
  const targetUrl = `http://localhost:${configuredPort}${internalPath}`;
  
  // 根据模式选择预览方式
  if (mode === 'beside') {
    PreviewPanel.createOrShow(context.extensionUri, targetUrl, vscode.ViewColumn.Beside);
  } else if (mode === 'fullscreen') {
    PreviewPanel.createOrShow(context.extensionUri, targetUrl, vscode.ViewColumn.Active);
  } else {
    vscode.env.openExternal(vscode.Uri.parse(targetUrl));
  }
});

PreviewPanel 核心实现

export class PreviewPanel {
  public static currentPanel: PreviewPanel | undefined;
  private readonly _panel: vscode.WebviewPanel;
  private readonly _extensionUri: vscode.Uri;

  public static createOrShow(
    extensionUri: vscode.Uri, 
    url: string, 
    viewColumn: vscode.ViewColumn = vscode.ViewColumn.Two
  ) {
    if (PreviewPanel.currentPanel) {
      // 如果已经有预览面板,只更新内容,避免位置变化
      PreviewPanel.currentPanel.update(url);
      return;
    }

    const panel = new PreviewPanel(extensionUri, url, viewColumn);
    PreviewPanel.currentPanel = panel;
  }

  private constructor(extensionUri: vscode.Uri, url: string, viewColumn: vscode.ViewColumn) {
    this._extensionUri = extensionUri;

    // 根据 viewColumn 决定标题和图标
    const isFullscreen = viewColumn === vscode.ViewColumn.Active;
    const title = isFullscreen ? 'Mintlify Preview (Fullscreen)' : 'Mintlify Preview';

    this._panel = vscode.window.createWebviewPanel(
      'flashMintlifyPreview',
      title,
      { viewColumn, preserveFocus: !isFullscreen },
      {
        enableScripts: true,
        retainContextWhenHidden: true,
        localResourceRoots: [
          vscode.Uri.joinPath(extensionUri, 'media'),
          vscode.Uri.joinPath(extensionUri, 'out', 'webview')
        ]
      }
    );

    this._panel.onDidDispose(() => this.dispose());
    this.update(url);
  }

  public update(url: string) {
    const nonce = String(Date.now());
    const csp = `default-src 'none'; img-src vscode-resource: https: data:; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; frame-src ${url};`;
    
    // 使用 iframe 嵌入 Mintlify 预览页面
    this._panel.webview.html = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="Content-Security-Policy" content="${csp}">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Mintlify Preview</title>
  <style>
    html, body, iframe { height: 100%; width: 100%; padding: 0; margin: 0; }
    body { background: var(--vscode-editor-background); }
    iframe { border: 0; }
  </style>
</head>
<body>
  <iframe src="${url}" allow="clipboard-read; clipboard-write"></iframe>
</body>
</html>`;
  }
}

预览设置面板实现

export class PreviewSettingsPanel extends SettingsPanel {
  protected async handleSave(data: SettingsData): Promise<void> {
    const cfg = vscode.workspace.getConfiguration('flashMintlify');
    const portValue = Number(data['preview.port']?.value ?? 3000);
    const modeValue = (data['preview.mode']?.value ?? 'browser') as 'beside' | 'fullscreen' | 'browser';

    // 验证端口值
    if (isNaN(portValue) || portValue <= 0 || portValue > 65535) {
      vscode.window.showErrorMessage(`Invalid port number: ${portValue}`);
      return;
    }

    try {
      // 保存配置到工作区
      await cfg.update('preview.port', portValue, vscode.ConfigurationTarget.Workspace);
      await cfg.update('preview.mode', modeValue, vscode.ConfigurationTarget.Workspace);

      const action = await vscode.window.showInformationMessage(
        'Preview options saved successfully!',
        'OK',
        'Reload Window'
      );

      if (action === 'Reload Window') {
        vscode.commands.executeCommand('workbench.action.reloadWindow');
      }
    } catch (error) {
      vscode.window.showErrorMessage('Failed to save preview options: ' + error);
    }
  }

  protected getFields(): SettingsField[] {
    const cfg = vscode.workspace.getConfiguration('flashMintlify');
    const port = cfg.get<number>('preview.port', 3000);
    const mode = cfg.get<string>('preview.mode', 'browser');

    return [
      {
        name: 'preview.port',
        type: 'number',
        label: 'Preview port',
        description: "Port where 'mint dev' is running",
        defaultValue: String(port),
        currentValue: String(port),
        placeholder: '3000'
      },
      {
        name: 'preview.mode',
        type: 'select',
        label: 'Preview mode',
        description: 'Choose how to open the preview',
        options: ['beside', 'fullscreen', 'browser'],
        defaultValue: mode,
        currentValue: mode
      }
    ];
  }
}

实现要点和注意事项

  1. 路径转换逻辑:需要正确处理 Windows 和 Unix 路径分隔符,并将 .md.mdx 扩展名移除
  2. 特殊路径处理:根目录的 index.mdx 文件应该映射到 / 路径,而不是 /index
  3. 面板管理:使用单例模式确保同一时间只有一个预览面板,避免资源浪费
  4. 安全策略:正确配置 Content Security Policy,允许 iframe 加载本地开发服务器内容
  5. 错误处理:对无效端口号、文件路径等进行适当的验证和错误提示

简单总结

VSCode 内预览功能极大地提升了使用 Mintlify 进行技术写作的效率和体验:

  1. 无缝集成:在编辑器内直接预览,无需切换窗口
  2. 多种模式:支持分屏、全屏和浏览器三种预览方式,满足不同场景需求
  3. 智能路径:自动处理文件路径到 URL 路径的转换
  4. 配置友好:可视化的设置界面,轻松配置端口和预览模式
  5. 实时同步:文档变化时自动更新预览内容

这个功能让技术写作者能够真正专注于内容创作,而不是被技术细节分散注意力。它体现了 FlashMintlify 的核心理念:让写作回归本质,把重心放在内容架构和核心价值传达上。

后续改进方向

  1. 智能端口检测:自动检测 mint dev 运行的端口
  2. 预览状态同步:支持预览窗口和编辑器光标位置同步
  3. 快捷键支持:添加快捷键快速切换预览模式
  4. 错误页面优化:当服务器未启动时显示友好的提示页面

体验 FlashMintlify,让技术写作变得更加高效和愉悦!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐