UNPKG

4.88 kBJavaScriptView Raw
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
9const ol = require('common-tags').oneLine;
10
11const errors = require('./errors');
12const stringifyWithoutComments = require('./stringify-without-comments');
13
14/**
15 * Given a set of options that configures `sw-toolbox`'s behavior, convert it
16 * into a string that would configure equivalent `workbox-sw` behavior.
17 *
18 * @param {Object} options See
19 * https://googlechromelabs.github.io/sw-toolbox/api.html#options
20 * @return {string} A JSON string representing the equivalent options.
21 *
22 * @private
23 */
24function getOptionsString(options = {}) {
25 let plugins = [];
26 if (options.plugins) {
27 // Using libs because JSON.stringify won't handle functions.
28 plugins = options.plugins.map(stringifyWithoutComments);
29 delete options.plugins;
30 }
31
32 // Pull handler-specific config from the options object, since they are
33 // not directly used to construct a Plugin instance. If set, need to be
34 // passed as options to the handler constructor instead.
35 const handlerOptionKeys = [
36 'cacheName',
37 'networkTimeoutSeconds',
38 'fetchOptions',
39 'matchOptions',
40 ];
41 const handlerOptions = {};
42 for (const key of handlerOptionKeys) {
43 if (key in options) {
44 handlerOptions[key] = options[key];
45 delete options[key];
46 }
47 }
48
49 const pluginsMapping = {
50 backgroundSync: 'workbox.backgroundSync.Plugin',
51 broadcastUpdate: 'workbox.broadcastUpdate.Plugin',
52 expiration: 'workbox.expiration.Plugin',
53 cacheableResponse: 'workbox.cacheableResponse.Plugin',
54 };
55
56 for (const [pluginName, pluginConfig] of Object.entries(options)) {
57 // Ensure that we have some valid configuration to pass to Plugin().
58 if (Object.keys(pluginConfig).length === 0) {
59 continue;
60 }
61
62 const pluginString = pluginsMapping[pluginName];
63 if (!pluginString) {
64 throw new Error(`${errors['bad-runtime-caching-config']} ${pluginName}`);
65 }
66
67 let pluginCode;
68 switch (pluginName) {
69 // Special case logic for plugins that have a required parameter, and then
70 // an additional optional config parameter.
71 case 'backgroundSync': {
72 const name = pluginConfig.name;
73 pluginCode = `new ${pluginString}(${JSON.stringify(name)}`;
74 if ('options' in pluginConfig) {
75 pluginCode += `, ${stringifyWithoutComments(pluginConfig.options)}`;
76 }
77 pluginCode += `)`;
78
79 break;
80 }
81
82 case 'broadcastUpdate': {
83 const channelName = pluginConfig.channelName;
84 pluginCode = `new ${pluginString}(${JSON.stringify(channelName)}`;
85 if ('options' in pluginConfig) {
86 pluginCode += `, ${stringifyWithoutComments(pluginConfig.options)}`;
87 }
88 pluginCode += `)`;
89
90 break;
91 }
92
93 // For plugins that just pass in an Object to the constructor, like
94 // expiration and cacheableResponse
95 default: {
96 pluginCode = `new ${pluginString}(${stringifyWithoutComments(
97 pluginConfig
98 )})`;
99 }
100 }
101
102 plugins.push(pluginCode);
103 }
104
105 if (Object.keys(handlerOptions).length > 0 || plugins.length > 0) {
106 const optionsString = JSON.stringify(handlerOptions).slice(1, -1);
107 return ol`{
108 ${optionsString ? optionsString + ',' : ''}
109 plugins: [${plugins.join(', ')}]
110 }`;
111 } else {
112 return '';
113 }
114}
115
116module.exports = (runtimeCaching = []) => {
117 return runtimeCaching.map((entry) => {
118 const method = entry.method || 'GET';
119
120 if (!entry.urlPattern) {
121 throw new Error(errors['urlPattern-is-required']);
122 }
123
124 if (!entry.handler) {
125 throw new Error(errors['handler-is-required']);
126 }
127
128 // This validation logic is a bit too gnarly for joi, so it's manually
129 // implemented here.
130 if (entry.options && entry.options.networkTimeoutSeconds &&
131 entry.handler !== 'NetworkFirst') {
132 throw new Error(errors['invalid-network-timeout-seconds']);
133 }
134
135 // urlPattern might be a string, a RegExp object, or a function.
136 // If it's a string, it needs to be quoted.
137 const matcher = typeof entry.urlPattern === 'string' ?
138 JSON.stringify(entry.urlPattern) :
139 entry.urlPattern;
140
141 if (typeof entry.handler === 'string') {
142 const optionsString = getOptionsString(entry.options || {});
143 const strategyString =
144 `new workbox.strategies.${entry.handler}(${optionsString})`;
145
146 return `workbox.routing.registerRoute(` +
147 `${matcher}, ${strategyString}, '${method}');\n`;
148 } else if (typeof entry.handler === 'function') {
149 return `workbox.routing.registerRoute(` +
150 `${matcher}, ${entry.handler}, '${method}');\n`;
151 }
152 }).filter((entry) => Boolean(entry)); // Remove undefined map() return values.
153};