Skip to content

Commit

Permalink
Sw precaching (#76)
Browse files Browse the repository at this point in the history
* Adding sw-precaching

* Started to add some caching logic. Needs errors cases covered and a test to confirm existing cached assets aren't re-cached

* Fixing lint error - happy travis

* Started moving to indexeddb tracking of revisions

* Fixing up tests and fixing caching to remove old entries

* Review comments

* Moving test server over to gulp-serve

* Handling errors for bad inputs to cache manifest

* Adding some valid values to ensure they work

* Tidying up indexeddb usage

* Fixing up tests and removing the demo section for sw-precaching

* Adding tests to manage cache and indexeddb deletion

* Switching away from async and await due to build issues

* sw-precaching

* Moving to async and await

* sw-precaching

* Adding secondary server and smartest file manifest generation

* Hopefully making travis happy

* Fixing same server issue in sw-offline-analytics
  • Loading branch information
Matt Gaunt authored Dec 2, 2016
1 parent 43dea0f commit 17a2703
Show file tree
Hide file tree
Showing 30 changed files with 1,366 additions and 292 deletions.
19 changes: 5 additions & 14 deletions gulp-tasks/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,12 @@
/* eslint-disable no-console, valid-jsdoc */

const gulp = require('gulp');
const path = require('path');
const express = require('express');
const serveIndex = require('serve-index');
const serveStatic = require('serve-static');
const testServer = require('../utils/test-server.js');

gulp.task('serve', (unusedCallback) => {
const app = express();
const rootDirectory = global.projectOrStar === '*' ?
'packages' :
path.join('packages', global.projectOrStar);

app.use(serveStatic(rootDirectory));
app.use(serveIndex(rootDirectory, {view: 'details'}));
app.listen(global.port, () => {
console.log(`Serving '${rootDirectory}' at ` +
`http://localhost:${global.port}/`);
return testServer.start('.', global.port)
.then((port) => {
console.log(`Primary Server http://localhost:${port}/`);
console.log(`Secondary Server http://localhost:${port + 1}/`);
});
});
230 changes: 123 additions & 107 deletions lib/idb-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,123 +19,139 @@ import idb from 'idb';
* A wrapper to store for an IDB connection to a particular ObjectStore.
*
* @private
* @class
*/
function IDBHelper(name, version, storeName) {
if (name == undefined || version == undefined || storeName == undefined) {
throw Error('name, version, storeName must be passed to the constructor.');
class IDBHelper {
constructor(name, version, storeName) {
if (name == undefined || version == undefined || storeName == undefined) {
throw Error('name, version, storeName must be passed to the ' +
'constructor.');
}

this._name = name;
this._version = version;
this._storeName = storeName;
}

this._name = name;
this._version = version;
this._storeName = storeName;
}
/**
* Returns a promise that resolves with an open connection to IndexedDB,
* either existing or newly opened.
*
* @private
* @return {Promise<DB>}
*/
_getDb() {
if (this._dbPromise) {
return this._dbPromise;
}

/**
* Returns a promise that resolves with an open connection to IndexedDB, either
* existing or newly opened.
*
* @private
* @return {Promise<DB>}
*/
IDBHelper.prototype._getDb = function() {
if (this._db) {
return Promise.resolve(this._db);
this._dbPromise = idb.open(this._name, this._version, (upgradeDB) => {
upgradeDB.createObjectStore(this._storeName);
})
.then((db) => {
return db;
});

return this._dbPromise;
}

return idb.open(this._name, this._version, (upgradeDB) => {
upgradeDB.createObjectStore(this._storeName);
}).then((db) => {
this._db = db;
return db;
});
};
close() {
if (!this._dbPromise) {
return;
}

/**
* Wrapper on top of the idb wrapper, which simplifies saving the key/value
* pair to the object store.
* Returns a Promise that fulfills when the transaction completes.
*
* @private
* @param {String} key
* @param {Object} value
* @return {Promise<T>}
*/
IDBHelper.prototype.put = function(key, value) {
return this._getDb().then((db) => {
const tx = db.transaction(this._storeName, 'readwrite');
const objectStore = tx.objectStore(this._storeName);
objectStore.put(value, key);
return tx.complete;
});
};
return this._dbPromise
.then((db) => {
db.close();
this._dbPromise = null;
});
}

/**
* Wrapper on top of the idb wrapper, which simplifies deleting an entry
* from the object store.
* Returns a Promise that fulfills when the transaction completes.
*
* @private
* @param {String} key
* @return {Promise<T>}
*/
IDBHelper.prototype.delete = function(key) {
return this._getDb().then((db) => {
const tx = db.transaction(this._storeName, 'readwrite');
const objectStore = tx.objectStore(this._storeName);
objectStore.delete(key);
return tx.complete;
});
};
/**
* Wrapper on top of the idb wrapper, which simplifies saving the key/value
* pair to the object store.
* Returns a Promise that fulfills when the transaction completes.
*
* @private
* @param {String} key
* @param {Object} value
* @return {Promise<T>}
*/
put(key, value) {
return this._getDb().then((db) => {
const tx = db.transaction(this._storeName, 'readwrite');
const objectStore = tx.objectStore(this._storeName);
objectStore.put(value, key);
return tx.complete;
});
}

/**
* Wrapper on top of the idb wrapper, which simplifies getting a key's value
* from the object store.
* Returns a promise that fulfills with the value.
*
* @private
* @param {String} key
* @return {Promise<Object>}
*/
IDBHelper.prototype.get = function(key) {
return this._getDb().then((db) => {
return db.transaction(this._storeName)
.objectStore(this._storeName)
.get(key);
});
};
/**
* Wrapper on top of the idb wrapper, which simplifies deleting an entry
* from the object store.
* Returns a Promise that fulfills when the transaction completes.
*
* @private
* @param {String} key
* @return {Promise<T>}
*/
delete(key) {
return this._getDb().then((db) => {
const tx = db.transaction(this._storeName, 'readwrite');
const objectStore = tx.objectStore(this._storeName);
objectStore.delete(key);
return tx.complete;
});
}

/**
* Wrapper on top of the idb wrapper, which simplifies getting all the values
* in an object store.
* Returns a promise that fulfills with all the values.
*
* @private
* @return {Promise<Array<Object>>}
*/
IDBHelper.prototype.getAllValues = function() {
return this._getDb().then((db) => {
return db.transaction(this._storeName)
.objectStore(this._storeName)
.getAll();
});
};
/**
* Wrapper on top of the idb wrapper, which simplifies getting a key's value
* from the object store.
* Returns a promise that fulfills with the value.
*
* @private
* @param {String} key
* @return {Promise<Object>}
*/
get(key) {
return this._getDb().then((db) => {
return db.transaction(this._storeName)
.objectStore(this._storeName)
.get(key);
});
}

/**
* Wrapper on top of the idb wrapper, which simplifies getting all the keys
* in an object store.
* Returns a promise that fulfills with all the keys.
*
* @private
* @param {String} storeName
* @return {Promise<Array<Object>>}
*/
IDBHelper.prototype.getAllKeys = function() {
return this._getDb().then((db) => {
return db.transaction(this._storeName)
.objectStore(this._storeName)
.getAllKeys();
});
};
/**
* Wrapper on top of the idb wrapper, which simplifies getting all the values
* in an object store.
* Returns a promise that fulfills with all the values.
*
* @private
* @return {Promise<Array<Object>>}
*/
getAllValues() {
return this._getDb().then((db) => {
return db.transaction(this._storeName)
.objectStore(this._storeName)
.getAll();
});
}

/**
* Wrapper on top of the idb wrapper, which simplifies getting all the keys
* in an object store.
* Returns a promise that fulfills with all the keys.
*
* @private
* @param {String} storeName
* @return {Promise<Array<Object>>}
*/
getAllKeys() {
return this._getDb().then((db) => {
return db.transaction(this._storeName)
.objectStore(this._storeName)
.getAllKeys();
});
}
}

export default IDBHelper;
12 changes: 8 additions & 4 deletions packages/sw-appcache-behavior/test/automated-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const parseManifest = require('parse-appcache-manifest');
const path = require('path');
const promisify = require('promisify-node');
const seleniumAssistant = require('selenium-assistant');
const swTestingHelpers = require('sw-testing-helpers');
const fs = require('fs');
const testServer = require('../../../utils/test-server');

// Ensure the selenium drivers are added Node scripts path.
require('geckodriver');
Expand All @@ -37,7 +37,6 @@ require('operadriver');

const fsePromise = promisify('fs-extra');

const testServer = new swTestingHelpers.TestServer();
const expect = chai.expect;
const TIMEOUT = 10 * 1000;
const RETRIES = 3;
Expand Down Expand Up @@ -93,7 +92,7 @@ const configureTestSuite = function(browser) {

return generateInitialManifests()
.then(() => {
return testServer.startServer('.');
return testServer.start('.');
})
.then((portNumber) => {
baseTestUrl = `http://localhost:${portNumber}/packages/sw-appcache-behavior/test/`;
Expand All @@ -106,7 +105,7 @@ const configureTestSuite = function(browser) {
after(function() {
return seleniumAssistant.killWebDriver(globalDriverReference)
.then(() => {
return testServer.killServer();
return testServer.stop();
})
.then(() => {
return fsePromise.remove(tempDirectory);
Expand Down Expand Up @@ -285,6 +284,11 @@ seleniumAssistant.getAvailableBrowsers().forEach(function(browser) {
case 'chrome':
case 'firefox':
case 'opera':
if (browser.getSeleniumBrowserId() === 'opera' &&
browser.getVersionNumber() <= 43) {
console.log(`Skipping Opera <= 43 due to driver issues.`);
return;
}
configureTestSuite(browser);
break;
default:
Expand Down
6 changes: 3 additions & 3 deletions packages/sw-lib/test/automated-test-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

const seleniumAssistant = require('selenium-assistant');
const swTestingHelpers = require('sw-testing-helpers');
const testServer = new swTestingHelpers.TestServer();
const testServer = require('../../../utils/test-server.js');

require('chromedriver');
require('operadriver');
Expand All @@ -34,14 +34,14 @@ const setupTestSuite = (assistantDriver) => {

// Set up the web server before running any tests in this suite.
before(function() {
return testServer.startServer('.').then((portNumber) => {
return testServer.start('.').then((portNumber) => {
baseTestUrl = `http://localhost:${portNumber}/packages/sw-lib`;
});
});

// Kill the web server once all tests are complete.
after(function() {
return testServer.killServer();
return testServer.stop();
});

afterEach(function() {
Expand Down
2 changes: 2 additions & 0 deletions packages/sw-lib/test/browser-unit/library-namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ describe('Test Behaviors of Loading the Script', function() {
this.timeout(5 * 60 * 1000);

it('should print an error when added to the window.', function() {
this.timeout(2000);

return new Promise((resolve, reject) => {
window.onerror = (msg, url, lineNo, columnNo, error) => {
window.onerror = null;
Expand Down
8 changes: 0 additions & 8 deletions packages/sw-lib/test/utils/dev-server.js

This file was deleted.

Loading

0 comments on commit 17a2703

Please sign in to comment.