Skip to content

vitepress-rendering-strategies

logo


npm packagenode compatibilitybuild statusStart new PR in StackBlitz Codeflowdiscord chatlicense


English | 简体中文

⚡ 项目状态: 积极开发中 - VitePress 项目生产可用。v0.1.0+ 开始采用语义化版本。

VitePress 引入跨框架组件渲染能力(当前内置 React),在不改变 VitePressSSG 架构前提下提供更细粒度的渲染与 Hydration 策略,灵感参考自 AstroIslands Architecture

  • 跨框架渲染: 生产就绪的 React 集成,采用统一的 Islands Architecture,正扩展到 Solid / Svelte / Preact / Angular,共享渲染原语。
  • 四种客户端指令: client:onlyclient:loadclient:visiblessr:only(默认)。
  • SPA 同步渲染优化: spa:sync-render/spa:sr,在路由切换时同步注入关键组件的预渲染输出,消除导致视觉不稳定的异步加载间隙。
  • HMR 支持: 跨框架边界的完整热模块替换,保持状态,维护开发速度。
  • MPA 模式兼容: 兼容 VitePress MPA 模式。

更完整的策略设计、动机与示例,见文档 VitePress Cross-Framework Rendering Strategy

要求

  • Node.js: ^20.19.0 或 >=22.12.0
  • VitePress: ^1.6.3
  • React/ReactDOM(可选): ^18.2.0
  • @vitejs/plugin-react-swc(可选): ^3.9.0

快速开始

1. 安装依赖

bash
pnpm add -D vitepress-rendering-strategies @vitejs/plugin-react-swc
pnpm add react react-dom

2. 配置 VitePress

  1. VitePress 配置中启用 React 渲染策略插件:

    ts
    // .vitepress/config.ts
    import { defineConfig } from 'vitepress';
    import vitepressReactRenderingStrategies from 'vitepress-rendering-strategies/react';
    
    const vitePressConfig = defineConfig({
      // 你的 VitePress 配置...
    });
    
    // 向 Vite 配置注入 React 渲染支持与构建期优化
    vitepressReactRenderingStrategies(vitePressConfig);
    
    export default vitePressConfig;
  2. 在主题增强中安装客户端运行时:

    ts
    // .vitepress/theme/index.ts
    import DefaultTheme from 'vitepress/theme';
    import reactClientIntegration from 'vitepress-rendering-strategies/react/client';
    import type { Theme } from 'vitepress';
    
    const theme: Theme = {
      extends: DefaultTheme,
      async enhanceApp() {
        await reactClientIntegration();
      }
    };
    
    export default theme;

3. 使用 React 组件

  1. 编写 React 组件:

    tsx
    // components/Landing.tsx
    import { useState } from 'react';
    
    export default function Landing() {
      return <div>Hello World</div>;
    }
  2. Markdown 中导入 React 组件并使用渲染指令:

    md
    <script lang="react">
      import Landing from '../components/Landing';
    </script>
    
    <Landing ssr:only spa:sr title="Hello" />

注意事项

  1. 组件标签命名

    • 必须以大写字母开头(PascalCase 风格),例如 MyComp
    • 标签名必须与同一 .md 文件内 <script lang="react"> 块中的本地导入名完全一致。如果使用了别名导入(如 import { Landing as HomeLanding } from '...';),则标签必须写为 <HomeLanding ... />
    • 任何不匹配情况都会在编译时跳过,并输出一条告警。
  2. 仅支持自闭合标签

    • Markdown 中的 React 组件必须写成自闭合形式:<Comp ... />
    • 非自闭合形式(如 <Comp>...</Comp>)会被跳过并输出一条告警。
  3. 位置与导入

    • 组件必须在同一 Markdown 页面内的 <script lang="react"> 块中完成导入,未导入的组件会被忽略。
    • 组件可以在 Vue 的插槽/模板中使用(例如在 <template #default>...</template> 内部),也会被正确发现并转换。
  4. Props 传递(一次性)

    • 标签上的所有非策略属性会作为字符串 props 传递给 React 组件。Vue 绑定(如 :page-title="page.title")会先由 Vue 求值为 DOM 属性,再在 React 渲染/水合时转发为 props。这是一次性数据传递,非响应式。
    • 不要通过属性传递函数或事件处理(如 onClick);当前不支持跨框架桥接可调用的 props/事件。
  5. 支持的指令

    • client:onlyclient:loadclient:visiblessr:only(默认)。
    • spa:sync-render(即 spa:sr)对 client:* 默认关闭,对 ssr:only 默认开启(除非显式 spa:sync-render:disable/spa:sr:disable)。

组件编写规范(TypeScript)

tsx
// components/Landing.tsx
import { useState } from 'react';

export interface LandingProps {
  title: string;
}

export default function Landing(props: LandingProps) {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h2>{props.title}</h2>
      <button type="button" onClick={() => setCount(c => c + 1)}>
        Click
      </button>
      <span>{count}</span>
    </div>
  );
}

约束:<script lang="react"> 里仅支持静态 ESM import。初始渲染时的 props 为一次性快照,非响应式双向绑定(通过父级 Vue 传入的数据只用于初始化)。

ssr:only 使用 Node API 的约束

  • 仅当组件在单个页面上“仅以 ssr:only 形式”渲染时,才能依赖 Node API(例如 node:fs)。若同一页面上该组件同时以任意 client:* 指令使用,则不得依赖 Node API。
  • 由于 Vite 可能不会把环境 API 模块纳入客户端依赖图,在 ssr:only 模式读取本地文件时,请通过 import.meta.dirname 解析目标路径。
ts
import { readFileSync } from 'node:fs';
import { join } from 'pathe';

const targetPath = join(import.meta.dirname, 'local-data.json');
const data = JSON.parse(readFileSync(targetPath, 'utf-8')) as {
  data: unknown;
};

渲染指令与行为

指令一览

  • ssr:only(默认)
    • 构建期完成预渲染,仅输出静态 HTML,不执行客户端 Hydration
    • 适合静态内容、SEO 关键内容,最利于 FCP/LCP/SEO,并避免增加客户端 JS 体积。
  • client:load
    • 先预渲染 HTML,随后在客户端立即 Hydration 接管交互。
    • 适合首屏关键且需要交互的组件;对 TTI 有一定压力。
  • client:visible
    • 预渲染 HTML,组件进入视口后再 Hydration
    • 适合非首屏交互组件(评论区、图表等);脚本默认预加载,非纯惰性。
  • client:only
    • 仅客户端渲染,无 SSR/SSG 预渲染。
    • 适合强宿主依赖或非关键、轻量组件。

指令速查表

指令是否预渲染 HTML客户端 Hydration触发时机典型场景spa:sr 默认
ssr:onlyN/A静态/SEO 关键内容开启
client:load立即预加载模块,加载后即水合首屏关键交互组件关闭
client:visible可见时预加载;进入视口后水合非首屏交互(评论、图表等)关闭
client:onlyN/A仅客户端强宿主依赖或轻量小部件关闭

SPA 同步渲染(spa:sync-render / spa:sr

VitePressSPA 路由切换时,Vue 内容同步更新;非 Vue 组件(如 React)的预渲染 HTML 与客户端脚本加载是异步的,在弱网环境和低性能设备上容易造成 闪烁 现象。spa:sr 通过将目标页面中使用该指令的组件的预渲染输出合并到 Vue 的客户端脚本,高优阻塞下载和解析所有使用 spa:sr 指令的组件的 CSS 模块,同步渲染来消除闪烁现象。

默认规则:

  • client:only 组件不支持 spa:sr
  • 使用 client:* 指令的组件默认不开启 spa:sr,除非显式(spa:sr/spa:sync-render)标注。
  • 使用 ssr:only 的组件(以及无指令组件)默认开启 spa:sr,除非显式(spa:sr:disable/spa:sync-render:disable)标注。

权衡:spa:sr 改善切页体验,但会增大切页加载的客户端脚本体积,建议对 关键渲染组件 启用。

示例:

md
<Landing client:load spa:sr title="Home" />
<Hero ssr:only />
<Chart client:visible />
<Widget client:only />

包体积提示:体积增加仅发生在 SPA 切页时加载的页面客户端脚本;不影响首屏使用的 .lean.js(VitePress 首次渲染用以水合的精简脚本)。

使用建议与限制

  • 默认以 ssr:only 渲染,建议仅对需要交互的关键组件使用 client:load;非首屏交互组件用 client:visibleclient:only 适合强宿主依赖或降级。
  • 对开启 spa:sr 的组件保持克制,避免过多增大切页脚本负担。

与 Vue 组合使用

  • 可在 Vue 组件内部通过插槽嵌入 React 组件。初始渲染时,容器占位节点的属性会先被 Vue 求值,再以一次性 props 传递给 React 组件。
md
<script setup>
  import Parent from './Parent.vue';
</script>
<script lang="react">
  import Child from './Child';
</script>

<Parent>
  <template #default="{ msg }">
    <Child client:only title="Hello" :message="msg" />
  </template>
</Parent>

排错 / FAQ

  • 标签被忽略:确保标签以大写字母开头、名称与本地导入名完全一致,且 React 标签必须自闭合。
  • 组件未渲染:组件必须在同一 .md 内的 <script lang="react"> 完成导入,且不要放在围栏代码块中。
  • 切页闪烁:对首屏关键组件启用 spa:sr
  • 水合报错:运行时会回退到客户端渲染;请确保服务端标记与客户端输出一致,避免通过属性传递函数。
  • Node API 使用报错:仅在该页面 ssr:only 渲染时使用,并通过 import.meta.dirname 解析路径。

本地开发与调试(DX)

为开发 vitepress-rendering-strategies 提供了优化的本地调试体验:在预览文档站点中实时加载源码改动,支持 JavaScript Debug Terminal 下的断点调试,无需手动重启服务器。

  1. 准备 docs 项目以消费本地包:

    bash
    pnpm install
    pnpm build          # 首次构建,产出 dist/* 供开发期构建使用
    pnpm docs:dev-prepare
  2. 在 JavaScript Debug Terminal 中启动文档:

    • VS Code:Terminal → New JavaScript Debug Terminal
    • 执行:
    bash
    pnpm docs:dev

    该命令会等待 dist/node/index.js 可用,再启动 VitePress 开发服务器。你可以在库源码中(例如 src/node/**src/node/react/**)放置 debugger;,当相关代码路径被执行时,调试器会自动断点。

  3. 修改、保存、继续:

    • vitepress-rendering-strategies 下的源码改动会触发 Vite 自动重建相关的 config 模块;无需手动重启服务器。
    • 客户端/运行时代码建议用浏览器 DevTools 调试;Node/插件侧代码建议用 JavaScript Debug Terminal(或 debugger;)。

提示:若需切回使用已构建产物,可执行:

bash
pnpm docs:prod-prepare

参与贡献

欢迎社区贡献!更多详情请参阅 贡献指南

许可证

MIT © XiSenao


⭐ 觉得有用?给我点个星!
GitHub stars

Released under the CC BY-SA 4.0 License. (4547523)