本案例演示如何从一个zipfile添加内容到缓存。
中级
作为一名web开发人员,我希望将应用程序作为数个单独的压缩包分发,这样既可以减少HTTP请求数量又可以提供一种隐式的方法来列出所有资源供离线使用。
在安装service worker的时候,下载zipfile并解压,将资源加入缓存。
离线扩展
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Cache from ZIP - ServiceWorker Cookbook</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: monospace;
font-size: 1rem;
white-space: pre-wrap;
}
p {
text-align: center;
}
</style>
</head>
<body>
<section id="install-notice">
We are going to simulate installing and uninstalling a web application
in the browser. To do that, enable logs from service workers to be shown
and click on install button.
<button id="install">Install</button>
</section>
<section id="images" hidden>
Choose from one the images to be loaded: <select>
<option value="imgs/a.jpeg">Collaboration</option>
<option value="imgs/b.jpeg">Loneliness</option>
<option value="imgs/c.jpeg">Bread</option>
</select> <button id="load-image">Load</button>
<p><img src="" alt="" /></p>
Now you can go offline, restart the browser and check how the page
continues working. Once you convince yourself, click on uninstall.
<button id="uninstall">Uninstall</button>
</section>
<pre id="results"></pre>
<script src="./index.js"></script>
</body>
</html>
index.js
var $ = document.querySelector.bind(document);
// 通过监测controller验证应用是否已经完成装载
// 我们假设一旦service worker控制页面,那么则认为应用已经加载完成
navigator.serviceWorker.getRegistration().then(function(registration) {
if (registration && registration.active) {
showImagesSection();
}
});
// 在安装阶段,一旦service worker被激活就将image动态加载选项显示
navigator.serviceWorker.oncontrollerchange = function() {
if (navigator.serviceWorker.controller) {
logInstall('The application has been installed');
showImagesSection();
}
};
// 这里装载service worker,它负责下载资源压缩包,解压和将资源添加到缓存
$('#install').onclick = function() {
navigator.serviceWorker.register('worker.js').then(function() {
logInstall('Installing...');
}).catch(function(error) {
logInstall('An error happened during installing the service worker:');
logInstall(error.message);
});
};
// 这里的卸载只是卸载service worker并没有删除缓存的资源,这些资源实际上还是加载进来了,只不过没有service worker控制它们
$('#uninstall').onclick = function() {
navigator.serviceWorker.getRegistration().then(function(registration) {
if (!registration) { return; }
registration.unregister()
.then(function() {
logUninstall('The application has been uninstalled');
setTimeout(function() { location.reload(); }, 500);
})
.catch(function(error) {
logUninstall('Error while uninstalling the service worker:');
logUninstall(error.message);
});
});
};
// 加载图片只不过是在显示器上显示正确的URL
$('#load-image').onclick = function() {
$('img').src = $('select').value;
};
// 一系列控制UI的工具方法
function showImagesSection() {
$('#images').hidden = false;
$('#install-notice').hidden = true;
}
function logInstall(what) {
log(what, 'Install');
}
function logUninstall(what) {
log(what, 'Uninstall');
}
function log(what, tag) {
var label = '[' + tag + ']';
console.log(label, what);
$('#results').textContent += label + ' ' + what + '\n';
}
worker.js
importScripts('./lib/zip.js');
importScripts('./lib/ArrayBufferReader.js');
importScripts('./lib/deflate.js');
importScripts('./lib/inflate.js');
var ZIP_URL = './package.zip';
zip.useWebWorkers = false;
// 在install阶段,扩展event以恢复此案例中用到的包并安装到离线缓存中
self.oninstall = function(event) {
event.waitUntil(
fetch(ZIP_URL)
.then(function(response) {
return response.arrayBuffer();
})
.then(getZipReader)
.then(cacheContents)
.then(self.skipWaiting.bind(self)) // control clients ASAP
);
};
// service worker立即接管客户端
self.onactivate = function(event) {
event.waitUntil(self.clients.claim());
};
// 用缓存响应请求,如果缓存没有就走网络请求
self.onfetch = function(event) {
event.respondWith(openCache().then(function(cache) {
return cache.match(event.request).then(function(response) {
return response || fetch(event.request);
});
}));
};
// 使用zip.js的API读取zip文件
function getZipReader(data) {
return new Promise(function(fulfill, reject) {
zip.createReader(new zip.ArrayBufferReader(data), fulfill, reject);
});
}
// 用reader读取zipfile中的每个文件再将他们添加到离线缓存
function cacheContents(reader) {
return new Promise(function(fulfill, reject) {
reader.getEntries(function(entries) {
console.log('Installing', entries.length, 'files from zip');
Promise.all(entries.map(cacheEntry)).then(fulfill, reject);
});
});
}
// 跳过文件夹缓存文件
function cacheEntry(entry) {
if (entry.directory) { return Promise.resolve(); }
return new Promise(function(fulfill, reject) {
// writer指定读取的数据格式
// 这个例子中我们想要一个通用的blob,因为blob是response构造函数支持的格式之一
entry.getData(new zip.BlobWriter(), function(data) {
return openCache().then(function(cache) {
var location = getLocation(entry.filename);
var response = new Response(data, { headers: {
// 由于zip文件没有说明文件的性质,
'Content-Type': getContentType(entry.filename)
} });
console.log('-> Caching', location,'(size:', entry.uncompressedSize, 'bytes)');
// 如果entry文件是index,那么则将其加入根域缓存
if (entry.filename === 'index.html') {
// 由于响应是一次性对象,因此.put()方法克隆响应主体中的数据以便再次使用
cache.put(getLocation(), response.clone());
}
return cache.put(location, response);
}).then(fulfill, reject);
});
});
}
// 返回入口location
function getLocation(filename) {
return location.href.replace(/worker\.js$/, filename || '');
}
var contentTypesByExtension = {
'css': 'text/css',
'js': 'application/javascript',
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'html': 'text/html',
'htm': 'text/html'
};
// 根据文件扩展名返回响应类型
function getContentType(filename) {
var tokens = filename.split('.');
var extension = tokens[tokens.length - 1];
return contentTypesByExtension[extension] || 'text/plain';
}
// 由于开启缓存是开销昂贵的操作,
// 因此我们只打开缓存一次,将返回的promise对象缓存起来
var cachePromise;
function openCache() {
if (!cachePromise) { cachePromise = caches.open('cache-from-zip'); }
return cachePromise;
}