From 9219bd9c4cf39d40e79b36b280a0bc5689fae975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salvador=20de=20la=20Puente=20Gonz=C3=A1lez?= Date: Sat, 14 Nov 2015 00:59:29 +0100 Subject: [PATCH] New template --- lib/bootstrap.js | 4 +- lib/offline.js | 1 + templates/app/offline-worker.tmpl | 168 ++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 templates/app/offline-worker.tmpl diff --git a/lib/bootstrap.js b/lib/bootstrap.js index c085520f..dbe719ea 100644 --- a/lib/bootstrap.js +++ b/lib/bootstrap.js @@ -118,7 +118,9 @@ module.exports = function(config) { console.log('\nCreating files…'); return new Promise(function(resolve, reject) { - var stream = gulp.src([__dirname + '/../templates/**']) + var contents = __dirname + '/../templates/**'; + var workerTemplate = __dirname + '/../templates/app/offline-worker.tmpl'; + var stream = gulp.src([contents, '!' + workerTemplate]) .pipe(rename(function (path) { // NPM can't include a .gitignore file so we have to rename it. if (path.basename === 'gitignore') { diff --git a/lib/offline.js b/lib/offline.js index f6d98c9f..7497cf99 100644 --- a/lib/offline.js +++ b/lib/offline.js @@ -108,6 +108,7 @@ module.exports = function(config) { cacheId: cacheId, ignoreUrlParametersMatching: config.ignoreUrlParametersMatching || [/./], maximumFileSizeToCacheInBytes: Infinity, + templateFilePath: __dirname + '/../templates/app/offline-worker.tmpl', }); })); }); diff --git a/templates/app/offline-worker.tmpl b/templates/app/offline-worker.tmpl new file mode 100644 index 00000000..68f2e309 --- /dev/null +++ b/templates/app/offline-worker.tmpl @@ -0,0 +1,168 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +<% if (importScripts) { %> +importScripts(<%= importScripts %>); +<% } %> + +(function (self) { + 'use strict'; + + // On install, cache resources and skip waiting so the worker won't + // wait for clients to be closed before becoming active. + self.addEventListener('install', function (event) { + event.waitUntil(oghliner.cacheResources().then(function () { + if (typeof self.skipWaiting === 'function') { + return self.skipWaiting(); + } + })); + }); + + // On activation, delete old caches and start controlling the clients + // without waiting for them to reload. + self.addEventListener('activate', function (event) { + event.waitUntil(oghliner.activateNewContent().then(function () { + if (self.clients && typeof self.clients.claim === "function") { + return self.clients.claim(); + } + })); + }); + +<% if (handleFetch) { %> + // Retrieves the request following oghliner strategy. + self.addEventListener('fetch', function (event) { + if (event.request.method === 'GET') { + event.respondWith(oghliner.get(event.request)); + } + else { + event.respondWith(self.fetch(event.request)); + } + }); +<% } %> + + var oghliner = self.oghliner = { + + // This is the unique prefix for all the caches controlled by this worker. + CACHE_PREFIX: 'sw-precache-<%= version %>:<%= cacheId %>:' + (self.registration ? self.registration.scope : '') + ':', + + // This is the unique name for the cache controlled by this version of the worker. + get CACHE_NAME() { + return this.CACHE_PREFIX + 'offline-cache'; + }, + + // In order to avoid deleting the former content during installation, new contents are + // cached here until activation. + get TEMP_CACHE_NAME() { + return this.CACHE_NAME + ':new'; + }, + + // This is a list of resources that will be cached. Hashes are needed to include + // byte-level changes when these files change and thus, make the browser to update + // the service worker. + RESOURCES: [ + ['./'], +<% JSON.parse(precacheConfig).forEach(function (pathAndHash) { +%> ['<%= pathAndHash[0] %>', '<%= pathAndHash[1] %>'], +<% }); %> + ], + + // Adds the resources to the cache controlled by this worker. + cacheResources: function () { + var _this = this; + var now = Date.now(); + var baseUrl = self.location; + return _this.openNewCache() + .then(function (cache) { + return Promise.all(_this.RESOURCES.map(function (resource) { + resource = resource[0]; + + // Bust the request to get a fresh response + var url = new URL(resource, baseUrl); + var bustParameter = (url.search ? '&' : '') + '__bust=' + now; + var bustedUrl = new URL(url.toString()); + bustedUrl.search += bustParameter; + + // But cache the response for the original request + var requestConfig = { credentials: 'same-origin' }; + var originalRequest = new Request(url.toString(), requestConfig); + var bustedRequest = new Request(bustedUrl.toString(), requestConfig); + return fetch(bustedRequest).then(function (response) { + if (response.ok) { + return cache.put(originalRequest, response); + } + console.error('Error fetching ' + url + ', status was ' + response.status); + }); + })); + }); + }, + + // Get a response from the current offline cache or from the network. + get: function (request) { + return this.openCache() + .then(function (cache) { + return cache.match(request); + }) + .then(function (response) { + if (response) { + return response; + } + return self.fetch(request); + }); + }, + + // Open and cache the offline cache promise to improve the performance when + // serving from the offline-cache. + openCache: function () { + if (!this._cache) { + this._cache = self.caches.open(this.CACHE_NAME); + } + return this._cache; + }, + + // Called during installation, recreates a cache to put the new content without + // overwritting the old one. + openNewCache: function () { + var tempCache = this.TEMP_CACHE_NAME; + return self.caches.delete(tempCache).then(function () { + return self.caches.open(tempCache); + }); + }, + + // Called during activation, copies the new contents to the current cache. + activateNewContent: function () { + var tempCacheName = this.TEMP_CACHE_NAME; + var currentCacheName = this.CACHE_NAME; + + return prepareCaches(currentCacheName, tempCacheName).then(function (caches) { + var tempCache = caches[1]; + var currentCache = caches[0]; + + return getEntries(tempCache).then(function (entries) { + return Promise.all(entries.map(function (entry) { + return currentCache.put(entry.request, entry.response); + })); + }); + }).then(function () { + self.caches.delete(tempCacheName); + }); + + function prepareCaches(target, source) { + return self.caches.delete(target).then(function () { + return Promise.all([target, source].map(function (cacheName) { + return self.caches.open(cacheName); + })); + }); + } + + function getEntries(cache) { + return cache.keys().then(function (requests) { + return Promise.all(requests.map(function (request) { + return cache.match(request).then(function (response) { + return { request: request, response: response }; + }); + })); + }); + } + } + }; +}(self));