Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sw lib caching strategies #108

Merged
merged 10 commits into from
Jan 10, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
layout: index
title: Home
navigation_weight: 0
---
2 changes: 1 addition & 1 deletion packages/sw-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "sw-lib",
"name": "sw-cli",
"private": true,
"version": "0.0.5",
"description": "A CLI tool to generate a service worker and file manifest making use of the sw-lib module.",
Expand Down
48 changes: 48 additions & 0 deletions packages/sw-lib/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,54 @@ if (!assert.isSWEnv()) {
throw ErrorFactory.createError('not-in-sw');
}

/**
* The sw-lib module is a high-level library that makes it easier to
* configure routes with caching strategies as well as manage precaching
* of assets during the install step of a service worker.
*
* @example
* importScripts('/<Path to Module>/build/sw-lib.min.js');
*
* // cacheRevisionedAssets() can take an array of strings with
* // the revision details in the url.
* goog.swlib.cacheRevisionedAssets([
* '/styles/main.1234.css',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you're trying to show here is that cacheRevisionedAssets accepts both filename paths with revisions in there or an object with the raw path and a separate revision number. Is that the case?

It might be worth splitting these into two examples (one showing caching a few files with one approach (e.g `/styles/main.1234.css', '/js/main.1234.js' etc) and then the object variation. Might make it more easy to read through.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* '/images/logo.abcd.jpg'
* ]);
*
* // Or it can accept objects with the URL and revision data
* // kept seperate.
* goog.swlib.cacheRevisionedAssets([
* {
* url: '/index.html',
* revision: '1234'
* },
* {
* url: '/about.html',
* revision: 'abcd'
* }
* ]);
*
* // If you have assets that aren't revisioned, you can cache them
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested rephrasing:

If you have assets that aren't revisioned, but which you plan to reference using a runtime caching strategy, you can add them to your runtime cache

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment the precaching module doesn't configure fetch routes, so technically both require "run time caching strategies".

* // during the installation of you service worker using warmRuntimeCache()
* goog.swlib.warmRuntimeCache([
* '/scripts/main.js',
* '/images/default-avater.png'
* ]);
*
* // warmRuntimeCache can also accept Requests, in case you need greater
* // control over the request.
* goog.swlib.warmRuntimeCache([
* new Request('/images/logo.png'),
* new Request('/api/data.json')
* ]);
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* goog.swlib.router.registerRoute('/', goog.swlib.cacheFirst);
* goog.swlib.router.registerRoute('/example/', goog.swlib.networkFirst);
*
* @module sw-lib
*/

const swLibInstance = new SWLib();
swLibInstance.Route = Route;
export default swLibInstance;
67 changes: 58 additions & 9 deletions packages/sw-lib/src/lib/router-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,56 @@ import ErrorFactory from './error-factory.js';

/**
* A simple class that pulls together a few different pieces from the
* Router Module to surface them in a slightly easier API surface.
* Router Module to surface them in a friendly API.
*
* @example <caption>How to define a simple route with caching
* strategy.</caption>
*
* self.goog.swlib.router.registerRoute('/about', ....);
*
* @example <caption>How to define a simple route with custom caching
* strategy.</caption>
*
* self.goog.swlib.router.registerRoute('/about', (args) => {
* // The requested URL
* console.log(args.url);
*
* // The FetchEvent to handle
* console.log(args.event);
*
* // The parameters from the matching route.
* console.log(args.params);
*
* // Return a promise that resolves with a Response.
* return fetch(args.url);
* }));
*
* @example <caption>How to define a route using a Route instance.</caption>
*
* const routeInstance = new goog.swlib.Route({
* match: (url) => {
* // Return true or false
* return true;
* },
* handler: {
* handle: (args) => {
* // The requested URL
* console.log(args.url);
*
* // The FetchEvent to handle
* console.log(args.event);
*
* // The parameters from the matching route.
* console.log(args.params);
*
* // Return a promise that resolves with a Response.
* return fetch(args.url);
* },
* },
* });
* self.goog.swlib.router.registerRoute(routeInstance);
*
* @memberof module:sw-lib
*/
class RouterWrapper {
/**
Expand All @@ -32,16 +81,16 @@ class RouterWrapper {
}

/**
* @param {String|Regex|Route} capture the The capture for a route can be one
* @param {String|Regex|Route} capture The capture for a route can be one
* of three types.
* 1. It can be an Express style route, like: '/example/:anything/route/'
* The only gotcha with this is that it will only capture URL's on your
* origin.
* 2. A regex that will be tested against request URL's.
* 3. A Route object
* 1. It can be an Express style route, like: '/example/:anything/route/'
* The only gotcha with this is that it will only capture URL's on your
* origin.
* 1. A regex that will be tested against request URL's.
* 1. A [Route]{@link module:sw-routing.Route} instance.
* @param {function|Handler} handler The handler is ignored if you pass in
* a Route, otherwise it's required. The handler will be called when the route
* is caught by the capture criteria.
* a Route object, otherwise it's required. The handler will be called when
* the route is caught by the capture criteria.
*/
registerRoute(capture, handler) {
if (typeof handler === 'function') {
Expand Down
62 changes: 35 additions & 27 deletions packages/sw-lib/src/lib/sw-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,44 @@

import RouterWrapper from './router-wrapper.js';
import ErrorFactory from './error-factory.js';
import {PrecacheManager}
from '../../../sw-precaching/src/index.js';
import {PrecacheManager} from '../../../sw-precaching/src/index.js';
import {
CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate,
} from '../../../sw-runtime-caching/src/index.js';

/**
* This is a high level library to help with using service worker
* precaching and run time caching.
*
* @memberof module:sw-lib
*/
class SWLib {
/**
* Initialises the classes router wrapper.
* Initialises an instance of SWLib. An instance of this class is
* accessible when the module is imported into a service worker as
* `self.goog.swlib`.
*/
constructor() {
this._router = new RouterWrapper();
this._precacheManager = new PrecacheManager();
}

/**
* Can be used to define debug logging, default cache name etc.
* @param {Object} options The options to set.
*/
setOptions(options) {

}

/**
* If there are assets that are revisioned, they can be cached intelligently
* Revisioned assets can be cached intelligently
* during the install (i.e. old files are cleared from the cache, new files
* are added tot he cache and unchanged files are left as is).
* @param {Array<String>} revisionedFiles A set of urls to cache when the
* service worker is installed.
* are added to the cache and unchanged files are left as is).
*
* @example
* self.goog.swlib.cacheRevisionedAssets([
* '/styles/main.1234.css',
* {
* url: '/index.html',
* revision: '1234'
* }
* ]);
*
* @param {Array<String|Object>} revisionedFiles A set of urls to cache
* when the service worker is installed.
*/
cacheRevisionedAssets(revisionedFiles) {
// Add a more helpful error message than assertion error.
Expand All @@ -60,10 +68,18 @@ class SWLib {
}

/**
* If there are assets that should be cached on the install step but
* aren't revisioned, you can cache them here.
* @param {Array<String>} unrevisionedFiles A set of urls to cache when the
* service worker is installed.
* Any assets you wish to cache ahead of time which can't be revisioned
* should be cached with this method. All assets are cached on install
* regardless of whether an older version of the request is in the cache.
*
* @example
* self.goog.swlib.warmRuntimeCache([
* '/scripts/main.js',
* new Request('/images/logo.png')
* ]);
*
* @param {Array<String|Request>} unrevisionedFiles A set of urls to cache
* when the service worker is installed.
*/
warmRuntimeCache(unrevisionedFiles) {
// Add a more helpful error message than assertion error.
Expand All @@ -75,14 +91,6 @@ class SWLib {
unrevisionedFiles,
});
}

/**
* A getter for the Router Wrapper.
* @return {RouterWrapper} Returns the Router Wrapper
*/
get router() {
return this._router;
}
}

export default SWLib;
5 changes: 5 additions & 0 deletions packages/sw-lib/test/browser-unit/library-namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ describe('Test Behaviors of Loading the Script', function() {
return window.goog.mochaUtils.registerServiceWorkerMochaTests(serviceWorkerPath);
});

it('should perform caching-strategies.js sw tests', function() {
const serviceWorkerPath = 'sw-unit/caching-strategies.js';
return window.goog.mochaUtils.registerServiceWorkerMochaTests(serviceWorkerPath);
});

it('should perform cache-revisioned-assets.js sw tests', function() {
const serviceWorkerPath = 'sw-unit/cache-revisioned-assets.js';
return window.goog.mochaUtils.registerServiceWorkerMochaTests(serviceWorkerPath);
Expand Down
44 changes: 44 additions & 0 deletions packages/sw-lib/test/browser-unit/sw-unit/caching-strategies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
importScripts('/node_modules/mocha/mocha.js');
importScripts('/node_modules/chai/chai.js');
importScripts('/node_modules/sw-testing-helpers/build/browser/mocha-utils.js');

importScripts('/packages/sw-lib/build/sw-lib.min.js');

/* global goog */

const expect = self.chai.expect;
self.chai.should();
mocha.setup({
ui: 'bdd',
reporter: null,
});

describe('Test swlib.cacheOnly', function() {
it('should be accessible goog.swlib.cacheOnly', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about why these functions aren't just using arrow syntax offline. I think your reason about Mocha breaking decorated it bindings etc if arrows are used was fine. I did see https://github.com/skozin/arrow-mocha in case we wanted to explore how to work around this in future.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably going to stick with the function name for now. Ideally Mocha would change to have a more explicit approach of declaring the timeout and retries of a unit test.

expect(goog.swlib.cacheOnly).to.exist;
});
});

describe('Test swlib.cacheFirst', function() {
it('should be accessible goog.swlib.cacheFirst', function() {
expect(goog.swlib.cacheFirst).to.exist;
});
});

describe('Test swlib.networkOnly', function() {
it('should be accessible goog.swlib.networkOnly', function() {
expect(goog.swlib.networkOnly).to.exist;
});
});

describe('Test swlib.networkFirst', function() {
it('should be accessible goog.swlib.networkFirst', function() {
expect(goog.swlib.networkFirst).to.exist;
});
});

describe('Test swlib.fastest', function() {
it('should be accessible goog.swlib.fastest', function() {
expect(goog.swlib.fastest).to.exist;
});
});
30 changes: 20 additions & 10 deletions packages/sw-lib/test/browser-unit/sw-unit/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,17 @@ describe('Test swlib.router', function() {

it('should be able to register a valid express route', function() {
const date = Date.now();
const expressRoute = `/${date}/:id/test/`;
const exampleRoute = `/${date}/1234567890/test/`;
const fakeID = '1234567890';
const expressRoute = `/:date/:id/test/`;
const exampleRoute = `/${date}/${fakeID}/test/`;

return new Promise((resolve, reject) => {
goog.swlib.router.registerRoute(expressRoute, () => {
// TODO: Checking ':id' makes it through to here.
goog.swlib.router.registerRoute(expressRoute, (args) => {
(args.event instanceof FetchEvent).should.equal(true);
args.url.toString().should.equal(new URL(exampleRoute, location).toString());
args.params[0].should.equal(exampleRoute);
args.params[1].should.equal(date.toString());
args.params[2].should.equal(fakeID);

resolve();
});
Expand All @@ -82,12 +87,16 @@ describe('Test swlib.router', function() {
});

it('should be able to register a valid regex route', function() {
const regexRoute = /\/1234567890\/\w+\//;
const exampleRoute = `/1234567890/test/`;
const capturingGroup = 'test';
const regexRoute = /\/1234567890\/(\w+)\//;
const exampleRoute = `/1234567890/${capturingGroup}/`;

return new Promise((resolve, reject) => {
goog.swlib.router.registerRoute(regexRoute, () => {
// TODO: Checking 'test' capture group makes it through to here.
goog.swlib.router.registerRoute(regexRoute, (args) => {
(args.event instanceof FetchEvent).should.equal(true);
args.url.toString().should.equal(new URL(exampleRoute, location).toString());
args.params[0].should.equal(exampleRoute);
args.params[1].should.equal(capturingGroup);

resolve();
});
Expand All @@ -106,8 +115,9 @@ describe('Test swlib.router', function() {
const routeInstance = new goog.swlib.Route({
match: (url) => true,
handler: {
handle: () => {
// TODO: Check returned value from match makes it through to here.
handle: (args) => {
(args.event instanceof FetchEvent).should.equal(true);
args.url.toString().should.equal(new URL(exampleRoute, location).toString());

resolve();
},
Expand Down