vitepress-rendering-strategies
为 VitePress
引入跨框架组件渲染能力(当前内置 React
),在不改变 VitePress
的 SSG
架构前提下提供更细粒度的渲染与 Hydration
策略,灵感参考自 Astro
的 Islands Architecture。
- 跨框架渲染: 先支持
React
,规划扩展到Solid
/Svelte
/Preact
/Angular
等。 - 四种客户端指令:
client:only
、client:load
、client:visible
、ssr:only
(默认)。 - SPA 同步渲染优化:
spa:sync-render
/spa:sr
,在路由切换时同步注入关键组件的预渲染输出,避免页面闪烁。 - 一致的 Dev/Prod 体验:
Dev
提供HMR
,Prod
生成页面级metafile
与modulepreload
以优化切页性能。 - MPA 模式兼容: 兼容
VitePress
MPA
模式。
更完整的策略设计、动机与示例,见文档 VitePress Cross-Framework Rendering Strategy。
要求
Node.js
: ^20.19.0 或 >=22.12.0VitePress
: ^1.6.3React
/ReactDOM
: ^18.2.0@vitejs/plugin-react-swc
: ^3.9.0
安装
bash
pnpm add -D vitepress-rendering-strategies @vitejs/plugin-react-swc
pnpm add react react-dom
快速开始
在
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;
在主题增强中安装客户端运行时:
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;
在
Markdown
中导入React
组件并使用渲染指令:md<script lang="react"> import Landing from '../components/Landing'; </script> <Landing ssr:only spa:sr title="Hello" />
注意事项
组件标签命名
- 必须以大写字母开头(
React
风格),例如MyComp
。 - 标签名必须与同一
.md
文件内<script lang="react">
块中的本地导入名完全一致。如果使用了别名导入(如import { Landing as HomeLanding } from '...';
),则标签必须写为<HomeLanding ... />
。 - 任何不匹配情况都会在编译时跳过,并输出一条告警。
- 必须以大写字母开头(
仅支持自闭合标签
Markdown
中的React
组件必须写成自闭合形式:<Comp ... />
。- 非自闭合形式(如
<Comp>...</Comp>
)会被跳过并输出一条告警。
位置与导入
- 组件必须在同一
Markdown
页面内的<script lang="react">
块中完成导入,未导入的组件会被忽略。 - 组件可以在
Vue
的插槽/模板中使用(例如在<template #default>...</template>
内部),也会被正确发现并转换。
- 组件必须在同一
Props 传递(一次性)
- 标签上的所有非策略属性会作为字符串
props
传递给React
组件。Vue
绑定(如:page-title="page.title"
)会先由Vue
求值为DOM
属性,再在React
渲染/水合时转发为props
。这是一次性数据传递,非响应式。 - 不要通过属性传递函数或事件处理(如
onClick
);当前不支持跨框架桥接可调用的props
/事件。
- 标签上的所有非策略属性会作为字符串
支持的指令
client:only
、client:load
、client:visible
、ssr: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
(默认)- 构建期完成预渲染,仅输出静态
HTML
,不执行客户端Hydration
。 - 适合静态内容、
SEO
关键内容,最利于FCP
/LCP
/SEO
,并避免增加客户端JS
体积。
- 构建期完成预渲染,仅输出静态
client:load
- 先预渲染
HTML
,随后在客户端立即Hydration
接管交互。 - 适合首屏关键且需要交互的组件;对 TTI 有一定压力。
- 先预渲染
client:visible
- 预渲染
HTML
,组件进入视口后再Hydration
。 - 适合非首屏交互组件(评论区、图表等);脚本默认预加载,非纯惰性。
- 预渲染
client:only
- 仅客户端渲染,无 SSR/SSG 预渲染。
- 适合强宿主依赖或非关键、轻量组件。
SPA 同步渲染(spa:sync-render
/ spa:sr
)
VitePress
在 SPA
路由切换时,Vue
内容同步更新;非 Vue
组件(如 React
)的预渲染 HTML
与客户端脚本加载是异步的,可能造成“闪烁”。spa:sr
通过将目标页面中使用该指令的组件的预渲染输出合并到 Vue
的客户端脚本,同步注入以消除闪烁。
默认规则:
- 使用
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 />
架构与运行时
- 编译期(transformHtml + Rollup)
- 扫描
.md
中的<script lang="react">
,静态解析import
,提取组件映射。 - 将形如
<MyComp ...>
的自定义标签替换为占位<div>
,注入内部属性:渲染指令、renderId
、组件名、spa:sr
标记等。 - 按页面聚合
React
依赖,分别产出:- 组件浏览器端
loader
脚本与对应的modulepreload
、CSS
依赖; SSR
产物与注入脚本(用于切页时补丁预渲染HTML
);React
/ReactDOM
的稳定chunk
(assets/chunks/react@x.js
、client@x.js
)。
- 组件浏览器端
- 扫描
- 开发期(SPA + HMR)
- 首屏与切页均在客户端根据占位节点信息进行渲染/
Hydration
。 - 对
client:load/client:visible/ssr:only
组件,在路由变更时先通过服务端SSR
返回HTML
片段,再完成客户端渲染/Hydration
,避免状态丢失与闪烁。
- 首屏与切页均在客户端根据占位节点信息进行渲染/
- 生产期(SSG + SPA 切页)
- 构建时生成全站
vrite-page-metafile.json
,记录每个页面React
组件的loader
脚本、modulepreload
与可选SSR
注入脚本。 - 初次进入页面先注入当前页必要脚本,渲染/水合后再按需预加载其他页面的入口脚本以优化后续切页。
- 构建时生成全站
- MPA 模式
- 单页预览互不跳转,无需全局 metafile;每页暴露自身所需脚本即可。
错误处理:Hydration
失败会自动回退到客户端渲染,保证用户体验不中断。
使用建议与限制
- 默认以
ssr:only
渲染,建议仅对需要交互的关键组件使用client:load
;非首屏交互组件用client:visible
;client:only
适合强宿主依赖或降级。 - 对开启
spa:sr
的组件保持克制,避免过多增大切页脚本负担。 - 目录结构需与路由保持一致,否则可能导致资源定位失败。
许可证
MIT