字节笔记本
2026年2月20日
SSR Stubs 技术详解:优化 Cloudflare Workers 部署的包体积
SSR stubs(服务端渲染存根)是一种优化技术,用于在构建时将仅客户端使用的重型库替换为空实现,从而显著减小 SSR bundle 的体积。本文深入解析其原理、实现方式及在 Cloudflare Workers 部署中的实际应用。
什么是 SSR Stubs
SSR stubs 是一种构建时优化技术,核心思想是:
在服务端渲染(SSR)过程中,某些库只在浏览器环境使用,服务端并不需要。通过将这些库替换为"空实现"(stub),可以大幅减小服务端 bundle 体积。
典型应用场景
| 场景 | 客户端需求 | 服务端需求 | 优化效果 |
|---|---|---|---|
| 图表库(Mermaid) | 渲染流程图 | 无需渲染 | 减少 ~2 MiB |
| 代码高亮(Shiki) | 语法高亮 | 无需高亮 | 减少 ~500 KiB |
| WASM 模块 | 高性能计算 | 不兼容 Workers | 避免运行时错误 |
| 富文本编辑器 | 交互编辑 | 仅展示 HTML | 减少 ~1 MiB |
工作原理
构建流程
源代码
↓
Vite 构建
├── 客户端构建 → 完整库代码
└── SSR 构建 → stubs 替换重型库
↓
生成精简的 SSR bundle实现机制
通过 Vite 插件在 SSR 构建时拦截模块导入:
// vite.config.ts
const ssrOnlyStubs = () => ({
name: 'ssr-only-stubs',
enforce: 'pre',
resolveId(id, importer, { ssr }) {
// 仅在 SSR 构建时生效
if (!ssr) return null;
// 匹配需要 stub 的库
if (id === 'beautiful-mermaid') {
return '\0stub:beautiful-mermaid';
}
},
load(id) {
if (id === '\0stub:beautiful-mermaid') {
// 返回空实现
return 'export default () => {}';
}
}
});VibeAny 中的实现
项目中的 Stubs 配置
在 feat/cloudflare 分支的 vite.config.ts 中:
const ssrOnlyStubs = () => ({
name: 'ssr-only-stubs',
enforce: 'pre',
resolveId(id, importer, { ssr }) {
if (!ssr) return null;
// Mermaid 相关库 - 客户端渲染图表
if (id.includes('beautiful-mermaid') ||
id.includes('@streamdown/')) {
return '\0stub:noop';
}
// Shiki 语言定义 - 客户端代码高亮
if (id.includes('shiki/langs/')) {
return '\0stub:empty-array';
}
// WASM 引擎 - Workers 不兼容
if (id.includes('@shikijs/engine-oniguruma')) {
return '\0stub:js-engine';
}
},
load(id) {
switch (id) {
case '\0stub:noop':
return 'export default () => {};';
case '\0stub:empty-array':
return 'export default [];';
case '\0stub:js-engine':
return 'export { createJavaScriptRegexEngine } from "@shikijs/engine-javascript";';
}
}
});包体积优化效果
| 优化项 | 原始大小 | 优化后 | 节省 |
|---|---|---|---|
| Mermaid + Cytoscape | ~2.0 MiB | 0 | ~2.0 MiB |
| Shiki 语言定义 | ~500 KiB | 0 | ~500 KiB |
| WASM 运行时 | ~200 KiB | JS 引擎 ~50 KiB | ~150 KiB |
| 总计 | ~2.7 MiB | ~50 KiB | ~2.65 MiB |
与 Cloudflare Workers 的结合
Workers 的限制
Cloudflare Workers 免费版有以下限制:
- Bundle 大小: gzip 后不超过 3 MiB
- 运行时: 不支持 Node.js 原生模块
- WASM: 需要特殊配置,部分功能受限
为什么 SSR Stubs 对 Workers 至关重要
未优化前的 bundle 分析:
原始 SSR bundle:
├── React/Vue 框架代码 ~300 KiB
├── 业务逻辑代码 ~200 KiB
├── Mermaid 图表库 ~1200 KiB ❌ 服务端不需要
├── Cytoscape 图论库 ~800 KiB ❌ 服务端不需要
├── Shiki 语法定义 ~500 KiB ❌ 服务端不需要
├── WASM 运行时 ~200 KiB ❌ Workers 不兼容
└── 其他依赖 ~300 KiB
总计: ~3.5 MiB (超过 3 MiB 限制 ❌)使用 SSR stubs 优化后:
优化后 SSR bundle:
├── React/Vue 框架代码 ~300 KiB
├── 业务逻辑代码 ~200 KiB
├── Mermaid stub ~0 KiB ✅
├── Cytoscape stub ~0 KiB ✅
├── Shiki 精简版 ~50 KiB ✅
├── JS 正则引擎 ~50 KiB ✅
└── 其他依赖 ~300 KiB
总计: ~900 KiB (远低于 3 MiB 限制 ✅)实现自定义 Stub
步骤 1: 识别重型客户端库
分析 bundle 找出大体积的仅客户端库:
# 使用 wrangler 分析 bundle 大小
wrangler deploy --dry-run --outdir .wrangler/dist
# 查看各文件大小
ls -lh .wrangler/dist/步骤 2: 创建 Stub 模块
// stubs/mermaid.ts
export default {
render: () => Promise.resolve(''),
initialize: () => {},
};
// stubs/cytoscape.ts
export default () => ({
add: () => {},
layout: () => ({ run: () => {} }),
});步骤 3: 配置 Vite 插件
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
{
name: 'custom-ssr-stubs',
enforce: 'pre',
resolveId(id, importer, { ssr }) {
if (!ssr) return null;
// 映射到本地 stub 文件
const stubs: Record<string, string> = {
'mermaid': '/stubs/mermaid.ts',
'cytoscape': '/stubs/cytoscape.ts',
};
if (stubs[id]) {
return stubs[id];
}
}
}
]
});最佳实践
1. 渐进式采用
不要一次性 stub 所有库,按优先级逐步优化:
- 高优先级: 体积 > 500 KiB 的纯客户端库
- 中优先级: 体积 100-500 KiB 的非关键库
- 低优先级: 体积 < 100 KiB 的库(收益有限)
2. 保持客户端功能完整
SSR stubs 只影响服务端渲染,客户端行为保持不变:
// 组件代码无需修改
import Mermaid from 'beautiful-mermaid';
function Diagram({ code }) {
useEffect(() => {
// 客户端正常执行
Mermaid.render(code);
}, []);
// 服务端返回占位符
return <div className="mermaid-placeholder">{code}</div>;
}3. 动态导入进一步优化
对于非首屏必需的库,使用动态导入:
// 代替静态导入
// import HeavyChart from 'heavy-chart-lib';
// 使用动态导入
const HeavyChart = lazy(() => import('heavy-chart-lib'));
function ChartPage() {
return (
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
);
}常见问题
"Module not found" 错误
确保 stub 导出的 API 与实际库兼容:
// 错误:缺少必要导出
export default {};
// 正确:模拟库的 API 结构
export default {
render: () => Promise.resolve(''),
parse: () => ({}),
initialize: () => {},
};类型定义丢失
为 stub 创建类型声明文件:
// stubs/mermaid.d.ts
declare module 'beautiful-mermaid' {
export function render(code: string): Promise<string>;
export function initialize(config: any): void;
}客户端 hydrate 不匹配
确保服务端 stub 返回的 HTML 结构与客户端一致:
// 服务端 stub
export function renderToString() {
// 返回与客户端一致的占位结构
return '<div class="chart-container"></div>';
}总结
SSR stubs 是解决 Cloudflare Workers 3 MiB 限制的有效方案:
| 优势 | 说明 |
|---|---|
| 零运行时开销 | 构建时完成替换,不影响运行时性能 |
| 透明化 | 组件代码无需修改,客户端行为不变 |
| 可扩展 | 易于添加新的 stub 规则 |
| 类型安全 | 可配合 TypeScript 类型定义 |
通过合理使用 SSR stubs,VibeAny 项目成功将 SSR bundle 从 3.5 MiB 优化到 900 KiB,同时保持完整的客户端功能。
相关文章
-
VibeAny 部署到 Cloudflare Workers 完全指南 - 了解完整的部署流程
-
Shiki 代码高亮完全指南 - 了解 Shiki 在 Workers 环境中的特殊配置
-
Mermaid 图表绘制完全指南 - 了解图表库的 Workers 适配方案
-
Tiptap 富文本编辑器完全指南 - 了解富文本编辑器的 SSR stubs 配置