| 1 | /*
|
|---|
| 2 | Copyright 2018 Google LLC
|
|---|
| 3 |
|
|---|
| 4 | Use of this source code is governed by an MIT-style
|
|---|
| 5 | license that can be found in the LICENSE file or at
|
|---|
| 6 | https://opensource.org/licenses/MIT.
|
|---|
| 7 | */
|
|---|
| 8 |
|
|---|
| 9 | import {oneLine as ol} from 'common-tags';
|
|---|
| 10 |
|
|---|
| 11 | import {errors} from './errors';
|
|---|
| 12 | import {ModuleRegistry} from './module-registry';
|
|---|
| 13 | import {RuntimeCaching} from '../types';
|
|---|
| 14 | import {stringifyWithoutComments} from './stringify-without-comments';
|
|---|
| 15 |
|
|---|
| 16 | /**
|
|---|
| 17 | * Given a set of options that configures runtime caching behavior, convert it
|
|---|
| 18 | * to the equivalent Workbox method calls.
|
|---|
| 19 | *
|
|---|
| 20 | * @param {ModuleRegistry} moduleRegistry
|
|---|
| 21 | * @param {Object} options See
|
|---|
| 22 | * https://developers.google.com/web/tools/workbox/modules/workbox-build#generateSW-runtimeCaching
|
|---|
| 23 | * @return {string} A JSON string representing the equivalent options.
|
|---|
| 24 | *
|
|---|
| 25 | * @private
|
|---|
| 26 | */
|
|---|
| 27 | function getOptionsString(
|
|---|
| 28 | moduleRegistry: ModuleRegistry,
|
|---|
| 29 | options: RuntimeCaching['options'] = {},
|
|---|
| 30 | ) {
|
|---|
| 31 | const plugins: Array<string> = [];
|
|---|
| 32 | const handlerOptions: {[key in keyof typeof options]: any} = {};
|
|---|
| 33 |
|
|---|
| 34 | for (const optionName of Object.keys(options) as Array<
|
|---|
| 35 | keyof typeof options
|
|---|
| 36 | >) {
|
|---|
| 37 | if (options[optionName] === undefined) {
|
|---|
| 38 | continue;
|
|---|
| 39 | }
|
|---|
| 40 |
|
|---|
| 41 | switch (optionName) {
|
|---|
| 42 | // Using a library here because JSON.stringify won't handle functions.
|
|---|
| 43 | case 'plugins': {
|
|---|
| 44 | plugins.push(...options.plugins!.map(stringifyWithoutComments));
|
|---|
| 45 | break;
|
|---|
| 46 | }
|
|---|
| 47 |
|
|---|
| 48 | // These are the option properties that we want to pull out, so that
|
|---|
| 49 | // they're passed to the handler constructor.
|
|---|
| 50 | case 'cacheName':
|
|---|
| 51 | case 'networkTimeoutSeconds':
|
|---|
| 52 | case 'fetchOptions':
|
|---|
| 53 | case 'matchOptions': {
|
|---|
| 54 | handlerOptions[optionName] = options[optionName];
|
|---|
| 55 | break;
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | // The following cases are all shorthands for creating a plugin with a
|
|---|
| 59 | // given configuration.
|
|---|
| 60 | case 'backgroundSync': {
|
|---|
| 61 | const name = options.backgroundSync!.name;
|
|---|
| 62 | const plugin = moduleRegistry.use(
|
|---|
| 63 | 'workbox-background-sync',
|
|---|
| 64 | 'BackgroundSyncPlugin',
|
|---|
| 65 | );
|
|---|
| 66 |
|
|---|
| 67 | let pluginCode = `new ${plugin}(${JSON.stringify(name)}`;
|
|---|
| 68 | if (options.backgroundSync!.options) {
|
|---|
| 69 | pluginCode += `, ${stringifyWithoutComments(
|
|---|
| 70 | options.backgroundSync!.options,
|
|---|
| 71 | )}`;
|
|---|
| 72 | }
|
|---|
| 73 | pluginCode += `)`;
|
|---|
| 74 |
|
|---|
| 75 | plugins.push(pluginCode);
|
|---|
| 76 | break;
|
|---|
| 77 | }
|
|---|
| 78 |
|
|---|
| 79 | case 'broadcastUpdate': {
|
|---|
| 80 | const channelName = options.broadcastUpdate!.channelName;
|
|---|
| 81 | const opts = Object.assign(
|
|---|
| 82 | {channelName},
|
|---|
| 83 | options.broadcastUpdate!.options,
|
|---|
| 84 | );
|
|---|
| 85 | const plugin = moduleRegistry.use(
|
|---|
| 86 | 'workbox-broadcast-update',
|
|---|
| 87 | 'BroadcastUpdatePlugin',
|
|---|
| 88 | );
|
|---|
| 89 |
|
|---|
| 90 | plugins.push(`new ${plugin}(${stringifyWithoutComments(opts)})`);
|
|---|
| 91 | break;
|
|---|
| 92 | }
|
|---|
| 93 |
|
|---|
| 94 | case 'cacheableResponse': {
|
|---|
| 95 | const plugin = moduleRegistry.use(
|
|---|
| 96 | 'workbox-cacheable-response',
|
|---|
| 97 | 'CacheableResponsePlugin',
|
|---|
| 98 | );
|
|---|
| 99 |
|
|---|
| 100 | plugins.push(
|
|---|
| 101 | `new ${plugin}(${stringifyWithoutComments(
|
|---|
| 102 | options.cacheableResponse!,
|
|---|
| 103 | )})`,
|
|---|
| 104 | );
|
|---|
| 105 | break;
|
|---|
| 106 | }
|
|---|
| 107 |
|
|---|
| 108 | case 'expiration': {
|
|---|
| 109 | const plugin = moduleRegistry.use(
|
|---|
| 110 | 'workbox-expiration',
|
|---|
| 111 | 'ExpirationPlugin',
|
|---|
| 112 | );
|
|---|
| 113 |
|
|---|
| 114 | plugins.push(
|
|---|
| 115 | `new ${plugin}(${stringifyWithoutComments(options.expiration!)})`,
|
|---|
| 116 | );
|
|---|
| 117 | break;
|
|---|
| 118 | }
|
|---|
| 119 |
|
|---|
| 120 | case 'precacheFallback': {
|
|---|
| 121 | const plugin = moduleRegistry.use(
|
|---|
| 122 | 'workbox-precaching',
|
|---|
| 123 | 'PrecacheFallbackPlugin',
|
|---|
| 124 | );
|
|---|
| 125 |
|
|---|
| 126 | plugins.push(
|
|---|
| 127 | `new ${plugin}(${stringifyWithoutComments(
|
|---|
| 128 | options.precacheFallback!,
|
|---|
| 129 | )})`,
|
|---|
| 130 | );
|
|---|
| 131 | break;
|
|---|
| 132 | }
|
|---|
| 133 |
|
|---|
| 134 | case 'rangeRequests': {
|
|---|
| 135 | const plugin = moduleRegistry.use(
|
|---|
| 136 | 'workbox-range-requests',
|
|---|
| 137 | 'RangeRequestsPlugin',
|
|---|
| 138 | );
|
|---|
| 139 |
|
|---|
| 140 | // There are no configuration options for the constructor.
|
|---|
| 141 | plugins.push(`new ${plugin}()`);
|
|---|
| 142 | break;
|
|---|
| 143 | }
|
|---|
| 144 |
|
|---|
| 145 | default: {
|
|---|
| 146 | throw new Error(
|
|---|
| 147 | // In the default case optionName is typed as 'never'.
|
|---|
| 148 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|---|
| 149 | `${errors['bad-runtime-caching-config']} ${optionName}`,
|
|---|
| 150 | );
|
|---|
| 151 | }
|
|---|
| 152 | }
|
|---|
| 153 | }
|
|---|
| 154 |
|
|---|
| 155 | if (Object.keys(handlerOptions).length > 0 || plugins.length > 0) {
|
|---|
| 156 | const optionsString = JSON.stringify(handlerOptions).slice(1, -1);
|
|---|
| 157 | return ol`{
|
|---|
| 158 | ${optionsString ? optionsString + ',' : ''}
|
|---|
| 159 | plugins: [${plugins.join(', ')}]
|
|---|
| 160 | }`;
|
|---|
| 161 | } else {
|
|---|
| 162 | return '';
|
|---|
| 163 | }
|
|---|
| 164 | }
|
|---|
| 165 |
|
|---|
| 166 | export function runtimeCachingConverter(
|
|---|
| 167 | moduleRegistry: ModuleRegistry,
|
|---|
| 168 | runtimeCaching: Array<RuntimeCaching>,
|
|---|
| 169 | ): Array<string> {
|
|---|
| 170 | return runtimeCaching
|
|---|
| 171 | .map((entry) => {
|
|---|
| 172 | const method = entry.method || 'GET';
|
|---|
| 173 |
|
|---|
| 174 | if (!entry.urlPattern) {
|
|---|
| 175 | throw new Error(errors['urlPattern-is-required']);
|
|---|
| 176 | }
|
|---|
| 177 |
|
|---|
| 178 | if (!entry.handler) {
|
|---|
| 179 | throw new Error(errors['handler-is-required']);
|
|---|
| 180 | }
|
|---|
| 181 |
|
|---|
| 182 | if (
|
|---|
| 183 | entry.options &&
|
|---|
| 184 | entry.options.networkTimeoutSeconds &&
|
|---|
| 185 | entry.handler !== 'NetworkFirst'
|
|---|
| 186 | ) {
|
|---|
| 187 | throw new Error(errors['invalid-network-timeout-seconds']);
|
|---|
| 188 | }
|
|---|
| 189 |
|
|---|
| 190 | // urlPattern might be a string, a RegExp object, or a function.
|
|---|
| 191 | // If it's a string, it needs to be quoted.
|
|---|
| 192 | const matcher =
|
|---|
| 193 | typeof entry.urlPattern === 'string'
|
|---|
| 194 | ? JSON.stringify(entry.urlPattern)
|
|---|
| 195 | : entry.urlPattern;
|
|---|
| 196 |
|
|---|
| 197 | const registerRoute = moduleRegistry.use(
|
|---|
| 198 | 'workbox-routing',
|
|---|
| 199 | 'registerRoute',
|
|---|
| 200 | );
|
|---|
| 201 | if (typeof entry.handler === 'string') {
|
|---|
| 202 | const optionsString = getOptionsString(moduleRegistry, entry.options);
|
|---|
| 203 | const handler = moduleRegistry.use('workbox-strategies', entry.handler);
|
|---|
| 204 | const strategyString = `new ${handler}(${optionsString})`;
|
|---|
| 205 |
|
|---|
| 206 | return `${registerRoute}(${matcher.toString()}, ${strategyString}, '${method}');\n`;
|
|---|
| 207 | } else if (typeof entry.handler === 'function') {
|
|---|
| 208 | return `${registerRoute}(${matcher.toString()}, ${entry.handler.toString()}, '${method}');\n`;
|
|---|
| 209 | }
|
|---|
| 210 |
|
|---|
| 211 | // '' will be filtered out.
|
|---|
| 212 | return '';
|
|---|
| 213 | })
|
|---|
| 214 | .filter((entry) => Boolean(entry));
|
|---|
| 215 | }
|
|---|