💡 痛点共鸣
作为开发者,你是否经历过这些崩溃时刻?
- 高铁上灵感迸发时,编辑器因断网提示"连接失败"
- 会议室演示时,网络波动导致文档无法保存
- 紧急修改文档时,发现本地缓存早已过期
传统在线编辑器的致命缺陷:网络依赖性强,用户体验被网络质量绑架。今天,我将揭秘如何用Service Worker打破这一枷锁,打造真正"离线优先"的Markdown编辑器。
技术架构全景图
graph LR
A[用户界面] --> B(Service Worker)
B --> C[缓存策略]
C --> D[网络代理]
D --> E{在线?}
E -->|是| F[实时同步]
E -->|否| G[本地操作]
G --> H[IndexedDB]
核心实现四部曲
1️⃣ Service Worker注册与安装
javascript
// 主线程注册SW
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', {
scope: '/',
type: 'module' // 支持ES模块
});
}
// sw.js - 安装阶段
const CACHE_NAME = 'md-editor-v1';
const PRE_CACHE = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/marked.min.js' // Markdown解析器
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(PRE_CACHE))
);
});
2️⃣ 智能缓存策略
javascript
// 动态缓存策略
self.addEventListener('fetch', (event) => {
// 对Markdown文件使用NetworkFirst策略
if (event.request.url.endsWith('.md')) {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
return;
}
// 静态资源使用CacheFirst策略
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
3️⃣ 离线数据持久化
javascript
// 使用IndexedDB存储离线内容
const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MarkdownDB', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('documents')) {
db.createObjectStore('documents', { keyPath: 'id' });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = reject;
});
};
// 保存文档
const saveDocument = async (content) => {
const db = await openDB();
const tx = db.transaction('documents', 'readwrite');
tx.objectStore('documents').put({
id: Date.now(),
content,
timestamp: new Date().toISOString()
});
};
4️⃣ 网络恢复自动同步
javascript
// 监听网络状态变化
const syncManager = navigator.serviceWorker.ready.then(reg => reg.sync);
window.addEventListener('online', async () => {
const db = await openDB();
const tx = db.transaction('documents', 'readonly');
const store = tx.objectStore('documents');
store.getAll().onsuccess = (event) => {
const docs = event.target.result;
docs.forEach(doc => {
fetch('/api/save', {
method: 'POST',
body: JSON.stringify(doc)
}).then(() => {
// 同步成功后清理本地缓存
store.delete(doc.id);
});
});
};
});
🚀 性能优化关键点
-
缓存分区策略
- 静态资源:永久缓存
- Markdown文件:缓存24小时
- 用户数据:仅离线时缓存
-
增量更新机制
javascript// 只同步修改部分 const diffSync = (prev, current) => { const changes = []; for (let i = 0; i < current.length; i++) { if (i >= prev.length || prev[i] !== current[i]) { changes.push({ index: i, char: current[i] }); } } return changes; };
-
存储空间管理
javascript// 自动清理旧缓存 const purgeOldCache = async () => { const keys = await caches.keys(); const current = keys.filter(k => k === CACHE_NAME); for (const key of keys) { if (!current.includes(key)) { await caches.delete(key); } } };
⚠️ 避坑指南
-
缓存中毒问题
- 始终对用户输入内容进行消毒
- 使用
DOMPurify
防止XSS攻击
-
版本控制陷阱
javascript// 每次更新时变更缓存名称 const version = 'v2'; // 随版本更新 const CACHE_NAME = `md-editor-${version}`;
-
存储限制处理
javascript// 检查存储配额 navigator.storage.estimate().then(estimate => { const used = (estimate.usage / estimate.quota * 100).toFixed(1); if (used > 80) showStorageWarning(); });
🌐 完整实现效果
-
离线体验
- 断网状态下仍可编辑保存
- 历史版本自动保留
-
无缝切换
- 网络恢复自动同步
- 冲突内容智能合并
-
性能指标
场景 首次加载(ms) 二次加载(ms) 在线 1200 300 离线 - 150
最后思考:Service Worker不是银弹,但确实是构建离线应用的最佳武器。当你的编辑器在网络荒漠中依然坚挺时,用户收获的不仅是功能,更是安全感。现在就去实现它,让"网络不可用"成为历史名词!