diff --git a/docs/options.md b/docs/options.md index 2a67e0c975..deda2df1 100755 --- a/docs/options.md +++ b/docs/options.md @@ -129,6 +129,10 @@ Allows you to adjust what and how to cache the assets. > Default: `'all'`. +* `shouldServeFromNetwork`: `(response: Response, urlString: string, cacheUrl: string) => boolean`. +Allows to customize the behaviour when to fallback to a cached version of a resource if `responseStrategy: 'network-first'`. +Defaults to `(response) => response.ok`, meaning a response is only valid when the server returns a 2xx status code (see https://developer.mozilla.org/de/docs/Web/API/Response). + #### `AppCache: Object | null | false` Settings for the `AppCache` cache. Use `null` or `false` to disable `AppCache` generation. diff --git a/lib/misc/sw-loader.js b/lib/misc/sw-loader.js index 9c3f8c67..e034fddb 100644 --- a/lib/misc/sw-loader.js +++ b/lib/misc/sw-loader.js @@ -51,7 +51,7 @@ module.exports.pitch = function pitch(remainingRequest, precedingRequest, data) var navigationPreloadCode = params.navigationPreload; - var helpersCode = [', {', 'loaders: ' + loadersCode + ',', 'cacheMaps: ' + cacheMapsCode + ',', 'navigationPreload: ' + navigationPreloadCode + ',', '}']; + var helpersCode = [', {', 'shouldServeFromNetwork: ' + params.shouldServeFromNetwork + ',', 'loaders: ' + loadersCode + ',', 'cacheMaps: ' + cacheMapsCode + ',', 'navigationPreload: ' + navigationPreloadCode + ',', '}']; Promise.all([].concat(_toConsumableArray(modules.map(function (mod) { return readFile(path.join(__dirname, mod)); diff --git a/lib/misc/sw-template.js b/lib/misc/sw-template.js index 37d7d1c1..59a801e3 100644 --- a/lib/misc/sw-template.js +++ b/lib/misc/sw-template.js @@ -382,9 +382,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } diff --git a/lib/service-worker.js b/lib/service-worker.js index 8ed8ebd2..8beb094c 100644 --- a/lib/service-worker.js +++ b/lib/service-worker.js @@ -26,6 +26,8 @@ var _deepExtend2 = _interopRequireDefault(_deepExtend); var _miscUtils = require('./misc/utils'); +var REGEX_MINIFY_FUNC = /(\r\n|\n|\r|\s+)/gm; + var ServiceWorker = (function () { function ServiceWorker(options) { _classCallCheck(this, ServiceWorker); @@ -37,7 +39,7 @@ var ServiceWorker = (function () { this.minify = options.minify; this.output = options.output.replace(/^\.\/+/, ''); this.publicPath = options.publicPath; - + this.shouldServeFromNetwork = options.shouldServeFromNetwork; this.basePath = null; this.location = null; this.pathRewrite = null; @@ -76,6 +78,7 @@ var ServiceWorker = (function () { var data = JSON.stringify({ data_var_name: this.SW_DATA_VAR, cacheMaps: plugin.cacheMaps, + shouldServeFromNetwork: (0, _miscUtils.functionToString)(this.shouldServeFromNetwork), navigationPreload: this.stringifyNavigationPreload(this.navigationPreload, plugin) }); @@ -222,7 +225,9 @@ var ServiceWorker = (function () { pluginVersion = plugin.pluginVersion; } - return ('\n var ' + this.SW_DATA_VAR + ' = ' + JSON.stringify({ + var loaders = Object.keys(plugin.loaders || {}).length ? plugin.loaders : void 0; + + var result = ('\n var ' + this.SW_DATA_VAR + ' = ' + JSON.stringify({ assets: { main: cache('main'), additional: cache('additional'), @@ -230,7 +235,6 @@ var ServiceWorker = (function () { }, externals: externals, - hashesMap: hashesMap, strategy: plugin.strategy, @@ -241,12 +245,14 @@ var ServiceWorker = (function () { relativePaths: plugin.relativePaths, prefetchRequest: this.prefetchRequest, - + loaders: loaders, // These aren't added alwaysRevalidate: plugin.alwaysRevalidate, preferOnline: plugin.preferOnline, ignoreSearch: plugin.ignoreSearch }, null, minify ? void 0 : ' ') + ';\n ').trim(); + + return result; } }, { key: 'getConfig', diff --git a/src/misc/sw-loader.js b/src/misc/sw-loader.js index 9e24cd6c..3a9a66f7 100644 --- a/src/misc/sw-loader.js +++ b/src/misc/sw-loader.js @@ -57,6 +57,7 @@ module.exports.pitch = function pitch(remainingRequest, precedingRequest, data) const helpersCode = [ ', {', + `shouldServeFromNetwork: ${params.shouldServeFromNetwork},`, `loaders: ${loadersCode},`, `cacheMaps: ${cacheMapsCode},`, `navigationPreload: ${navigationPreloadCode},`, @@ -90,4 +91,4 @@ function readFile(path) { resolve(file); }); }); -} \ No newline at end of file +} diff --git a/src/misc/sw-template.js b/src/misc/sw-template.js index 8dfe6afe..1ebc886d 100644 --- a/src/misc/sw-template.js +++ b/src/misc/sw-template.js @@ -397,10 +397,17 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event) .then((response) => { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', `URL [${ urlString }] from network`); } @@ -748,4 +755,4 @@ function logGroup(title, assets) { }); console.groupEnd(); -} \ No newline at end of file +} diff --git a/src/service-worker.js b/src/service-worker.js index 619b32e6..6d248635 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -7,6 +7,8 @@ import { isAbsolutePath, functionToString } from './misc/utils'; +const REGEX_MINIFY_FUNC = /(\r\n|\n|\r|\s+)/gm; + export default class ServiceWorker { constructor(options) { if (isAbsolutePath(options.output)) { @@ -19,7 +21,7 @@ export default class ServiceWorker { this.minify = options.minify; this.output = options.output.replace(/^\.\/+/, ''); this.publicPath = options.publicPath; - + this.shouldServeFromNetwork = options.shouldServeFromNetwork; this.basePath = null; this.location = null; this.pathRewrite = null; @@ -56,6 +58,7 @@ export default class ServiceWorker { const data = JSON.stringify({ data_var_name: this.SW_DATA_VAR, cacheMaps: plugin.cacheMaps, + shouldServeFromNetwork: functionToString(this.shouldServeFromNetwork), navigationPreload: this.stringifyNavigationPreload(this.navigationPreload, plugin) }); @@ -213,7 +216,10 @@ export default class ServiceWorker { pluginVersion = plugin.pluginVersion; } - return ` + const loaders = Object.keys(plugin.loaders || {}).length ? + plugin.loaders : void 0; + + const result = ` var ${ this.SW_DATA_VAR } = ${ JSON.stringify({ assets: { main: cache('main'), @@ -222,7 +228,6 @@ export default class ServiceWorker { }, externals: externals, - hashesMap: hashesMap, strategy: plugin.strategy, @@ -233,13 +238,15 @@ export default class ServiceWorker { relativePaths: plugin.relativePaths, prefetchRequest: this.prefetchRequest, - + loaders: loaders, // These aren't added alwaysRevalidate: plugin.alwaysRevalidate, preferOnline: plugin.preferOnline, ignoreSearch: plugin.ignoreSearch, }, null, minify ? void 0 : ' ') }; `.trim(); + + return result; } getConfig(plugin) { diff --git a/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack2/sw.js b/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack2/sw.js index 01a7e8c7..b5ad7806 100644 --- a/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack2/sw.js +++ b/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack2/sw.js @@ -522,9 +522,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -870,6 +877,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack3/sw.js b/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack3/sw.js index a823b2fc..25210a6c 100644 --- a/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack3/sw.js +++ b/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack3/sw.js @@ -513,9 +513,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -861,6 +868,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack4/sw.js b/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack4/sw.js index 64ac5c5a..4971e05d 100644 --- a/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack4/sw.js +++ b/tests/legacy/fixtures/appshell-absolute-path/__expected/webpack4/sw.js @@ -534,9 +534,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -882,6 +889,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/appshell-basic/__expected/webpack2/sw.js b/tests/legacy/fixtures/appshell-basic/__expected/webpack2/sw.js index 09a08ea9..a688ee6d 100644 --- a/tests/legacy/fixtures/appshell-basic/__expected/webpack2/sw.js +++ b/tests/legacy/fixtures/appshell-basic/__expected/webpack2/sw.js @@ -522,9 +522,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -870,6 +877,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/appshell-basic/__expected/webpack3/sw.js b/tests/legacy/fixtures/appshell-basic/__expected/webpack3/sw.js index 4a4e8950..60d4dc3a 100644 --- a/tests/legacy/fixtures/appshell-basic/__expected/webpack3/sw.js +++ b/tests/legacy/fixtures/appshell-basic/__expected/webpack3/sw.js @@ -513,9 +513,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -861,6 +868,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/appshell-basic/__expected/webpack4/sw.js b/tests/legacy/fixtures/appshell-basic/__expected/webpack4/sw.js index 797a8035..eda8fb58 100644 --- a/tests/legacy/fixtures/appshell-basic/__expected/webpack4/sw.js +++ b/tests/legacy/fixtures/appshell-basic/__expected/webpack4/sw.js @@ -534,9 +534,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -882,6 +889,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack2/sw.js b/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack2/sw.js index b55d2808..34fd3ee5 100644 --- a/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack2/sw.js +++ b/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack2/sw.js @@ -522,9 +522,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -870,6 +877,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [], navigationPreload: false, diff --git a/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack3/sw.js b/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack3/sw.js index 0141d2c4..a711b29f 100644 --- a/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack3/sw.js +++ b/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack3/sw.js @@ -513,9 +513,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -861,6 +868,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [], navigationPreload: false, diff --git a/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack4/sw.js b/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack4/sw.js index 42393adb..1c11a8bc 100644 --- a/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack4/sw.js +++ b/tests/legacy/fixtures/basic-wIth-sw-out/__expected/webpack4/sw.js @@ -534,9 +534,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -882,6 +889,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [], navigationPreload: false, diff --git a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/sw.js b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/sw.js new file mode 100644 index 00000000..ad57d16c --- /dev/null +++ b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/sw.js @@ -0,0 +1,907 @@ +var __wpo = { + "assets": { + "main": [ + "./external.js" + ], + "additional": [], + "optional": [] + }, + "externals": [ + "./external.js" + ], + "hashesMap": {}, + "strategy": "changed", + "responseStrategy": "cache-first", + "version": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "name": "webpack-offline", + "relativePaths": true +}; + +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +(function () { + var waitUntil = ExtendableEvent.prototype.waitUntil; + var respondWith = FetchEvent.prototype.respondWith; + var promisesMap = new WeakMap(); + + ExtendableEvent.prototype.waitUntil = function (promise) { + var extendableEvent = this; + var promises = promisesMap.get(extendableEvent); + + if (promises) { + promises.push(Promise.resolve(promise)); + return; + } + + promises = [Promise.resolve(promise)]; + promisesMap.set(extendableEvent, promises); + + // call original method + return waitUntil.call(extendableEvent, Promise.resolve().then(function processPromises() { + var len = promises.length; + + // wait for all to settle + return Promise.all(promises.map(function (p) { + return p["catch"](function () {}); + })).then(function () { + // have new items been added? If so, wait again + if (promises.length != len) return processPromises(); + // we're done! + promisesMap["delete"](extendableEvent); + // reject if one of the promises rejected + return Promise.all(promises); + }); + })); + }; + + FetchEvent.prototype.respondWith = function (promise) { + this.waitUntil(promise); + return respondWith.call(this, promise); + }; +})();; + 'use strict'; + +if (typeof DEBUG === 'undefined') { + var DEBUG = false; +} + +function WebpackServiceWorker(params, helpers) { + var cacheMaps = helpers.cacheMaps; + // navigationPreload: true, { map: (URL) => URL, test: (URL) => boolean } + var navigationPreload = helpers.navigationPreload; + + // (update)strategy: changed, all + var strategy = params.strategy; + // responseStrategy: cache-first, network-first + var responseStrategy = params.responseStrategy; + + var assets = params.assets; + + var hashesMap = params.hashesMap; + var externals = params.externals; + + var prefetchRequest = params.prefetchRequest || { + credentials: 'same-origin', + mode: 'cors' + }; + + var CACHE_PREFIX = params.name; + var CACHE_TAG = params.version; + var CACHE_NAME = CACHE_PREFIX + ':' + CACHE_TAG; + + var PRELOAD_CACHE_NAME = CACHE_PREFIX + '$preload'; + var STORED_DATA_KEY = '__offline_webpack__data'; + + mapAssets(); + + var allAssets = [].concat(assets.main, assets.additional, assets.optional); + + self.addEventListener('install', function (event) { + console.log('[SW]:', 'Install event'); + + var installing = undefined; + + if (strategy === 'changed') { + installing = cacheChanged('main'); + } else { + installing = cacheAssets('main'); + } + + event.waitUntil(installing); + }); + + self.addEventListener('activate', function (event) { + console.log('[SW]:', 'Activate event'); + + var activation = cacheAdditional(); + + // Delete all assets which name starts with CACHE_PREFIX and + // is not current cache (CACHE_NAME) + activation = activation.then(storeCacheData); + activation = activation.then(deleteObsolete); + activation = activation.then(function () { + if (self.clients && self.clients.claim) { + return self.clients.claim(); + } + }); + + if (navigationPreload && self.registration.navigationPreload) { + activation = Promise.all([activation, self.registration.navigationPreload.enable()]); + } + + event.waitUntil(activation); + }); + + function cacheAdditional() { + if (!assets.additional.length) { + return Promise.resolve(); + } + + if (DEBUG) { + console.log('[SW]:', 'Caching additional'); + } + + var operation = undefined; + + if (strategy === 'changed') { + operation = cacheChanged('additional'); + } else { + operation = cacheAssets('additional'); + } + + // Ignore fail of `additional` cache section + return operation['catch'](function (e) { + console.error('[SW]:', 'Cache section `additional` failed to load'); + }); + } + + function cacheAssets(section) { + var batch = assets[section]; + + return caches.open(CACHE_NAME).then(function (cache) { + return addAllNormalized(cache, batch, { + bust: params.version, + request: prefetchRequest, + failAll: section === 'main' + }); + }).then(function () { + logGroup('Cached assets: ' + section, batch); + })['catch'](function (e) { + console.error(e); + throw e; + }); + } + + function cacheChanged(section) { + return getLastCache().then(function (args) { + if (!args) { + return cacheAssets(section); + } + + var lastCache = args[0]; + var lastKeys = args[1]; + var lastData = args[2]; + + var lastMap = lastData.hashmap; + var lastVersion = lastData.version; + + if (!lastData.hashmap || lastVersion === params.version) { + return cacheAssets(section); + } + + var lastHashedAssets = Object.keys(lastMap).map(function (hash) { + return lastMap[hash]; + }); + + var lastUrls = lastKeys.map(function (req) { + var url = new URL(req.url); + url.search = ''; + url.hash = ''; + + return url.toString(); + }); + + var sectionAssets = assets[section]; + var moved = []; + var changed = sectionAssets.filter(function (url) { + if (lastUrls.indexOf(url) === -1 || lastHashedAssets.indexOf(url) === -1) { + return true; + } + + return false; + }); + + Object.keys(hashesMap).forEach(function (hash) { + var asset = hashesMap[hash]; + + // Return if not in sectionAssets or in changed or moved array + if (sectionAssets.indexOf(asset) === -1 || changed.indexOf(asset) !== -1 || moved.indexOf(asset) !== -1) return; + + var lastAsset = lastMap[hash]; + + if (lastAsset && lastUrls.indexOf(lastAsset) !== -1) { + moved.push([lastAsset, asset]); + } else { + changed.push(asset); + } + }); + + logGroup('Changed assets: ' + section, changed); + logGroup('Moved assets: ' + section, moved); + + var movedResponses = Promise.all(moved.map(function (pair) { + return lastCache.match(pair[0]).then(function (response) { + return [pair[1], response]; + }); + })); + + return caches.open(CACHE_NAME).then(function (cache) { + var move = movedResponses.then(function (responses) { + return Promise.all(responses.map(function (pair) { + return cache.put(pair[0], pair[1]); + })); + }); + + return Promise.all([move, addAllNormalized(cache, changed, { + bust: params.version, + request: prefetchRequest, + failAll: section === 'main', + deleteFirst: section !== 'main' + })]); + }); + }); + } + + function deleteObsolete() { + return caches.keys().then(function (keys) { + var all = keys.map(function (key) { + if (key.indexOf(CACHE_PREFIX) !== 0 || key.indexOf(CACHE_NAME) === 0) return; + + console.log('[SW]:', 'Delete cache:', key); + return caches['delete'](key); + }); + + return Promise.all(all); + }); + } + + function getLastCache() { + return caches.keys().then(function (keys) { + var index = keys.length; + var key = undefined; + + while (index--) { + key = keys[index]; + + if (key.indexOf(CACHE_PREFIX) === 0) { + break; + } + } + + if (!key) return; + + var cache = undefined; + + return caches.open(key).then(function (_cache) { + cache = _cache; + return _cache.match(new URL(STORED_DATA_KEY, location).toString()); + }).then(function (response) { + if (!response) return; + + return Promise.all([cache, cache.keys(), response.json()]); + }); + }); + } + + function storeCacheData() { + return caches.open(CACHE_NAME).then(function (cache) { + var data = new Response(JSON.stringify({ + version: params.version, + hashmap: hashesMap + })); + + return cache.put(new URL(STORED_DATA_KEY, location).toString(), data); + }); + } + + self.addEventListener('fetch', function (event) { + // Handle only GET requests + if (event.request.method !== 'GET') { + return; + } + + // This prevents some weird issue with Chrome DevTools and 'only-if-cached' + // Fixes issue #385, also ref to: + // - https://github.com/paulirish/caltrainschedule.io/issues/49 + // - https://bugs.chromium.org/p/chromium/issues/detail?id=823392 + if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { + return; + } + + var url = new URL(event.request.url); + url.hash = ''; + + var urlString = url.toString(); + + // Not external, so search part of the URL should be stripped, + // if it's external URL, the search part should be kept + if (externals.indexOf(urlString) === -1) { + url.search = ''; + urlString = url.toString(); + } + + var assetMatches = allAssets.indexOf(urlString) !== -1; + var cacheUrl = urlString; + + if (!assetMatches) { + var cacheRewrite = matchCacheMap(event.request); + + if (cacheRewrite) { + cacheUrl = cacheRewrite; + assetMatches = true; + } + } + + if (!assetMatches) { + // Use request.mode === 'navigate' instead of isNavigateRequest + // because everything what supports navigationPreload supports + // 'navigate' request.mode + if (event.request.mode === 'navigate') { + // Requesting with fetchWithPreload(). + // Preload is used only if navigationPreload is enabled and + // navigationPreload mapping is not used. + if (navigationPreload === true) { + event.respondWith(fetchWithPreload(event)); + return; + } + } + + // Something else, positive, but not `true` + if (navigationPreload) { + var preloadedResponse = retrivePreloadedResponse(event); + + if (preloadedResponse) { + event.respondWith(preloadedResponse); + return; + } + } + + // Logic exists here if no cache match + return; + } + + // Cache handling/storing/fetching starts here + var resource = undefined; + + if (responseStrategy === 'network-first') { + resource = networkFirstResponse(event, urlString, cacheUrl); + } + // 'cache-first' otherwise + // (responseStrategy has been validated before) + else { + resource = cacheFirstResponse(event, urlString, cacheUrl); + } + + event.respondWith(resource); + }); + + self.addEventListener('message', function (e) { + var data = e.data; + if (!data) return; + + switch (data.action) { + case 'skipWaiting': + { + if (self.skipWaiting) self.skipWaiting(); + }break; + } + }); + + function cacheFirstResponse(event, urlString, cacheUrl) { + handleNavigationPreload(event); + + return cachesMatch(cacheUrl, CACHE_NAME).then(function (response) { + if (response) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + cacheUrl + '](' + urlString + ') from cache'); + } + + return response; + } + + // Load and cache known assets + var fetching = fetch(event.request).then(function (response) { + if (!response.ok) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] wrong response: [' + response.status + '] ' + response.type); + } + + return response; + } + + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] from network'); + } + + if (cacheUrl === urlString) { + (function () { + var responseClone = response.clone(); + var storing = caches.open(CACHE_NAME).then(function (cache) { + return cache.put(urlString, responseClone); + }).then(function () { + console.log('[SW]:', 'Cache asset: ' + urlString); + }); + + event.waitUntil(storing); + })(); + } + + return response; + }); + + return fetching; + }); + } + + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + + function networkFirstResponse(event, urlString, cacheUrl) { + return fetchWithPreload(event).then(function (response) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] from network'); + } + + return response; + } + + // Throw to reach the code in the catch below + throw response; + }) + // This needs to be in a catch() and not just in the then() above + // cause if your network is down, the fetch() will throw + ['catch'](function (erroredResponse) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] from cache if possible'); + } + + return cachesMatch(cacheUrl, CACHE_NAME).then(function (response) { + if (response) { + return response; + } + + if (erroredResponse instanceof Response) { + return erroredResponse; + } + + // Not a response at this point, some other error + throw erroredResponse; + // return Response.error(); + }); + }); + } + + function handleNavigationPreload(event) { + if (navigationPreload && typeof navigationPreload.map === 'function' && + // Use request.mode === 'navigate' instead of isNavigateRequest + // because everything what supports navigationPreload supports + // 'navigate' request.mode + event.preloadResponse && event.request.mode === 'navigate') { + var mapped = navigationPreload.map(new URL(event.request.url), event.request); + + if (mapped) { + storePreloadedResponse(mapped, event); + } + } + } + + // Temporary in-memory store for faster access + var navigationPreloadStore = new Map(); + + function storePreloadedResponse(_url, event) { + var url = new URL(_url, location); + var preloadResponsePromise = event.preloadResponse; + + navigationPreloadStore.set(preloadResponsePromise, { + url: url, + response: preloadResponsePromise + }); + + var isSamePreload = function isSamePreload() { + return navigationPreloadStore.has(preloadResponsePromise); + }; + + var storing = preloadResponsePromise.then(function (res) { + // Return if preload isn't enabled or hasn't happened + if (!res) return; + + // If navigationPreloadStore already consumed + // or navigationPreloadStore already contains another preload, + // then do not store anything and return + if (!isSamePreload()) { + return; + } + + var clone = res.clone(); + + // Storing the preload response for later consume (hasn't yet been consumed) + return caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + if (!isSamePreload()) return; + + return cache.put(url, clone).then(function () { + if (!isSamePreload()) { + return caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + return cache['delete'](url); + }); + } + }); + }); + }); + + event.waitUntil(storing); + } + + function retriveInMemoryPreloadedResponse(url) { + if (!navigationPreloadStore) { + return; + } + + var foundResponse = undefined; + var foundKey = undefined; + + navigationPreloadStore.forEach(function (store, key) { + if (store.url.href === url.href) { + foundResponse = store.response; + foundKey = key; + } + }); + + if (foundResponse) { + navigationPreloadStore['delete'](foundKey); + return foundResponse; + } + } + + function retrivePreloadedResponse(event) { + var url = new URL(event.request.url); + + if (self.registration.navigationPreload && navigationPreload && navigationPreload.test && navigationPreload.test(url, event.request)) {} else { + return; + } + + var fromMemory = retriveInMemoryPreloadedResponse(url); + var request = event.request; + + if (fromMemory) { + event.waitUntil(caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + return cache['delete'](request); + })); + + return fromMemory; + } + + return cachesMatch(request, PRELOAD_CACHE_NAME).then(function (response) { + if (response) { + event.waitUntil(caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + return cache['delete'](request); + })); + } + + return response || fetch(event.request); + }); + } + + function mapAssets() { + Object.keys(assets).forEach(function (key) { + assets[key] = assets[key].map(function (path) { + var url = new URL(path, location); + + url.hash = ''; + + if (externals.indexOf(path) === -1) { + url.search = ''; + } + + return url.toString(); + }); + }); + + hashesMap = Object.keys(hashesMap).reduce(function (result, hash) { + var url = new URL(hashesMap[hash], location); + url.search = ''; + url.hash = ''; + + result[hash] = url.toString(); + return result; + }, {}); + + externals = externals.map(function (path) { + var url = new URL(path, location); + url.hash = ''; + + return url.toString(); + }); + } + + function addAllNormalized(cache, requests, options) { + var bustValue = options.bust; + var failAll = options.failAll !== false; + var deleteFirst = options.deleteFirst === true; + var requestInit = options.request || { + credentials: 'omit', + mode: 'cors' + }; + + var deleting = Promise.resolve(); + + if (deleteFirst) { + deleting = Promise.all(requests.map(function (request) { + return cache['delete'](request)['catch'](function () {}); + })); + } + + return Promise.all(requests.map(function (request) { + if (bustValue) { + request = applyCacheBust(request, bustValue); + } + + return fetch(request, requestInit).then(fixRedirectedResponse).then(function (response) { + if (!response.ok) { + return { error: true }; + } + + return { response: response }; + }, function () { + return { error: true }; + }); + })).then(function (responses) { + if (failAll && responses.some(function (data) { + return data.error; + })) { + return Promise.reject(new Error('Wrong response status')); + } + + if (!failAll) { + responses = responses.filter(function (data) { + return !data.error; + }); + } + + return deleting.then(function () { + var addAll = responses.map(function (_ref, i) { + var response = _ref.response; + + return cache.put(requests[i], response); + }); + + return Promise.all(addAll); + }); + }); + } + + function matchCacheMap(request) { + var urlString = request.url; + var url = new URL(urlString); + + var requestType = undefined; + + if (isNavigateRequest(request)) { + requestType = 'navigate'; + } else if (url.origin === location.origin) { + requestType = 'same-origin'; + } else { + requestType = 'cross-origin'; + } + + for (var i = 0; i < cacheMaps.length; i++) { + var map = cacheMaps[i]; + + if (!map) continue; + if (map.requestTypes && map.requestTypes.indexOf(requestType) === -1) { + continue; + } + + var newString = undefined; + + if (typeof map.match === 'function') { + newString = map.match(url, request); + } else { + newString = urlString.replace(map.match, map.to); + } + + if (newString && newString !== urlString) { + return newString; + } + } + } + + function fetchWithPreload(event) { + if (!event.preloadResponse || navigationPreload !== true) { + return fetch(event.request); + } + + return event.preloadResponse.then(function (response) { + return response || fetch(event.request); + }); + } +} + +function cachesMatch(request, cacheName) { + return caches.match(request, { + cacheName: cacheName + }).then(function (response) { + if (isNotRedirectedResponse(response)) { + return response; + } + + // Fix already cached redirected responses + return fixRedirectedResponse(response).then(function (fixedResponse) { + return caches.open(cacheName).then(function (cache) { + return cache.put(request, fixedResponse); + }).then(function () { + return fixedResponse; + }); + }); + }) + // Return void if error happened (cache not found) + ['catch'](function () {}); +} + +function applyCacheBust(asset, key) { + var hasQuery = asset.indexOf('?') !== -1; + return asset + (hasQuery ? '&' : '?') + '__uncache=' + encodeURIComponent(key); +} + +function isNavigateRequest(request) { + return request.mode === 'navigate' || request.headers.get('Upgrade-Insecure-Requests') || (request.headers.get('Accept') || '').indexOf('text/html') !== -1; +} + +function isNotRedirectedResponse(response) { + return !response || !response.redirected || !response.ok || response.type === 'opaqueredirect'; +} + +// Based on https://github.com/GoogleChrome/sw-precache/pull/241/files#diff-3ee9060dc7a312c6a822cac63a8c630bR85 +function fixRedirectedResponse(response) { + if (isNotRedirectedResponse(response)) { + return Promise.resolve(response); + } + + var body = 'body' in response ? Promise.resolve(response.body) : response.blob(); + + return body.then(function (data) { + return new Response(data, { + headers: response.headers, + status: response.status + }); + }); +} + +function copyObject(original) { + return Object.keys(original).reduce(function (result, key) { + result[key] = original[key]; + return result; + }, {}); +} + +function logGroup(title, assets) { + console.groupCollapsed('[SW]:', title); + + assets.forEach(function (asset) { + console.log('Asset:', asset); + }); + + console.groupEnd(); +} + WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), +loaders: {}, +cacheMaps: [], +navigationPreload: false, +}); + module.exports = __webpack_require__(1) + + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + +// custom sw entry + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack2/sw.js b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack2/sw.js index 54141e82..f7b9ccaa 100644 --- a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack2/sw.js +++ b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack2/sw.js @@ -522,9 +522,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -870,6 +877,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [], navigationPreload: false, diff --git a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack3/sw.js b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack3/sw.js index 594e780c..3ce80423 100644 --- a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack3/sw.js +++ b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack3/sw.js @@ -513,9 +513,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -861,6 +868,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [], navigationPreload: false, diff --git a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack4/sw.js b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack4/sw.js index 685f26c6..ad57d16c 100644 --- a/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack4/sw.js +++ b/tests/legacy/fixtures/basic-with-sw-custom-entry/__expected/webpack4/sw.js @@ -534,9 +534,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -882,6 +889,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [], navigationPreload: false, diff --git a/tests/legacy/fixtures/cachemaps-basic/__expected/sw.js b/tests/legacy/fixtures/cachemaps-basic/__expected/sw.js new file mode 100644 index 00000000..0ebdcca1 --- /dev/null +++ b/tests/legacy/fixtures/cachemaps-basic/__expected/sw.js @@ -0,0 +1,931 @@ +var __wpo = { + "assets": { + "main": [], + "additional": [], + "optional": [] + }, + "externals": [], + "hashesMap": {}, + "strategy": "changed", + "responseStrategy": "cache-first", + "version": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "name": "webpack-offline", + "relativePaths": true +}; + +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +(function () { + var waitUntil = ExtendableEvent.prototype.waitUntil; + var respondWith = FetchEvent.prototype.respondWith; + var promisesMap = new WeakMap(); + + ExtendableEvent.prototype.waitUntil = function (promise) { + var extendableEvent = this; + var promises = promisesMap.get(extendableEvent); + + if (promises) { + promises.push(Promise.resolve(promise)); + return; + } + + promises = [Promise.resolve(promise)]; + promisesMap.set(extendableEvent, promises); + + // call original method + return waitUntil.call(extendableEvent, Promise.resolve().then(function processPromises() { + var len = promises.length; + + // wait for all to settle + return Promise.all(promises.map(function (p) { + return p["catch"](function () {}); + })).then(function () { + // have new items been added? If so, wait again + if (promises.length != len) return processPromises(); + // we're done! + promisesMap["delete"](extendableEvent); + // reject if one of the promises rejected + return Promise.all(promises); + }); + })); + }; + + FetchEvent.prototype.respondWith = function (promise) { + this.waitUntil(promise); + return respondWith.call(this, promise); + }; +})();; + 'use strict'; + +if (typeof DEBUG === 'undefined') { + var DEBUG = false; +} + +function WebpackServiceWorker(params, helpers) { + var cacheMaps = helpers.cacheMaps; + // navigationPreload: true, { map: (URL) => URL, test: (URL) => boolean } + var navigationPreload = helpers.navigationPreload; + + // (update)strategy: changed, all + var strategy = params.strategy; + // responseStrategy: cache-first, network-first + var responseStrategy = params.responseStrategy; + + var assets = params.assets; + + var hashesMap = params.hashesMap; + var externals = params.externals; + + var prefetchRequest = params.prefetchRequest || { + credentials: 'same-origin', + mode: 'cors' + }; + + var CACHE_PREFIX = params.name; + var CACHE_TAG = params.version; + var CACHE_NAME = CACHE_PREFIX + ':' + CACHE_TAG; + + var PRELOAD_CACHE_NAME = CACHE_PREFIX + '$preload'; + var STORED_DATA_KEY = '__offline_webpack__data'; + + mapAssets(); + + var allAssets = [].concat(assets.main, assets.additional, assets.optional); + + self.addEventListener('install', function (event) { + console.log('[SW]:', 'Install event'); + + var installing = undefined; + + if (strategy === 'changed') { + installing = cacheChanged('main'); + } else { + installing = cacheAssets('main'); + } + + event.waitUntil(installing); + }); + + self.addEventListener('activate', function (event) { + console.log('[SW]:', 'Activate event'); + + var activation = cacheAdditional(); + + // Delete all assets which name starts with CACHE_PREFIX and + // is not current cache (CACHE_NAME) + activation = activation.then(storeCacheData); + activation = activation.then(deleteObsolete); + activation = activation.then(function () { + if (self.clients && self.clients.claim) { + return self.clients.claim(); + } + }); + + if (navigationPreload && self.registration.navigationPreload) { + activation = Promise.all([activation, self.registration.navigationPreload.enable()]); + } + + event.waitUntil(activation); + }); + + function cacheAdditional() { + if (!assets.additional.length) { + return Promise.resolve(); + } + + if (DEBUG) { + console.log('[SW]:', 'Caching additional'); + } + + var operation = undefined; + + if (strategy === 'changed') { + operation = cacheChanged('additional'); + } else { + operation = cacheAssets('additional'); + } + + // Ignore fail of `additional` cache section + return operation['catch'](function (e) { + console.error('[SW]:', 'Cache section `additional` failed to load'); + }); + } + + function cacheAssets(section) { + var batch = assets[section]; + + return caches.open(CACHE_NAME).then(function (cache) { + return addAllNormalized(cache, batch, { + bust: params.version, + request: prefetchRequest, + failAll: section === 'main' + }); + }).then(function () { + logGroup('Cached assets: ' + section, batch); + })['catch'](function (e) { + console.error(e); + throw e; + }); + } + + function cacheChanged(section) { + return getLastCache().then(function (args) { + if (!args) { + return cacheAssets(section); + } + + var lastCache = args[0]; + var lastKeys = args[1]; + var lastData = args[2]; + + var lastMap = lastData.hashmap; + var lastVersion = lastData.version; + + if (!lastData.hashmap || lastVersion === params.version) { + return cacheAssets(section); + } + + var lastHashedAssets = Object.keys(lastMap).map(function (hash) { + return lastMap[hash]; + }); + + var lastUrls = lastKeys.map(function (req) { + var url = new URL(req.url); + url.search = ''; + url.hash = ''; + + return url.toString(); + }); + + var sectionAssets = assets[section]; + var moved = []; + var changed = sectionAssets.filter(function (url) { + if (lastUrls.indexOf(url) === -1 || lastHashedAssets.indexOf(url) === -1) { + return true; + } + + return false; + }); + + Object.keys(hashesMap).forEach(function (hash) { + var asset = hashesMap[hash]; + + // Return if not in sectionAssets or in changed or moved array + if (sectionAssets.indexOf(asset) === -1 || changed.indexOf(asset) !== -1 || moved.indexOf(asset) !== -1) return; + + var lastAsset = lastMap[hash]; + + if (lastAsset && lastUrls.indexOf(lastAsset) !== -1) { + moved.push([lastAsset, asset]); + } else { + changed.push(asset); + } + }); + + logGroup('Changed assets: ' + section, changed); + logGroup('Moved assets: ' + section, moved); + + var movedResponses = Promise.all(moved.map(function (pair) { + return lastCache.match(pair[0]).then(function (response) { + return [pair[1], response]; + }); + })); + + return caches.open(CACHE_NAME).then(function (cache) { + var move = movedResponses.then(function (responses) { + return Promise.all(responses.map(function (pair) { + return cache.put(pair[0], pair[1]); + })); + }); + + return Promise.all([move, addAllNormalized(cache, changed, { + bust: params.version, + request: prefetchRequest, + failAll: section === 'main', + deleteFirst: section !== 'main' + })]); + }); + }); + } + + function deleteObsolete() { + return caches.keys().then(function (keys) { + var all = keys.map(function (key) { + if (key.indexOf(CACHE_PREFIX) !== 0 || key.indexOf(CACHE_NAME) === 0) return; + + console.log('[SW]:', 'Delete cache:', key); + return caches['delete'](key); + }); + + return Promise.all(all); + }); + } + + function getLastCache() { + return caches.keys().then(function (keys) { + var index = keys.length; + var key = undefined; + + while (index--) { + key = keys[index]; + + if (key.indexOf(CACHE_PREFIX) === 0) { + break; + } + } + + if (!key) return; + + var cache = undefined; + + return caches.open(key).then(function (_cache) { + cache = _cache; + return _cache.match(new URL(STORED_DATA_KEY, location).toString()); + }).then(function (response) { + if (!response) return; + + return Promise.all([cache, cache.keys(), response.json()]); + }); + }); + } + + function storeCacheData() { + return caches.open(CACHE_NAME).then(function (cache) { + var data = new Response(JSON.stringify({ + version: params.version, + hashmap: hashesMap + })); + + return cache.put(new URL(STORED_DATA_KEY, location).toString(), data); + }); + } + + self.addEventListener('fetch', function (event) { + // Handle only GET requests + if (event.request.method !== 'GET') { + return; + } + + // This prevents some weird issue with Chrome DevTools and 'only-if-cached' + // Fixes issue #385, also ref to: + // - https://github.com/paulirish/caltrainschedule.io/issues/49 + // - https://bugs.chromium.org/p/chromium/issues/detail?id=823392 + if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { + return; + } + + var url = new URL(event.request.url); + url.hash = ''; + + var urlString = url.toString(); + + // Not external, so search part of the URL should be stripped, + // if it's external URL, the search part should be kept + if (externals.indexOf(urlString) === -1) { + url.search = ''; + urlString = url.toString(); + } + + var assetMatches = allAssets.indexOf(urlString) !== -1; + var cacheUrl = urlString; + + if (!assetMatches) { + var cacheRewrite = matchCacheMap(event.request); + + if (cacheRewrite) { + cacheUrl = cacheRewrite; + assetMatches = true; + } + } + + if (!assetMatches) { + // Use request.mode === 'navigate' instead of isNavigateRequest + // because everything what supports navigationPreload supports + // 'navigate' request.mode + if (event.request.mode === 'navigate') { + // Requesting with fetchWithPreload(). + // Preload is used only if navigationPreload is enabled and + // navigationPreload mapping is not used. + if (navigationPreload === true) { + event.respondWith(fetchWithPreload(event)); + return; + } + } + + // Something else, positive, but not `true` + if (navigationPreload) { + var preloadedResponse = retrivePreloadedResponse(event); + + if (preloadedResponse) { + event.respondWith(preloadedResponse); + return; + } + } + + // Logic exists here if no cache match + return; + } + + // Cache handling/storing/fetching starts here + var resource = undefined; + + if (responseStrategy === 'network-first') { + resource = networkFirstResponse(event, urlString, cacheUrl); + } + // 'cache-first' otherwise + // (responseStrategy has been validated before) + else { + resource = cacheFirstResponse(event, urlString, cacheUrl); + } + + event.respondWith(resource); + }); + + self.addEventListener('message', function (e) { + var data = e.data; + if (!data) return; + + switch (data.action) { + case 'skipWaiting': + { + if (self.skipWaiting) self.skipWaiting(); + }break; + } + }); + + function cacheFirstResponse(event, urlString, cacheUrl) { + handleNavigationPreload(event); + + return cachesMatch(cacheUrl, CACHE_NAME).then(function (response) { + if (response) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + cacheUrl + '](' + urlString + ') from cache'); + } + + return response; + } + + // Load and cache known assets + var fetching = fetch(event.request).then(function (response) { + if (!response.ok) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] wrong response: [' + response.status + '] ' + response.type); + } + + return response; + } + + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] from network'); + } + + if (cacheUrl === urlString) { + (function () { + var responseClone = response.clone(); + var storing = caches.open(CACHE_NAME).then(function (cache) { + return cache.put(urlString, responseClone); + }).then(function () { + console.log('[SW]:', 'Cache asset: ' + urlString); + }); + + event.waitUntil(storing); + })(); + } + + return response; + }); + + return fetching; + }); + } + + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + + function networkFirstResponse(event, urlString, cacheUrl) { + return fetchWithPreload(event).then(function (response) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] from network'); + } + + return response; + } + + // Throw to reach the code in the catch below + throw response; + }) + // This needs to be in a catch() and not just in the then() above + // cause if your network is down, the fetch() will throw + ['catch'](function (erroredResponse) { + if (DEBUG) { + console.log('[SW]:', 'URL [' + urlString + '] from cache if possible'); + } + + return cachesMatch(cacheUrl, CACHE_NAME).then(function (response) { + if (response) { + return response; + } + + if (erroredResponse instanceof Response) { + return erroredResponse; + } + + // Not a response at this point, some other error + throw erroredResponse; + // return Response.error(); + }); + }); + } + + function handleNavigationPreload(event) { + if (navigationPreload && typeof navigationPreload.map === 'function' && + // Use request.mode === 'navigate' instead of isNavigateRequest + // because everything what supports navigationPreload supports + // 'navigate' request.mode + event.preloadResponse && event.request.mode === 'navigate') { + var mapped = navigationPreload.map(new URL(event.request.url), event.request); + + if (mapped) { + storePreloadedResponse(mapped, event); + } + } + } + + // Temporary in-memory store for faster access + var navigationPreloadStore = new Map(); + + function storePreloadedResponse(_url, event) { + var url = new URL(_url, location); + var preloadResponsePromise = event.preloadResponse; + + navigationPreloadStore.set(preloadResponsePromise, { + url: url, + response: preloadResponsePromise + }); + + var isSamePreload = function isSamePreload() { + return navigationPreloadStore.has(preloadResponsePromise); + }; + + var storing = preloadResponsePromise.then(function (res) { + // Return if preload isn't enabled or hasn't happened + if (!res) return; + + // If navigationPreloadStore already consumed + // or navigationPreloadStore already contains another preload, + // then do not store anything and return + if (!isSamePreload()) { + return; + } + + var clone = res.clone(); + + // Storing the preload response for later consume (hasn't yet been consumed) + return caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + if (!isSamePreload()) return; + + return cache.put(url, clone).then(function () { + if (!isSamePreload()) { + return caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + return cache['delete'](url); + }); + } + }); + }); + }); + + event.waitUntil(storing); + } + + function retriveInMemoryPreloadedResponse(url) { + if (!navigationPreloadStore) { + return; + } + + var foundResponse = undefined; + var foundKey = undefined; + + navigationPreloadStore.forEach(function (store, key) { + if (store.url.href === url.href) { + foundResponse = store.response; + foundKey = key; + } + }); + + if (foundResponse) { + navigationPreloadStore['delete'](foundKey); + return foundResponse; + } + } + + function retrivePreloadedResponse(event) { + var url = new URL(event.request.url); + + if (self.registration.navigationPreload && navigationPreload && navigationPreload.test && navigationPreload.test(url, event.request)) {} else { + return; + } + + var fromMemory = retriveInMemoryPreloadedResponse(url); + var request = event.request; + + if (fromMemory) { + event.waitUntil(caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + return cache['delete'](request); + })); + + return fromMemory; + } + + return cachesMatch(request, PRELOAD_CACHE_NAME).then(function (response) { + if (response) { + event.waitUntil(caches.open(PRELOAD_CACHE_NAME).then(function (cache) { + return cache['delete'](request); + })); + } + + return response || fetch(event.request); + }); + } + + function mapAssets() { + Object.keys(assets).forEach(function (key) { + assets[key] = assets[key].map(function (path) { + var url = new URL(path, location); + + url.hash = ''; + + if (externals.indexOf(path) === -1) { + url.search = ''; + } + + return url.toString(); + }); + }); + + hashesMap = Object.keys(hashesMap).reduce(function (result, hash) { + var url = new URL(hashesMap[hash], location); + url.search = ''; + url.hash = ''; + + result[hash] = url.toString(); + return result; + }, {}); + + externals = externals.map(function (path) { + var url = new URL(path, location); + url.hash = ''; + + return url.toString(); + }); + } + + function addAllNormalized(cache, requests, options) { + var bustValue = options.bust; + var failAll = options.failAll !== false; + var deleteFirst = options.deleteFirst === true; + var requestInit = options.request || { + credentials: 'omit', + mode: 'cors' + }; + + var deleting = Promise.resolve(); + + if (deleteFirst) { + deleting = Promise.all(requests.map(function (request) { + return cache['delete'](request)['catch'](function () {}); + })); + } + + return Promise.all(requests.map(function (request) { + if (bustValue) { + request = applyCacheBust(request, bustValue); + } + + return fetch(request, requestInit).then(fixRedirectedResponse).then(function (response) { + if (!response.ok) { + return { error: true }; + } + + return { response: response }; + }, function () { + return { error: true }; + }); + })).then(function (responses) { + if (failAll && responses.some(function (data) { + return data.error; + })) { + return Promise.reject(new Error('Wrong response status')); + } + + if (!failAll) { + responses = responses.filter(function (data) { + return !data.error; + }); + } + + return deleting.then(function () { + var addAll = responses.map(function (_ref, i) { + var response = _ref.response; + + return cache.put(requests[i], response); + }); + + return Promise.all(addAll); + }); + }); + } + + function matchCacheMap(request) { + var urlString = request.url; + var url = new URL(urlString); + + var requestType = undefined; + + if (isNavigateRequest(request)) { + requestType = 'navigate'; + } else if (url.origin === location.origin) { + requestType = 'same-origin'; + } else { + requestType = 'cross-origin'; + } + + for (var i = 0; i < cacheMaps.length; i++) { + var map = cacheMaps[i]; + + if (!map) continue; + if (map.requestTypes && map.requestTypes.indexOf(requestType) === -1) { + continue; + } + + var newString = undefined; + + if (typeof map.match === 'function') { + newString = map.match(url, request); + } else { + newString = urlString.replace(map.match, map.to); + } + + if (newString && newString !== urlString) { + return newString; + } + } + } + + function fetchWithPreload(event) { + if (!event.preloadResponse || navigationPreload !== true) { + return fetch(event.request); + } + + return event.preloadResponse.then(function (response) { + return response || fetch(event.request); + }); + } +} + +function cachesMatch(request, cacheName) { + return caches.match(request, { + cacheName: cacheName + }).then(function (response) { + if (isNotRedirectedResponse(response)) { + return response; + } + + // Fix already cached redirected responses + return fixRedirectedResponse(response).then(function (fixedResponse) { + return caches.open(cacheName).then(function (cache) { + return cache.put(request, fixedResponse); + }).then(function () { + return fixedResponse; + }); + }); + }) + // Return void if error happened (cache not found) + ['catch'](function () {}); +} + +function applyCacheBust(asset, key) { + var hasQuery = asset.indexOf('?') !== -1; + return asset + (hasQuery ? '&' : '?') + '__uncache=' + encodeURIComponent(key); +} + +function isNavigateRequest(request) { + return request.mode === 'navigate' || request.headers.get('Upgrade-Insecure-Requests') || (request.headers.get('Accept') || '').indexOf('text/html') !== -1; +} + +function isNotRedirectedResponse(response) { + return !response || !response.redirected || !response.ok || response.type === 'opaqueredirect'; +} + +// Based on https://github.com/GoogleChrome/sw-precache/pull/241/files#diff-3ee9060dc7a312c6a822cac63a8c630bR85 +function fixRedirectedResponse(response) { + if (isNotRedirectedResponse(response)) { + return Promise.resolve(response); + } + + var body = 'body' in response ? Promise.resolve(response.body) : response.blob(); + + return body.then(function (data) { + return new Response(data, { + headers: response.headers, + status: response.status + }); + }); +} + +function copyObject(original) { + return Object.keys(original).reduce(function (result, key) { + result[key] = original[key]; + return result; + }, {}); +} + +function logGroup(title, assets) { + console.groupCollapsed('[SW]:', title); + + assets.forEach(function (asset) { + console.log('Asset:', asset); + }); + + console.groupEnd(); +} + WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), +loaders: {}, +cacheMaps: [ + { + match: /^https:\/\/google\.com\/([\s\S]+)$/, + to: "https://facebook.com/$1", + requestTypes: null, + }, +{ + match: function (url) { + if (url.href.indexOf('https://google.com') === 0) { + return url.href.replace('https://google.com', 'https://facebook.com'); + } + }, + to: null, + requestTypes: null, + }, +{ + match: function (url) { + if (url.origin !== location.origin) return; + + if (url.pathname.indexOf('/api/') === 0) { + return; + } + + return new URL('/', location); + }, + to: null, + requestTypes: null, + } + ], +navigationPreload: false, +}); + module.exports = __webpack_require__(1) + + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/tests/legacy/fixtures/cachemaps-basic/__expected/webpack2/sw.js b/tests/legacy/fixtures/cachemaps-basic/__expected/webpack2/sw.js index 5cd4b883..30fd9525 100644 --- a/tests/legacy/fixtures/cachemaps-basic/__expected/webpack2/sw.js +++ b/tests/legacy/fixtures/cachemaps-basic/__expected/webpack2/sw.js @@ -518,9 +518,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -866,6 +873,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/cachemaps-basic/__expected/webpack3/sw.js b/tests/legacy/fixtures/cachemaps-basic/__expected/webpack3/sw.js index 45ed9392..ddc1f7cd 100644 --- a/tests/legacy/fixtures/cachemaps-basic/__expected/webpack3/sw.js +++ b/tests/legacy/fixtures/cachemaps-basic/__expected/webpack3/sw.js @@ -509,9 +509,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -857,6 +864,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/cachemaps-basic/__expected/webpack4/sw.js b/tests/legacy/fixtures/cachemaps-basic/__expected/webpack4/sw.js index d26b799d..0ebdcca1 100644 --- a/tests/legacy/fixtures/cachemaps-basic/__expected/webpack4/sw.js +++ b/tests/legacy/fixtures/cachemaps-basic/__expected/webpack4/sw.js @@ -530,9 +530,16 @@ function WebpackServiceWorker(params, helpers) { }); } + function shouldServeFromNetwork(response, urlString, cacheUrl) { + if (helpers.shouldServeFromNetwork) { + return helpers.shouldServeFromNetwork(response, urlString, cacheUrl); + } + return response.ok; + } + function networkFirstResponse(event, urlString, cacheUrl) { return fetchWithPreload(event).then(function (response) { - if (response.ok) { + if (shouldServeFromNetwork(response, urlString, cacheUrl)) { if (DEBUG) { console.log('[SW]:', 'URL [' + urlString + '] from network'); } @@ -878,6 +885,7 @@ function logGroup(title, assets) { console.groupEnd(); } WebpackServiceWorker(__wpo, { +shouldServeFromNetwork: (void 0), loaders: {}, cacheMaps: [ { diff --git a/tests/legacy/fixtures/network-first-response-should-serve-from-network/__expected/main.js b/tests/legacy/fixtures/network-first-response-should-serve-from-network/__expected/main.js new file mode 100644 index 00000000..53daa020 --- /dev/null +++ b/tests/legacy/fixtures/network-first-response-should-serve-from-network/__expected/main.js @@ -0,0 +1,50 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports) { + + + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/tests/legacy/fixtures/network-first-response-should-serve-from-network/__expected/sw.js b/tests/legacy/fixtures/network-first-response-should-serve-from-network/__expected/sw.js new file mode 100644 index 00000000..e4126eb4 --- /dev/null +++ b/tests/legacy/fixtures/network-first-response-should-serve-from-network/__expected/sw.js @@ -0,0 +1,24 @@ +var __wpo = { + "assets": { + "main": [ + "./external.js" + ], + "additional": [], + "optional": [] + }, + "externals": [ + "./external.js" + ], + "shouldServeFromNetwork": function (response, urlString, cacheUrl) { + if(urlString.match(/\//)) { + return true; + } + return response.ok; + }, + "hashesMap": {}, + "strategy": "changed", + "responseStrategy": "cache-first", + "version": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "name": "webpack-offline", + "relativePaths": true +}; \ No newline at end of file diff --git a/tests/legacy/fixtures/network-first-response-should-serve-from-network/main.js b/tests/legacy/fixtures/network-first-response-should-serve-from-network/main.js new file mode 100644 index 00000000..e69de29b diff --git a/tests/legacy/fixtures/network-first-response-should-serve-from-network/webpack.config.js b/tests/legacy/fixtures/network-first-response-should-serve-from-network/webpack.config.js new file mode 100644 index 00000000..89ac3c0a --- /dev/null +++ b/tests/legacy/fixtures/network-first-response-should-serve-from-network/webpack.config.js @@ -0,0 +1,17 @@ +module.exports = __CONFIG__({ + caches: { + main: ['external.js', ':rest:'] + }, + externals: ['external.js'], + excludes: ['main.js'], + version: '[hash]', + AppCache: false, + ServiceWorker: { + shouldServeFromNetwork: function(response, urlString, cacheUrl) { + if(urlString.match(/\//)) { + return true; + } + return response.ok; + } + } +}); \ No newline at end of file diff --git a/tests/legacy/fixtures/sw-minify-auto/__expected/webpack2/sw.js b/tests/legacy/fixtures/sw-minify-auto/__expected/webpack2/sw.js index 369c0e4d..747579b0 100644 --- a/tests/legacy/fixtures/sw-minify-auto/__expected/webpack2/sw.js +++ b/tests/legacy/fixtures/sw-minify-auto/__expected/webpack2/sw.js @@ -1,3 +1,3 @@ var __wpo = {"assets":{"main":["./external.js"],"additional":[],"optional":[]},"externals":["./external.js"],"hashesMap":{},"strategy":"changed","responseStrategy":"cache-first","version":"da39a3ee5e6b4b0d3255bfef95601890afd80709","name":"webpack-offline","relativePaths":true}; -!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var t={};n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=1)}([function(e,n){},function(e,n,t){"use strict";function r(e,n){return caches.match(e,{cacheName:n}).then(function(t){return a(t)?t:c(t).then(function(t){return caches.open(n).then(function(n){return n.put(e,t)}).then(function(){return t})})}).catch(function(){})}function o(e,n){return e+(-1!==e.indexOf("?")?"&":"?")+"__uncache="+encodeURIComponent(n)}function i(e){return"navigate"===e.mode||e.headers.get("Upgrade-Insecure-Requests")||-1!==(e.headers.get("Accept")||"").indexOf("text/html")}function a(e){return!e||!e.redirected||!e.ok||"opaqueredirect"===e.type}function c(e){return a(e)?Promise.resolve(e):("body"in e?Promise.resolve(e.body):e.blob()).then(function(n){return new Response(n,{headers:e.headers,status:e.status})})}function s(e,n){console.groupCollapsed("[SW]:",e),n.forEach(function(e){console.log("Asset:",e)}),console.groupEnd()}if(function(){var e=ExtendableEvent.prototype.waitUntil,n=FetchEvent.prototype.respondWith,t=new WeakMap;ExtendableEvent.prototype.waitUntil=function(n){var r=this,o=t.get(r);return o?void o.push(Promise.resolve(n)):(o=[Promise.resolve(n)],t.set(r,o),e.call(r,Promise.resolve().then(function e(){var n=o.length;return Promise.all(o.map(function(e){return e.catch(function(){})})).then(function(){return o.length!=n?e():(t.delete(r),Promise.all(o))})})))},FetchEvent.prototype.respondWith=function(e){return this.waitUntil(e),n.call(this,e)}}(),void 0===u)var u=!1;!function(e,n){function t(){if(!W.additional.length)return Promise.resolve();u&&console.log("[SW]:","Caching additional");var e=void 0;return e="changed"===O?f("additional"):a("additional"),e.catch(function(e){console.error("[SW]:","Cache section `additional` failed to load")})}function a(n){var t=W[n];return caches.open(j).then(function(r){return y(r,t,{bust:e.version,request:k,failAll:"main"===n})}).then(function(){s("Cached assets: "+n,t)}).catch(function(e){throw console.error(e),e})}function f(n){return h().then(function(t){if(!t)return a(n);var r=t[0],o=t[1],i=t[2],c=i.hashmap,u=i.version;if(!i.hashmap||u===e.version)return a(n);var f=Object.keys(c).map(function(e){return c[e]}),l=o.map(function(e){var n=new URL(e.url);return n.search="",n.hash="",n.toString()}),h=W[n],d=[],p=h.filter(function(e){return-1===l.indexOf(e)||-1===f.indexOf(e)});Object.keys(b).forEach(function(e){var n=b[e];if(-1!==h.indexOf(n)&&-1===p.indexOf(n)&&-1===d.indexOf(n)){var t=c[e];t&&-1!==l.indexOf(t)?d.push([t,n]):p.push(n)}}),s("Changed assets: "+n,p),s("Moved assets: "+n,d);var v=Promise.all(d.map(function(e){return r.match(e[0]).then(function(n){return[e[1],n]})}));return caches.open(j).then(function(t){var r=v.then(function(e){return Promise.all(e.map(function(e){return t.put(e[0],e[1])}))});return Promise.all([r,y(t,p,{bust:e.version,request:k,failAll:"main"===n,deleteFirst:"main"!==n})])})})}function l(){return caches.keys().then(function(e){var n=e.map(function(e){if(0===e.indexOf(E)&&0!==e.indexOf(j))return console.log("[SW]:","Delete cache:",e),caches.delete(e)});return Promise.all(n)})}function h(){return caches.keys().then(function(e){for(var n=e.length,t=void 0;n--&&(t=e[n],0!==t.indexOf(E)););if(t){var r=void 0;return caches.open(t).then(function(e){return r=e,e.match(new URL(M,location).toString())}).then(function(e){if(e)return Promise.all([r,r.keys(),e.json()])})}})}function d(){return caches.open(j).then(function(n){var t=new Response(JSON.stringify({version:e.version,hashmap:b}));return n.put(new URL(M,location).toString(),t)})}function p(e,n,t){return m(e),r(t,j).then(function(r){return r?(u&&console.log("[SW]:","URL ["+t+"]("+n+") from cache"),r):fetch(e.request).then(function(r){return r.ok?(u&&console.log("[SW]:","URL ["+n+"] from network"),t===n&&function(){var t=r.clone(),o=caches.open(j).then(function(e){return e.put(n,t)}).then(function(){console.log("[SW]:","Cache asset: "+n)});e.waitUntil(o)}(),r):(u&&console.log("[SW]:","URL ["+n+"] wrong response: ["+r.status+"] "+r.type),r)})})}function v(e,n,t){return R(e).then(function(e){if(e.ok)return u&&console.log("[SW]:","URL ["+n+"] from network"),e;throw e}).catch(function(e){return u&&console.log("[SW]:","URL ["+n+"] from cache if possible"),r(t,j).then(function(n){if(n)return n;if(e instanceof Response)return e;throw e})})}function m(e){if(q&&"function"==typeof q.map&&e.preloadResponse&&"navigate"===e.request.mode){var n=q.map(new URL(e.request.url),e.request);n&&g(n,e)}}function g(e,n){var t=new URL(e,location),r=n.preloadResponse;F.set(r,{url:t,response:r});var o=function(){return F.has(r)},i=r.then(function(e){if(e&&o()){var n=e.clone();return caches.open(C).then(function(e){if(o())return e.put(t,n).then(function(){if(!o())return caches.open(C).then(function(e){return e.delete(t)})})})}});n.waitUntil(i)}function w(e){if(F){var n=void 0,t=void 0;return F.forEach(function(r,o){r.url.href===e.href&&(n=r.response,t=o)}),n?(F.delete(t),n):void 0}}function U(e){var n=new URL(e.request.url);if(self.registration.navigationPreload&&q&&q.test&&q.test(n,e.request)){var t=w(n),o=e.request;return t?(e.waitUntil(caches.open(C).then(function(e){return e.delete(o)})),t):r(o,C).then(function(n){return n&&e.waitUntil(caches.open(C).then(function(e){return e.delete(o)})),n||fetch(e.request)})}}function y(e,n,t){var r=t.bust,i=!1!==t.failAll,a=!0===t.deleteFirst,s=t.request||{credentials:"omit",mode:"cors"},u=Promise.resolve();return a&&(u=Promise.all(n.map(function(n){return e.delete(n).catch(function(){})}))),Promise.all(n.map(function(e){return r&&(e=o(e,r)),fetch(e,s).then(c).then(function(e){return e.ok?{response:e}:{error:!0}},function(){return{error:!0}})})).then(function(t){return i&&t.some(function(e){return e.error})?Promise.reject(new Error("Wrong response status")):(i||(t=t.filter(function(e){return!e.error})),u.then(function(){var r=t.map(function(t,r){var o=t.response;return e.put(n[r],o)});return Promise.all(r)}))})}function P(e){var n=e.url,t=new URL(n),r=void 0;r=i(e)?"navigate":t.origin===location.origin?"same-origin":"cross-origin";for(var o=0;o