docs(claude): 添加 Service Worker 预缓存机制文档
记录了 Precache Manifest 机制的工作原理: - 构建时生成资源清单 - SW 安装时预缓存所有资源 - 增量更新跳过未变化文件
This commit is contained in:
1 parent
1512fd7960
commit
c26bd23da4
2 files changed
+60
-2
No files matched your search
@@ -587,6 +587,63 @@ taskQueueService.observeTaskUpdates().subscribe((event) => {
|
||||
});
|
||||
```
|
||||
|
||||
### Service Worker 预缓存机制 (Precache Manifest)
|
||||
|
||||
项目使用 **Precache Manifest** 机制确保版本更新时用户能快速加载新版本:
|
||||
|
||||
**问题**:如果 SW 只预缓存少量基础文件(如 index.html),版本更新后用户首次访问需要从网络下载所有 JS/CSS,导致加载慢。
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **构建时生成资源清单** (`vite.config.ts`):
|
||||
```typescript
|
||||
// Vite 插件在构建完成后扫描 dist 目录
|
||||
function precacheManifestPlugin(): Plugin {
|
||||
return {
|
||||
name: 'precache-manifest',
|
||||
closeBundle: {
|
||||
async handler() {
|
||||
// 扫描所有静态资源,生成 precache-manifest.json
|
||||
// 包含 URL 和文件哈希(用于增量更新)
|
||||
manifest.push({ url: '/assets/xxx.js', revision: 'a1b2c3d4' });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
2. **SW 安装时预缓存所有资源** (`sw/index.ts`):
|
||||
```typescript
|
||||
sw.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const files = await loadPrecacheManifest(); // 读取 manifest
|
||||
await precacheStaticFiles(cache, files); // 并发预缓存
|
||||
})()
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
3. **增量更新**:通过 `x-sw-revision` 头比较文件哈希,跳过未变化的文件
|
||||
|
||||
**工作流程**:
|
||||
```
|
||||
版本更新发布 → 用户访问 → 新 SW 安装
|
||||
↓
|
||||
读取 precache-manifest.json
|
||||
↓
|
||||
并发预缓存所有资源(6个并发)
|
||||
↓
|
||||
预缓存完成 → 通知用户更新
|
||||
↓
|
||||
用户确认 → 激活新 SW → 缓存优先秒开
|
||||
```
|
||||
|
||||
**相关文件**:
|
||||
- `apps/web/vite.config.ts` - `precacheManifestPlugin()` 生成 manifest
|
||||
- `apps/web/src/sw/index.ts` - `loadPrecacheManifest()` 和 `precacheStaticFiles()`
|
||||
- `dist/apps/web/precache-manifest.json` - 构建产物
|
||||
|
||||
### 编辑器插件系统
|
||||
```
|
||||
Drawnix (主编辑器)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DialogType, useDrawnix } from '../../hooks/use-drawnix';
|
||||
import MarkdownToDrawnix from './markdown-to-drawnix';
|
||||
import AIImageGeneration from './ai-image-generation';
|
||||
import AIVideoGeneration from './ai-video-generation';
|
||||
import type { ReferenceImage } from './shared/ReferenceImageUpload';
|
||||
import { useI18n } from '../../i18n';
|
||||
import { useBoard } from '@plait-board/react-board';
|
||||
import React, { useState, useEffect, useRef, memo, useCallback, lazy, Suspense } from 'react';
|
||||
@@ -83,7 +84,7 @@ const TTDDialogComponent = ({ container }: { container: HTMLElement | null }) =>
|
||||
// AI 图片生成的初始数据
|
||||
const [aiImageData, setAiImageData] = useState<{
|
||||
initialPrompt: string;
|
||||
initialImages: (File | { url: string; name: string })[];
|
||||
initialImages: ReferenceImage[];
|
||||
selectedElementIds: string[]; // 保存选中元素的IDs
|
||||
initialResultUrl?: string; // 初始结果URL,用于显示预览
|
||||
}>({
|
||||
@@ -224,7 +225,7 @@ const TTDDialogComponent = ({ container }: { container: HTMLElement | null }) =>
|
||||
const processedContent = await processSelectedContentForAI(board, selectedElementIds);
|
||||
|
||||
// 准备图片列表
|
||||
const imageItems: (File | { url: string; name: string })[] = [];
|
||||
const imageItems: ReferenceImage[] = [];
|
||||
|
||||
// 1. 先添加剩余的图片(非重叠的图片)
|
||||
processedContent.remainingImages.forEach(image => {
|
||||
|
||||
Reference in new issue
Block a user