diff --git a/packages/workbox-core/_private.mjs b/packages/workbox-core/_private.mjs index 3e19cdd78..da1db70b9 100644 --- a/packages/workbox-core/_private.mjs +++ b/packages/workbox-core/_private.mjs @@ -7,26 +7,30 @@ */ // We either expose defaults or we expose every named export. -import {DBWrapper} from './_private/DBWrapper.mjs'; -import {deleteDatabase} from './_private/deleteDatabase.mjs'; -import {WorkboxError} from './_private/WorkboxError.mjs'; import {assert} from './_private/assert.mjs'; import {cacheNames} from './_private/cacheNames.mjs'; import {cacheWrapper} from './_private/cacheWrapper.mjs'; +import {DBWrapper} from './_private/DBWrapper.mjs'; +import {Deferred} from './_private/Deferred.mjs'; +import {deleteDatabase} from './_private/deleteDatabase.mjs'; +import {executeQuotaErrorCallbacks} from './_private/executeQuotaErrorCallbacks.mjs'; import {fetchWrapper} from './_private/fetchWrapper.mjs'; import {getFriendlyURL} from './_private/getFriendlyURL.mjs'; import {logger} from './_private/logger.mjs'; +import {WorkboxError} from './_private/WorkboxError.mjs'; import './_version.mjs'; export { - DBWrapper, - deleteDatabase, - WorkboxError, assert, cacheNames, cacheWrapper, + DBWrapper, + Deferred, + deleteDatabase, + executeQuotaErrorCallbacks, fetchWrapper, getFriendlyURL, logger, + WorkboxError, }; diff --git a/packages/workbox-core/_private/cacheWrapper.mjs b/packages/workbox-core/_private/cacheWrapper.mjs index 3e92ad006..e7df56b8d 100644 --- a/packages/workbox-core/_private/cacheWrapper.mjs +++ b/packages/workbox-core/_private/cacheWrapper.mjs @@ -6,13 +6,13 @@ https://opensource.org/licenses/MIT. */ -import {pluginEvents} from '../models/pluginEvents.mjs'; -import {pluginUtils} from '../utils/pluginUtils.mjs'; import {WorkboxError} from './WorkboxError.mjs'; import {assert} from './assert.mjs'; -import {executeQuotaErrorCallbacks} from './quota.mjs'; import {getFriendlyURL} from './getFriendlyURL.mjs'; import {logger} from './logger.mjs'; +import {executeQuotaErrorCallbacks} from './executeQuotaErrorCallbacks.mjs'; +import {pluginEvents} from '../models/pluginEvents.mjs'; +import {pluginUtils} from '../utils/pluginUtils.mjs'; import '../_version.mjs'; diff --git a/packages/workbox-core/_private/checkSWFileCacheHeaders.mjs b/packages/workbox-core/_private/checkSWFileCacheHeaders.mjs deleted file mode 100644 index 42709a577..000000000 --- a/packages/workbox-core/_private/checkSWFileCacheHeaders.mjs +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. -*/ - -import {logger} from './logger.mjs'; -import '../_version.mjs'; - -/** - * Logs a warning to the user recommending changing - * to max-age=0 or no-cache. - * - * @param {string} cacheControlHeader - * - * @private - */ -function showWarning(cacheControlHeader) { - const docsURL = 'https://developers.google.com/web/tools/workbox/guides/service-worker-checklist#cache-control_of_your_service_worker_file'; - logger.warn(`You are setting a 'cache-control' header of ` + - `'${cacheControlHeader}' on your service worker file. This should be ` + - `set to 'max-age=0' or 'no-cache' to ensure the latest service worker ` + - `is served to your users. Learn more here: ${docsURL}` - ); -} - -/** - * Checks for cache-control header on SW file and - * warns the developer if it exists with a value - * other than max-age=0 or no-cache. - * - * @return {Promise} - * @private - */ -function checkSWFileCacheHeaders() { - // This is wrapped as an iife to allow async/await while making - // rollup exclude it in builds. - return (async () => { - try { - const swFile = self.location.href; - const response = await fetch(swFile); - if (!response.ok) { - // Response failed so nothing we can check; - return; - } - - if (!response.headers.has('cache-control')) { - // No cache control header. - return; - } - - const cacheControlHeader = response.headers.get('cache-control'); - const maxAgeResult = /max-age\s*=\s*(\d*)/g.exec(cacheControlHeader); - if (maxAgeResult) { - if (parseInt(maxAgeResult[1], 10) === 0) { - return; - } - } - - if (cacheControlHeader.indexOf('no-cache') !== -1) { - return; - } - - if (cacheControlHeader.indexOf('no-store') !== -1) { - return; - } - - showWarning(cacheControlHeader); - } catch (err) { - // NOOP - } - })(); -} - -const finalCheckSWFileCacheHeaders = - process.env.NODE_ENV === 'production' ? null : checkSWFileCacheHeaders; - -export {finalCheckSWFileCacheHeaders as checkSWFileCacheHeaders}; diff --git a/packages/workbox-core/_private/executeQuotaErrorCallbacks.mjs b/packages/workbox-core/_private/executeQuotaErrorCallbacks.mjs new file mode 100644 index 000000000..3653d7a61 --- /dev/null +++ b/packages/workbox-core/_private/executeQuotaErrorCallbacks.mjs @@ -0,0 +1,39 @@ +/* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. +*/ + +import {logger} from '../_private/logger.mjs'; +import {quotaErrorCallbacks} from '../models/quotaErrorCallbacks.mjs'; +import '../_version.mjs'; + + +/** + * Runs all of the callback functions, one at a time sequentially, in the order + * in which they were registered. + * + * @memberof workbox.core + * @private + */ +async function executeQuotaErrorCallbacks() { + if (process.env.NODE_ENV !== 'production') { + logger.log(`About to run ${quotaErrorCallbacks.size} ` + + `callbacks to clean up caches.`); + } + + for (const callback of quotaErrorCallbacks) { + await callback(); + if (process.env.NODE_ENV !== 'production') { + logger.log(callback, 'is complete.'); + } + } + + if (process.env.NODE_ENV !== 'production') { + logger.log('Finished running callbacks.'); + } +} + +export {executeQuotaErrorCallbacks}; diff --git a/packages/workbox-core/_private/quota.mjs b/packages/workbox-core/_private/quota.mjs deleted file mode 100644 index 78037acc5..000000000 --- a/packages/workbox-core/_private/quota.mjs +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. -*/ - -import {logger} from './logger.mjs'; -import {assert} from './assert.mjs'; - -import '../_version.mjs'; - -const callbacks = new Set(); - -/** - * Adds a function to the set of callbacks that will be executed when there's - * a quota error. - * - * @param {Function} callback - * @memberof workbox.core - */ -function registerQuotaErrorCallback(callback) { - if (process.env.NODE_ENV !== 'production') { - assert.isType(callback, 'function', { - moduleName: 'workbox-core', - funcName: 'register', - paramName: 'callback', - }); - } - - callbacks.add(callback); - - if (process.env.NODE_ENV !== 'production') { - logger.log('Registered a callback to respond to quota errors.', callback); - } -} - -/** - * Runs all of the callback functions, one at a time sequentially, in the order - * in which they were registered. - * - * @memberof workbox.core - * @private - */ -async function executeQuotaErrorCallbacks() { - if (process.env.NODE_ENV !== 'production') { - logger.log(`About to run ${callbacks.size} callbacks to clean up caches.`); - } - - for (const callback of callbacks) { - await callback(); - if (process.env.NODE_ENV !== 'production') { - logger.log(callback, 'is complete.'); - } - } - - if (process.env.NODE_ENV !== 'production') { - logger.log('Finished running callbacks.'); - } -} - -export { - executeQuotaErrorCallbacks, - registerQuotaErrorCallback, -}; diff --git a/packages/workbox-core/models/quotaErrorCallbacks.mjs b/packages/workbox-core/models/quotaErrorCallbacks.mjs new file mode 100644 index 000000000..72e362844 --- /dev/null +++ b/packages/workbox-core/models/quotaErrorCallbacks.mjs @@ -0,0 +1,15 @@ +/* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. +*/ + +import '../_version.mjs'; + + +// Callbacks to be executed whenever there's a quota error. +const quotaErrorCallbacks = new Set(); + +export {quotaErrorCallbacks}; diff --git a/packages/workbox-core/registerQuotaErrorCallback.mjs b/packages/workbox-core/registerQuotaErrorCallback.mjs index 2e10f3a1e..93d4c87e8 100644 --- a/packages/workbox-core/registerQuotaErrorCallback.mjs +++ b/packages/workbox-core/registerQuotaErrorCallback.mjs @@ -6,8 +6,33 @@ https://opensource.org/licenses/MIT. */ -import {registerQuotaErrorCallback} from './_private/quota.mjs'; +import {logger} from './_private/logger.mjs'; +import {assert} from './_private/assert.mjs'; +import {quotaErrorCallbacks} from './models/quotaErrorCallbacks.mjs'; import './_version.mjs'; +/** + * Adds a function to the set of quotaErrorCallbacks that will be executed if + * there's a quota error. + * + * @param {Function} callback + * @memberof workbox.core + */ +function registerQuotaErrorCallback(callback) { + if (process.env.NODE_ENV !== 'production') { + assert.isType(callback, 'function', { + moduleName: 'workbox-core', + funcName: 'register', + paramName: 'callback', + }); + } + + quotaErrorCallbacks.add(callback); + + if (process.env.NODE_ENV !== 'production') { + logger.log('Registered a callback to respond to quota errors.', callback); + } +} + export {registerQuotaErrorCallback}; diff --git a/test/all/node/test-exports.mjs b/test/all/node/test-exports.mjs index e4e4e1ac6..4945249f9 100644 --- a/test/all/node/test-exports.mjs +++ b/test/all/node/test-exports.mjs @@ -73,4 +73,50 @@ describe(`[all] Window and SW packages`, function() { .to.deep.equal(topLevelFiles.concat(deprecatedExports).sort()); } }); + + it(`should have top a level module for every export in _private.mjs (and vise-versa)`, async function() { + for (const pkg of windowAndBrowserPackages) { + const packagePath = path.join(__dirname, '..', '..', '..', 'packages', pkg.name); + const privateFile = path.join(packagePath, '_private.mjs'); + + // Only some packages have a `_private.mjs` module. + if (!fs.existsSync(privateFile)) { + continue; + } + + const privateContents = await fs.readFile(privateFile, 'utf-8'); + + // Use the acorn parser to generate a list of named exports. + const namedExports = []; + const indexAST = acorn.parse(privateContents, { + ecmaVersion: 6, + sourceType: 'module', + }); + for (const node of indexAST.body) { + if (node.type === 'ExportDefaultDeclaration') { + throw new Error(`'_private.mjs' files cannot contain default exports`); + } + if (node.type === 'ExportNamedDeclaration') { + if (node.specifiers.length === 0) { + throw new Error(`'_private.mjs' files may only contain a single, named-export block`); + } + for (const specifier of node.specifiers) { + namedExports.push(specifier.exported.name); + } + } + } + + // Inspect the package directory to get a list of top-level, public + // module basenames. + const privateDirectoryPath = path.join(packagePath, '_private'); + const topLevelFiles = glob.sync('*.mjs', {cwd: privateDirectoryPath}) + .map((file) => path.basename(file, '.mjs')); + + const deprecatedExports = deprecatedPackageExports[pkg.name] || []; + + // Assert there's a 1-to-1 mapping between exports and top-level files. + expect(namedExports.sort()) + .to.deep.equal(topLevelFiles.concat(deprecatedExports).sort()); + } + }); }); diff --git a/test/workbox-core/sw/_private/test-cacheWrapper.mjs b/test/workbox-core/sw/_private/test-cacheWrapper.mjs index d3f2eb395..44904e1c7 100644 --- a/test/workbox-core/sw/_private/test-cacheWrapper.mjs +++ b/test/workbox-core/sw/_private/test-cacheWrapper.mjs @@ -7,7 +7,7 @@ */ import {cacheWrapper} from 'workbox-core/_private/cacheWrapper.mjs'; -import {registerQuotaErrorCallback} from 'workbox-core/_private/quota.mjs'; +import {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.mjs'; describe(`cacheWrapper`, function() { diff --git a/test/workbox-core/sw/_private/test-checkSWFileCacheHeaders.mjs b/test/workbox-core/sw/_private/test-checkSWFileCacheHeaders.mjs deleted file mode 100644 index b3fde1d79..000000000 --- a/test/workbox-core/sw/_private/test-checkSWFileCacheHeaders.mjs +++ /dev/null @@ -1,131 +0,0 @@ -/* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. -*/ - -import {logger} from 'workbox-core/_private/logger.mjs'; -import {checkSWFileCacheHeaders} from 'workbox-core/_private/checkSWFileCacheHeaders.mjs'; - - -describe(`checkSWFileCacheHeaders`, function() { - let sandbox = sinon.createSandbox(); - - beforeEach(function() { - if (process.env.NODE_ENV !== 'production') { - sandbox.stub(logger); - } - }); - - afterEach(function() { - sandbox.restore(); - }); - - it(`should handle bad response`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('Not Found.', { - status: 404, - }); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(0); - }); - - it(`should handle no cache-control header`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('OK'); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(0); - }); - - it(`should log for bad cache-control header`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('OK', { - headers: { - 'cache-control': 'max-age=2000', - }, - }); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(1); - }); - - it(`should handle unexpected max-age cache-control header`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('OK', { - headers: { - 'cache-control': 'max-age=abc', - }, - }); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(1); - }); - - it(`should NOT log for max-age=0 cache-control header`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('OK', { - headers: { - 'cache-control': 'max-age=0', - }, - }); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(0); - }); - - it(`should NOT log for no-cache cache-control header`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('OK', { - headers: { - 'cache-control': 'no-cache', - }, - }); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(0); - }); - - it(`should NOT log for no-store cache-control header`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - sandbox.stub(self, 'fetch').callsFake(async () => { - return new Response('OK', { - headers: { - 'cache-control': 'no-cache', - }, - }); - }); - - await checkSWFileCacheHeaders(); - - expect(logger.warn.callCount).to.equal(0); - }); -}); diff --git a/test/workbox-core/sw/_private/test-executeQuotaErrorCallbacks.mjs b/test/workbox-core/sw/_private/test-executeQuotaErrorCallbacks.mjs new file mode 100644 index 000000000..faa44a3ec --- /dev/null +++ b/test/workbox-core/sw/_private/test-executeQuotaErrorCallbacks.mjs @@ -0,0 +1,55 @@ +/* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. +*/ + +import {executeQuotaErrorCallbacks} from 'workbox-core/_private/executeQuotaErrorCallbacks.mjs'; +import {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.mjs'; + + +describe(`executeQuotaErrorCallbacks()`, function() { + const sandbox = sinon.createSandbox(); + + afterEach(function() { + sandbox.restore(); + }); + + it('should call everything registered with registerQuotaErrorCallback()', async function() { + const callback1 = sandbox.stub(); + registerQuotaErrorCallback(callback1); + const callback2 = sandbox.stub(); + registerQuotaErrorCallback(callback2); + + await executeQuotaErrorCallbacks(); + + expect(callback1.calledOnce).to.be.true; + expect(callback2.calledOnce).to.be.true; + }); + + it(`shouldn't have any effect if called multiple times with the same callback`, async function() { + const callback1 = sandbox.stub(); + registerQuotaErrorCallback(callback1); + registerQuotaErrorCallback(callback1); + registerQuotaErrorCallback(callback1); + + await executeQuotaErrorCallbacks(); + + expect(callback1.calledOnce).to.be.true; + }); + + it(`should call everything registered with registerQuotaErrorCallback(), each time it's called`, async function() { + const callback1 = sandbox.stub(); + registerQuotaErrorCallback(callback1); + const callback2 = sandbox.stub(); + registerQuotaErrorCallback(callback2); + + await executeQuotaErrorCallbacks(); + await executeQuotaErrorCallbacks(); + + expect(callback1.calledTwice).to.be.true; + expect(callback2.calledTwice).to.be.true; + }); +}); diff --git a/test/workbox-core/sw/_private/test-quota.mjs b/test/workbox-core/sw/_private/test-quota.mjs deleted file mode 100644 index 067cdf8a3..000000000 --- a/test/workbox-core/sw/_private/test-quota.mjs +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2018 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. -*/ - -import {executeQuotaErrorCallbacks, registerQuotaErrorCallback} from 'workbox-core/_private/quota.mjs'; - - -describe(`quota`, function() { - describe(`registerQuotaErrorCallback()`, function() { - it(`should throw when passed a non-function in dev mode`, async function() { - if (process.env.NODE_ENV === 'production') this.skip(); - - await expectError(() => registerQuotaErrorCallback(null), 'incorrect-type'); - }); - }); - - describe(`executeQuotaErrorCallbacks()`, function() { - const sandbox = sinon.createSandbox(); - - afterEach(function() { - sandbox.restore(); - }); - - it('should call everything registered with registerQuotaErrorCallback()', async function() { - const callback1 = sandbox.stub(); - registerQuotaErrorCallback(callback1); - const callback2 = sandbox.stub(); - registerQuotaErrorCallback(callback2); - - await executeQuotaErrorCallbacks(); - - expect(callback1.calledOnce).to.be.true; - expect(callback2.calledOnce).to.be.true; - }); - - it(`shouldn't have any effect if called multiple times with the same callback`, async function() { - const callback1 = sandbox.stub(); - registerQuotaErrorCallback(callback1); - registerQuotaErrorCallback(callback1); - registerQuotaErrorCallback(callback1); - - await executeQuotaErrorCallbacks(); - - expect(callback1.calledOnce).to.be.true; - }); - - it(`should call everything registered with registerQuotaErrorCallback(), each time it's called`, async function() { - const callback1 = sandbox.stub(); - registerQuotaErrorCallback(callback1); - const callback2 = sandbox.stub(); - registerQuotaErrorCallback(callback2); - - await executeQuotaErrorCallbacks(); - await executeQuotaErrorCallbacks(); - - expect(callback1.calledTwice).to.be.true; - expect(callback2.calledTwice).to.be.true; - }); - }); -}); diff --git a/test/workbox-core/sw/test-registerQuotaErrorCallback.mjs b/test/workbox-core/sw/test-registerQuotaErrorCallback.mjs new file mode 100644 index 000000000..e9d22c977 --- /dev/null +++ b/test/workbox-core/sw/test-registerQuotaErrorCallback.mjs @@ -0,0 +1,18 @@ +/* + Copyright 2018 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. +*/ + +import {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.mjs'; + + +describe(`registerQuotaErrorCallback()`, function() { + it(`should throw when passed a non-function in dev mode`, async function() { + if (process.env.NODE_ENV === 'production') this.skip(); + + await expectError(() => registerQuotaErrorCallback(null), 'incorrect-type'); + }); +}); diff --git a/test/workbox-expiration/sw/test-Plugin.mjs b/test/workbox-expiration/sw/test-Plugin.mjs index 0a06e383e..d389fa775 100644 --- a/test/workbox-expiration/sw/test-Plugin.mjs +++ b/test/workbox-expiration/sw/test-Plugin.mjs @@ -9,7 +9,7 @@ import {Plugin} from 'workbox-expiration/Plugin.mjs'; import {CacheExpiration} from 'workbox-expiration/CacheExpiration.mjs'; import {cacheNames} from 'workbox-core/_private/cacheNames.mjs'; -import {executeQuotaErrorCallbacks} from 'workbox-core/_private/quota.mjs'; +import {executeQuotaErrorCallbacks} from 'workbox-core/_private/executeQuotaErrorCallbacks.mjs'; describe(`Plugin`, function() {