broccoli-serviceworker
Advanced tools
Comparing version 0.0.4 to 0.0.5
@@ -23,4 +23,4 @@ var path = require('path'); | ||
enabled: this.app.env === 'production', | ||
excludePaths: ['test.*'], | ||
includePaths: ['/'], | ||
excludePaths: ['test.*','robots.txt'], | ||
precacheURLs: [] | ||
}; | ||
@@ -52,3 +52,3 @@ | ||
contentFor: function(type, config) { | ||
if (config.environment !== 'test' && type === 'body-footer') { | ||
if (config.environment !== 'test' && (!config.serviceWorker || config.serviceWorker.includeRegistration!== false) && type === 'body-footer') { | ||
return stringifile('registration.js', 'script', __dirname); | ||
@@ -55,0 +55,0 @@ } |
if ('serviceWorker' in navigator) { | ||
navigator.serviceWorker.register('./service-worker.js', {scope: './'}) | ||
.catch(function(error) { | ||
alert('Error registering service worker:'+error); | ||
console.error('Error registering service worker:'+error); | ||
}); | ||
} else { | ||
alert('service worker not supported'); | ||
} | ||
console.log('service worker not supported'); | ||
} |
@@ -6,3 +6,5 @@ var fs = require("fs"); | ||
var funnel = require('broccoli-funnel'); | ||
var swCachePolyFillFile = require.resolve('serviceworker-cache-polyfill'); | ||
var swToolboxFile = require.resolve('sw-toolbox/sw-toolbox.js'); | ||
var swToolboxMapFile = require.resolve('sw-toolbox/sw-toolbox.map.json'); | ||
var toolboxLocation = 'sw-toolbox.js'; | ||
@@ -15,11 +17,22 @@ var BroccoliServiceWorker = function BroccoliServiceWorker(inTree, options) { | ||
options = options || {}; | ||
this.addPolyfill = options.addPolyfill || true; | ||
this.skipWaiting = options.skipWaiting || false; | ||
this.debug = options.debug || true; | ||
if (options.skipWaiting === false) { | ||
this.skipWaiting = false; | ||
} else { | ||
this.skipWaiting = true; | ||
} | ||
this.debug = options.debug || false; | ||
this.dynamicCache = options.dynamicCache || []; | ||
this.excludePaths = options.excludePaths || ['tests/*']; | ||
this.fallback = options.fallback || []; | ||
this.includePaths = options.includePaths || []; | ||
this.polyFillLocation = options.polyFillLocation || 'serviceworker-cache-polyfill.js'; | ||
this.serviceWorkerFile = options.serviceWorkerFile || "service-worker.js"; | ||
this.precacheURLs = options.precacheURLs || []; | ||
if (options.includeRegistration === false) { | ||
this.includeRegistration = false; | ||
} else { | ||
this.includeRegistration = true; | ||
} | ||
if (this.includeRegistration === true || !options.serviceWorkerFile) { | ||
this.serviceWorkerFile = "service-worker.js"; | ||
} else { | ||
this.serviceWorkerFile = options.serviceWorkerFile; | ||
} | ||
}; | ||
@@ -31,3 +44,2 @@ | ||
BroccoliServiceWorker.prototype.write = function(readTree, destDir) { | ||
var addPolyfill = this.addPolyfill; | ||
var skipWaiting = this.skipWaiting; | ||
@@ -37,4 +49,3 @@ var debug = this.debug; | ||
var fallback = this.fallback; | ||
var includePaths = this.includePaths; | ||
var polyFillLocation = this.polyFillLocation; | ||
var precacheURLs = this.precacheURLs; | ||
var serviceWorkerFile = this.serviceWorkerFile; | ||
@@ -46,36 +57,12 @@ var serviceWorkerTree = funnel(this.inTree, { | ||
return readTree(serviceWorkerTree).then(function (srcDir) { | ||
var cacheVersion = (new Date()).getTime(); | ||
var lines = []; | ||
if (addPolyfill) { | ||
lines.push("importScripts('"+polyFillLocation+"');"); | ||
lines.push("importScripts('"+toolboxLocation+"');"); | ||
lines.push("var CACHE_PREFIX = 'brocsw-v';"); | ||
lines.push("var CACHE_VERSION = CACHE_PREFIX+'"+(new Date()).getTime()+"';"); | ||
lines.push("toolbox.options.cache.name = CACHE_VERSION;"); | ||
if (debug) { | ||
lines.push("toolbox.options.debug = true;"); | ||
} | ||
lines.push("var CACHE_VERSION = '" + cacheVersion + "';"); | ||
lines.push("var CURRENT_CACHES = {"); | ||
lines.push(" prefetch: 'prefetch-cache-v' + CACHE_VERSION"); | ||
lines.push("};"); | ||
if (dynamicCache.length) { | ||
lines.push("CURRENT_CACHES['dynamic'] = 'dynamic-cache-v' + CACHE_VERSION;"); | ||
} | ||
if (dynamicCache.length) { | ||
lines.push("var DYNAMIC_URLS = ["); | ||
dynamicCache.forEach(function(cacheURL, idx, array) { | ||
lines.push(createArrayLine("new RegExp('"+escapeRegExp(cacheURL)+"')", idx, array.length)); | ||
}); | ||
lines.push("];"); | ||
} | ||
if (fallback.length) { | ||
lines.push("var FALLBACK_URLS = ["); | ||
fallback.forEach(function(fallback, idx, array) { | ||
var fallbackParts = fallback.split(' '); | ||
if (fallbackParts.length > 1) { | ||
var matchLine = "{match: new RegExp('"+escapeRegExp(fallbackParts[0])+"'), fallback:'"+fallbackParts[1]+"'}"; | ||
lines.push(createArrayLine(matchLine, idx, array.length)); | ||
} | ||
}); | ||
lines.push("];"); | ||
} | ||
lines.push("self.addEventListener('install', function(event) {"); | ||
lines.push(" var urlsToPrefetch = ["); | ||
lines.push("var urlsToPrefetch = ["); | ||
lines.push(" '/',"); | ||
getFilesRecursively(srcDir, [ "**/*" ]).forEach(function (file, idx, array) { | ||
@@ -90,146 +77,41 @@ var srcFile = path.join(srcDir, file); | ||
lines.push("];"); | ||
includePaths.forEach(function (file, idx, array) { | ||
precacheURLs.forEach(function (file, idx, array) { | ||
lines.push("urlsToPrefetch.push('"+file+"');"); | ||
}); | ||
//ServiceWorker code derived from examples at https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker | ||
addDebugLine("'Handling install event. Resources to pre-fetch:', urlsToPrefetch", debug, lines); | ||
if (skipWaiting) { | ||
lines.push(" if (self.skipWaiting) { self.skipWaiting(); }"); | ||
} | ||
lines.push(" event.waitUntil("); | ||
lines.push(" caches.open(CURRENT_CACHES['prefetch']).then(function(cache) {"); | ||
lines.push(" return cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {"); | ||
lines.push(" return new Request(urlToPrefetch, {mode: 'no-cors'});"); | ||
lines.push(" })).then(function() {"); | ||
addDebugLine("'All resources have been fetched and cached.'", debug, lines); | ||
lines.push(" });"); | ||
lines.push(" }).catch(function(error) {"); | ||
lines.push(" console.error('Pre-fetching failed:', error);"); | ||
lines.push(" })"); | ||
lines.push(" );"); | ||
lines.push("urlsToPrefetch.forEach(function(url) {"); | ||
lines.push(" toolbox.router.any(url, toolbox.cacheFirst);"); | ||
lines.push("});"); | ||
lines.push("self.addEventListener('activate', function(event) {"); | ||
lines.push(" // Delete all caches that aren't named in CURRENT_CACHES."); | ||
lines.push(" // While there is only one cache in this example, the same logic will handle the case where"); | ||
lines.push(" // there are multiple versioned caches."); | ||
lines.push(" var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {"); | ||
lines.push(" return CURRENT_CACHES[key];"); | ||
lines.push(" });"); | ||
lines.push(" event.waitUntil("); | ||
lines.push(" caches.keys().then(function(cacheNames) {"); | ||
lines.push(" return Promise.all("); | ||
lines.push(" cacheNames.map(function(cacheName) {"); | ||
lines.push(" if (expectedCacheNames.indexOf(cacheName) === -1) {"); | ||
lines.push(" // If this cache name isn't present in the array of \"expected\" cache names, then delete it."); | ||
addDebugLine("'Deleting out of date cache:', cacheName", debug, lines); | ||
lines.push(" return caches.delete(cacheName);"); | ||
lines.push(" }"); | ||
lines.push(" })"); | ||
lines.push(" );"); | ||
lines.push(" })"); | ||
lines.push(" );"); | ||
lines.push("});"); | ||
lines.push("self.addEventListener('fetch', function(event) {"); | ||
addDebugLine("'Handling fetch event for', event.request.url", debug, lines); | ||
lines.push("toolbox.precache(urlsToPrefetch);"); | ||
if (dynamicCache.length) { | ||
lines.push(" if(dynamicCacheResponse(event)) {"); | ||
addDebugLine("'Found dynamic cache response:', event.request.url", debug, lines); | ||
lines.push(" return;"); | ||
lines.push(" }"); | ||
dynamicCache.forEach(function(cacheURL, idx, array) { | ||
lines.push("toolbox.router.any('"+cacheURL+"',toolbox.networkFirst);"); | ||
}); | ||
} | ||
addDebugLine("'Looking in caches for:', event.request.url", debug, lines); | ||
lines.push(" event.respondWith("); | ||
lines.push(" // caches.match() will look for a cache entry in all of the caches available to the service worker."); | ||
lines.push(" // It's an alternative to first opening a specific named cache and then matching on that."); | ||
lines.push(" caches.match(event.request).then(function(response) {"); | ||
lines.push(" if (response) {"); | ||
addDebugLine("'Found response in cache:', response", debug, lines); | ||
if (fallback.length) { | ||
lines.push(" if (response.status >= 400) {"); | ||
addDebugLine("'Got error status, checking for fallback. Response status was:', response.status", debug, lines); | ||
lines.push(" return fallbackResponse(event.request, response);"); | ||
lines.push(" }"); | ||
fallback.forEach(function(fallback, idx, array) { | ||
var fallbackParts = fallback.split(' '); | ||
if (fallbackParts.length > 1) { | ||
var fallbackOptions = "{fallbackURL:'"+fallbackParts[1]+"'}"; | ||
lines.push("toolbox.router.any('"+fallbackParts[0]+"',fallbackResponse, "+fallbackOptions+");"); | ||
} | ||
}); | ||
} | ||
lines.push(" return response;"); | ||
lines.push(" }"); | ||
addDebugLine("'No response found in cache. About to fetch from network:'+event.request", debug, lines); | ||
lines.push(" // event.request will always have the proper mode set ('cors, 'no-cors', etc.) so we don't"); | ||
lines.push(" // have to hardcode 'no-cors' like we do when fetch()ing in the install handler."); | ||
lines.push(" return fetch(event.request).then(function(response) {"); | ||
addDebugLine("'Response from network is:', response", debug, lines); | ||
lines.push(" return response;"); | ||
lines.push(" }).catch(function(error) {"); | ||
lines.push(" // This catch() will handle exceptions thrown from the fetch() operation."); | ||
lines.push(" // Note that a HTTP error response (e.g. 404) will NOT trigger an exception."); | ||
lines.push(" // It will return a normal response object that has the appropriate error code set."); | ||
if (fallback.length) { | ||
addDebugLine("'Got error, checking for fallback. Error was:', error", debug, lines); | ||
lines.push(" return fallbackResponse(event.request, response);"); | ||
} else { | ||
lines.push(" console.error('Fetching failed:', error);"); | ||
lines.push(" throw error;"); | ||
if (skipWaiting || debug) { | ||
lines.push("self.addEventListener('install', function(event) {"); | ||
addDebugLine("'Handling install event. Resources to pre-fetch:', urlsToPrefetch", debug, lines); | ||
if (skipWaiting) { | ||
lines.push(" if (self.skipWaiting) { self.skipWaiting(); }"); | ||
} | ||
lines.push("});"); | ||
} | ||
lines.push(" });"); | ||
lines.push(" })"); | ||
lines.push(" );"); | ||
lines.push("});"); | ||
if (dynamicCache.length) { | ||
lines.push("function dynamicCacheResponse(event) {"); | ||
lines.push(" var matchingUrls = DYNAMIC_URLS.filter(function(dynamicURL) {"); | ||
lines.push(" return (event.request.url.match(dynamicURL) !== null);"); | ||
lines.push(" });"); | ||
lines.push(" if (matchingUrls.length) {"); | ||
addDebugLine("'Pulling dynamic url: '+event.request.url+' from network and adding to cache.'", debug, lines); | ||
lines.push(" event.respondWith("); | ||
lines.push(" caches.open('dynamic-cache-v"+cacheVersion+"').then(function(cache) {"); | ||
lines.push(" return fetch(event.request).then(function(response) {"); | ||
addDebugLine("'Got response for dynamic url: '+event.request.url+' now adding to cache.', response", debug, lines); | ||
lines.push(" if (response.status >= 400) {"); | ||
addDebugLine("'Got response error for dynamic url, try to pull from cache: ',response.status", debug, lines); | ||
lines.push(" caches.match(event.request).then(function(response) {"); | ||
lines.push(" return response;"); | ||
lines.push(" });"); | ||
lines.push(" } else {"); | ||
lines.push(" cache.put(event.request, response.clone());"); | ||
lines.push(" return response;"); | ||
lines.push(" }"); | ||
lines.push(" }).catch(function(error) {"); | ||
addDebugLine("'Got error for dynamic url, try to pull from cache: ',error", debug, lines); | ||
lines.push(" caches.match(event.request).then(function(response) {"); | ||
lines.push(" return response;"); | ||
lines.push(" });"); | ||
lines.push(" });"); | ||
lines.push(" })"); | ||
lines.push(" );"); | ||
lines.push(" return true;"); | ||
lines.push(" } else {"); | ||
lines.push(" return false;"); | ||
lines.push(" }"); | ||
lines.push("}"); | ||
} | ||
lines.push(getFileContents('delete-old-caches.js')); | ||
if (fallback.length) { | ||
lines.push("function fallbackResponse(request, response) {"); | ||
addDebugLine("'Looking for fallback for:', request.url", debug, lines); | ||
lines.push(" var matchingUrls =FALLBACK_URLS.filter(function(fallbackURL) {"); | ||
addDebugLine("'Checking for fallback match with:', fallbackURL", debug, lines); | ||
lines.push(" return (request.url.match(fallbackURL.match) !== null);"); | ||
lines.push(" });"); | ||
lines.push(" if (matchingUrls.length) {"); | ||
addDebugLine("'Fetching from fallback url: '+ matchingUrls[0].fallback +'for url: '+event.request.url", debug, lines); | ||
lines.push(" return caches.match(matchingUrls[0].fallback);"); | ||
lines.push(" } else {"); | ||
lines.push(" return response; "); | ||
lines.push(" } "); | ||
lines.push("}"); | ||
lines.push(getFileContents('fallback-response.js')); | ||
} | ||
lines.push(getFileContents('log-debug.js')); | ||
fs.writeFileSync(path.join(destDir, serviceWorkerFile), lines.join("\n")); | ||
if (addPolyfill) { | ||
fs.writeFileSync(path.join(destDir, polyFillLocation), fs.readFileSync(swCachePolyFillFile)); | ||
fs.writeFileSync(path.join(destDir, toolboxLocation), fs.readFileSync(swToolboxFile)); | ||
if (debug) { | ||
fs.writeFileSync(path.join(destDir, 'sw-toolbox.map.json'), fs.readFileSync(swToolboxMapFile)); | ||
} | ||
@@ -266,2 +148,7 @@ }); | ||
function getFileContents(fileName) { | ||
var filePath = [ __dirname, fileName].join('/'); | ||
return fs.readFileSync(filePath); | ||
} | ||
module.exports = BroccoliServiceWorker; |
{ | ||
"name": "broccoli-serviceworker", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "A broccoli plugin automating ServiceWorker file creation for Broccoli and Ember.js", | ||
@@ -33,5 +33,5 @@ "main": "lib/service-worker.js", | ||
"broccoli-writer": "~0.1.1", | ||
"serviceworker-cache-polyfill": "^3.0.0", | ||
"stringifile": "^0.1.1" | ||
"stringifile": "^0.1.1", | ||
"sw-toolbox": "^3.0.1" | ||
} | ||
} |
@@ -14,4 +14,6 @@ broccoli-serviceworker | ||
`npm install --save-dev broccoli-serviceworker` | ||
`ember install broccoli-serviceworker` | ||
###Configuration | ||
By default the service worker will be generated for production builds and the service worker registration logic will be added to your index.html automatically. Additionally, you can further customize broccoli-serviceworker by setting configurations in your environment.js file: | ||
```JavaScript | ||
@@ -22,16 +24,32 @@ //app/config/environment.js | ||
enabled: true, | ||
serviceWorkerFile: "service-worker.js", | ||
excludePaths: ['tests/', 'online.html',], | ||
includePaths: ['/'], | ||
debug: true, | ||
precacheURLs: ['/mystaticresouce'], | ||
excludePaths: ['test.*', 'robots.txt',], | ||
fallback: [ | ||
'/online.html offline.html' | ||
'/online.html /offline.html' | ||
], | ||
dynamicCache: [ | ||
'/api/todos' | ||
] | ||
], | ||
includeRegistration: true, | ||
serviceWorkerFile: "service-worker.js", | ||
skipWaiting: true | ||
}; | ||
``` | ||
The following options are available: | ||
* **enabled** - Generate service worker. Defaults to true in production. | ||
* **debug** - Display debug messages in console. | ||
* **precacheURLs** - Array of URLs to precache and always serve from the cache. broccoli-serviceworker will automatically add all Ember app resources (e.g. files in dist) as precached URLs unless explictly excluded in excludePaths. | ||
* **excludePaths** - Array of paths to exclude from precache. Files can be filtered using regular expressions. | ||
```JavaScript | ||
{ | ||
excludePaths: ['index.html', new RegExp(/.\.map$/)], | ||
} | ||
``` | ||
* **includeRegistration** -- Automatically add the service worker registration script using contentFor to place the script in body-footer. Defaults to true. | ||
* **serviceWorkerFile** - Name of the service worker file to generate. If **includeRegistration** is set to true, this setting is unused. Defaults to *service-worker.js*. | ||
* **fallback** - Array of URLs with fallbacks when the resource isn't available via network or cache. | ||
* **dynamicCache** - List of URLs that should use a network first strategy that falls back to a cached version of the response if the network is unavailable. For more details, see the details on [sw-toolbox's networkFirst strategy](https://github.com/GoogleChrome/sw-toolbox#user-content-toolboxnetworkfirst). | ||
* **skipWaiting** - Allows a simple page refresh to update the app. Defaults to true. | ||
Upgrade your `index.html` (see below) and you are done. | ||
The service worker bootstrap logic will be added to your index.html automatically, using contentFor hooks. | ||
@@ -54,2 +72,3 @@ Usage for Broccoli.js | ||
``` | ||
Upgrade your `index.html` (see below) and you are done. | ||
@@ -59,3 +78,3 @@ Options | ||
You can pass some options as the second argument to `writeServiceWorker`: | ||
You can the [options specified above](#configuration) as the second argument to `writeServiceWorker`: | ||
@@ -66,6 +85,6 @@ ```JavaScript | ||
serviceWorkerFile: "service-worker.js", | ||
excludePaths: ['tests/', 'online.html',], | ||
includePaths: ['/'], | ||
excludePaths: ['test.*', 'online.html',], | ||
precacheURLs: ['/api/offlineStates'], | ||
fallback: [ | ||
'/online.html offline.html' | ||
'/api/states /api/offlineStates' | ||
], | ||
@@ -79,12 +98,2 @@ dynamicCache: [ | ||
Files can be filtered using regular expressions. | ||
```JavaScript | ||
{ | ||
excludePaths: ['index.html', new RegExp(/.\.map$/)], | ||
includePaths: [''] | ||
} | ||
``` | ||
Upgrade your index.html | ||
@@ -107,5 +116,5 @@ ----------------------- | ||
alert('service worker not supported'); | ||
} | ||
} | ||
</script> | ||
</html> | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11
114
15320
237
1
+ Addedsw-toolbox@^3.0.1
+ Addedisarray@0.0.1(transitive)
+ Addedpath-to-regexp@1.9.0(transitive)
+ Addedserviceworker-cache-polyfill@4.0.0(transitive)
+ Addedsw-toolbox@3.6.0(transitive)
- Removedserviceworker-cache-polyfill@^3.0.0
- Removedserviceworker-cache-polyfill@3.0.0(transitive)