概述
在本文中, 您将学习如何使用 Workbox 和 IndexedDB 创建离线优先, 数据驱动的渐进式 web 应用程序 (PWA). 在离线的情况下也可以使用后台同步功能将应用程序与服务器同步.
将会学习到
如何使用 Workbox 缓存应用程序
如何使用 IndexedDB 存储数据
如何在用户脱机时从 IndexedDB 中检索和显示数据
脱机时如何保存数据
如何在脱机时使用后台同步更新应用程序
应该了解的
ES2015 Promises
如何使用命令行
熟悉一下 Workbox
熟悉一下 Gulp
熟悉一下 IndexedDB
需具备的条件
拥有 terminal/shell 访问权限的电脑
Chrome 52 或更高版本
编辑器
Nodejs 和 npm
设置
如果你没有安装 Nodejs 需要安装一下
之后通过下面的方式 clone 快速启动仓库
git clone https://github.com/googlecodelabs/workbox-indexeddb.git
或者直接下载 压缩包
安装依赖并启动服务
到下载好的 git 仓库目录中, 转到 project 文件夹
cd workbox-indexeddb/project/
然后安装依赖并启动服务
npm install
npm start
说明
这个步骤中会根据 package.json 定义的依赖并安装, 打开 package.json 文件查看, 有很多依赖, 大部分是开发环境需要的 (你可以忽略), 主要的依赖是:
workbox-sw Workbox
workbox-background-sync 是 Workbox 用来后台同步的, 稍后会提到
gulp 和 workbox-build 是构建工具
npm start 会构建并输出到 build 文件夹, 启动 dev server, 并且会开启一个 gulp watch 任务. gulp watch 会监听文件的修改自动构建. concurrently 可以同时跑 gulp 和 dev server
打开应用
打开 Chrome 并且跳转到 localhost:8081 你会看到一个事件列表的控制台, 在弹出的权限确认菜单中点击允许
我们使用通知系统来告知用户 app 的后台同步已经更新, 试着测试一下页面底部的添加功能
说明
这个小项目的目标是离线保存用户的事件日历. 你可以查看一下 app/js/main.js 文件的
loadContentNetworkFirst
方法当前是怎么工作的, 首先会请求 server, 成功则更新页面, 失败会在控制台打印一个信息, 目前脱机是无法使用的, 接下来我们添加一些方法使它脱机可用.
缓存 app shell
编写 service worker
要想脱机工作, 就需要 server worker, 现在写一个.
把下面的代码添加到 app/src/sw.js
importScripts('workbox-sw.dev.v2.0.0.js');
importScripts('workbox-background-sync.dev.v2.0.0.js');
const workboxSW = new WorkboxSW();
workboxSW.precache([]);
说明
在开头我们引入了 workbox-sw 和
workbox-background-sync
workbox-sw 包含了 precache 和向 service worker 添加路由的方法
workbox-background-sync
是在 service worker 中后台同步的库, 稍后会提到
precache 方法接收一个文件列表的数组, 先用一个空的, 下一步我们会用 workbox-build 去计算出这个数组的结果.
构建 service worker
推荐使用 Workbox 的构建模块, 比如 workbox-build
把下面的代码添加进 project/gulpfile.js
gulp.task('build-sw', () => {
return wbBuild.injectManifest({
swSrc: 'app/src/sw.js',
swDest: 'build/service-worker.js',
globDirectory: 'build',
staticFileGlobs: [
'style/main.css',
'index.html',
'js/idb-promised.js',
'js/main.js',
'images/**/*.*',
'manifest.json'
],
templatedUrls: {
'/': ['index.html']
}
}).catch((err) => {
console.log('[ERROR] This happened:' + err);
});
});
现在取消一些注释:
gulpfile.js:
// uncomment the line below:
const wbBuild = require('workbox-build');
// ...
gulp.task('default', ['clean'], cb => {
runSequence(
'copy',
// uncomment the line below:
'build-sw',
cb
);
});
保存修改, 因为修改了 gulp, 我们得重新跑一下, Ctrl + C 退出当前的进程, 重新运行 npm start, 会看到 service worker 的文件被生成在了
build/service-worker.js
取消 app/index.html 中 service worker 注册代码的注释
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
console.log('Service Worker registration successful with scope:',
registration.scope);
})
.catch(function(err) {
console.log('Service Worker registration failed:', err);
});
}
保存修改, 刷新浏览器 service worker 就会被安装. Ctrl + C 关闭 dev server, 再返回到浏览器中刷新页面, 已经可以脱机运行了!
说明
在这一步中, workbox-build 和 build-sw 任务被合并到我们的 gulp 文件中, 我们的构建过程是使用 workbox-build 库来从
swSrc(app/src/sw.js)
中生成 service work 到
swDest(build / service - worker.js) globDirectory(build) build / service - worker.js
function createIndexedDB() {
if (! ('indexedDB' in window)) {
return null;
}
return idb.open('dashboardr', 1,
function(upgradeDb) {
if (!upgradeDb.objectStoreNames.contains('events')) {
const eventsOS = upgradeDb.createObjectStore('events', {
keyPath: 'id'
});
}
})
}
const dbPromise = createIndexedDB();
npm start
function saveEventDataLocally(events) {
if (! ('indexedDB' in window)) {
return null;
}
return dbPromise.then(db = >{
const tx = db.transaction('events', 'readwrite');
const store = tx.objectStore('events');
return Promise.all(events.map(event = >store.put(event))).
catch(() = >{
tx.abort();
throw Error('Events were not added to the store');
});
});
}
function loadContentNetworkFirst() {
getServerData().then(dataFromNetwork = >{
updateUI(dataFromNetwork);
saveEventDataLocally(dataFromNetwork).then(() = >{
setLastUpdated(new Date());
messageDataSaved();
}).
catch(err = >{
messageSaveError();
console.warn(err);
});
}).
catch(err = >{ // if we can't connect to the server...
console.log('Network requests have failed, this is expected if offline');
});
}
function addAndPostEvent() {
// ...
saveEventDataLocally([data]);
// ...
}
function getLocalEventData() {
if (! ('indexedDB' in window)) {
return null;
}
return dbPromise.then(db = >{
const tx = db.transaction('events', 'readonly');
const store = tx.objectStore('events');
return store.getAll();
});
}
function loadContentNetworkFirst() {
getServerData().then(dataFromNetwork = >{
updateUI(dataFromNetwork);
saveEventDataLocally(dataFromNetwork).then(() = >{
setLastUpdated(new Date());
messageDataSaved();
}).
catch(err = >{
messageSaveError();
console.warn(err);
});
}).
catch(err = >{
console.log('Network requests have failed, this is expected if offline');
getLocalEventData().then(offlineData = >{
if (!offlineData.length) {
messageNoData();
} else {
messageOffline();
updateUI(offlineData);
}
});
});
}
let bgQueue = new workbox.backgroundSync.QueuePlugin({
callbacks: {
replayDidSucceed: async(hash, res) = >{
self.registration.showNotification('Background sync demo', {
body: 'Events have been updated!'
});
}
}
});
workboxSW.router.registerRoute('/api/add', workboxSW.strategies.networkOnly({
plugins: [bgQueue]
}), 'POST');
npm run start workboxSW.router.registerRoute('/api/getAll', () = >{
return bgQueue.replayRequests().then(() = >{
return fetch('/api/getAll');
}).
catch(err = >{
return err;
});
});
window.addEventListener('online', () = >{
container.innerHTML = '';
loadContentNetworkFirst();
});
npm start npm start
来源: https://www.cnblogs.com/wenpengfei/p/8397856.html