source: frontend/node_modules/workbox-webpack-plugin/src/inject-manifest.ts

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 12.1 KB
Line 
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
9import {escapeRegExp} from 'workbox-build/build/lib/escape-regexp';
10import {replaceAndUpdateSourceMap} from 'workbox-build/build/lib/replace-and-update-source-map';
11import {validateWebpackInjectManifestOptions} from 'workbox-build/build/lib/validate-options';
12import prettyBytes from 'pretty-bytes';
13import stringify from 'fast-json-stable-stringify';
14import upath from 'upath';
15import webpack from 'webpack';
16
17import {getManifestEntriesFromCompilation} from './lib/get-manifest-entries-from-compilation';
18import {getSourcemapAssetName} from './lib/get-sourcemap-asset-name';
19import {relativeToOutputPath} from './lib/relative-to-output-path';
20import {WebpackInjectManifestOptions} from 'workbox-build';
21// Used to keep track of swDest files written by *any* instance of this plugin.
22// See https://github.com/GoogleChrome/workbox/issues/2181
23const _generatedAssetNames = new Set<string>();
24
25// SingleEntryPlugin in v4 was renamed to EntryPlugin in v5.
26const SingleEntryPlugin = webpack.EntryPlugin || webpack.SingleEntryPlugin;
27
28// webpack v4/v5 compatibility:
29// https://github.com/webpack/webpack/issues/11425#issuecomment-686607633
30const {RawSource} = webpack.sources || require('webpack-sources');
31
32/**
33 * This class supports compiling a service worker file provided via `swSrc`,
34 * and injecting into that service worker a list of URLs and revision
35 * information for precaching based on the webpack asset pipeline.
36 *
37 * Use an instance of `InjectManifest` in the
38 * [`plugins` array](https://webpack.js.org/concepts/plugins/#usage) of a
39 * webpack config.
40 *
41 * In addition to injecting the manifest, this plugin will perform a compilation
42 * of the `swSrc` file, using the options from the main webpack configuration.
43 *
44 * ```
45 * // The following lists some common options; see the rest of the documentation
46 * // for the full set of options and defaults.
47 * new InjectManifest({
48 * exclude: [/.../, '...'],
49 * maximumFileSizeToCacheInBytes: ...,
50 * swSrc: '...',
51 * });
52 * ```
53 *
54 * @memberof module:workbox-webpack-plugin
55 */
56class InjectManifest {
57 protected config: WebpackInjectManifestOptions;
58 private alreadyCalled: boolean;
59
60 /**
61 * Creates an instance of InjectManifest.
62 */
63 constructor(config: WebpackInjectManifestOptions) {
64 this.config = config;
65 this.alreadyCalled = false;
66 }
67
68 /**
69 * @param {Object} [compiler] default compiler object passed from webpack
70 *
71 * @private
72 */
73 propagateWebpackConfig(compiler: webpack.Compiler): void {
74 // Because this.config is listed last, properties that are already set
75 // there take precedence over derived properties from the compiler.
76 this.config = Object.assign(
77 {
78 mode: compiler.options.mode,
79 // Use swSrc with a hardcoded .js extension, in case swSrc is a .ts file.
80 swDest: upath.parse(this.config.swSrc).name + '.js',
81 },
82 this.config,
83 );
84 }
85
86 /**
87 * @param {Object} [compiler] default compiler object passed from webpack
88 *
89 * @private
90 */
91 apply(compiler: webpack.Compiler): void {
92 this.propagateWebpackConfig(compiler);
93
94 compiler.hooks.make.tapPromise(this.constructor.name, (compilation) =>
95 this.handleMake(compilation, compiler).catch(
96 (error: webpack.WebpackError) => {
97 compilation.errors.push(error);
98 },
99 ),
100 );
101
102 // webpack v4/v5 compatibility:
103 // https://github.com/webpack/webpack/issues/11425#issuecomment-690387207
104 if (webpack.version?.startsWith('4.')) {
105 compiler.hooks.emit.tapPromise(this.constructor.name, (compilation) =>
106 this.addAssets(compilation).catch((error: webpack.WebpackError) => {
107 compilation.errors.push(error);
108 }),
109 );
110 } else {
111 const {PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER} = webpack.Compilation;
112 // Specifically hook into thisCompilation, as per
113 // https://github.com/webpack/webpack/issues/11425#issuecomment-690547848
114 compiler.hooks.thisCompilation.tap(
115 this.constructor.name,
116 (compilation) => {
117 compilation.hooks.processAssets.tapPromise(
118 {
119 name: this.constructor.name,
120 // TODO(jeffposnick): This may need to change eventually.
121 // See https://github.com/webpack/webpack/issues/11822#issuecomment-726184972
122 stage: PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 10,
123 },
124 () =>
125 this.addAssets(compilation).catch(
126 (error: webpack.WebpackError) => {
127 compilation.errors.push(error);
128 },
129 ),
130 );
131 },
132 );
133 }
134 }
135
136 /**
137 * @param {Object} compilation The webpack compilation.
138 * @param {Object} parentCompiler The webpack parent compiler.
139 *
140 * @private
141 */
142 async performChildCompilation(
143 compilation: webpack.Compilation,
144 parentCompiler: webpack.Compiler,
145 ): Promise<void> {
146 const outputOptions = {
147 path: parentCompiler.options.output.path,
148 filename: this.config.swDest,
149 };
150
151 const childCompiler = compilation.createChildCompiler(
152 this.constructor.name,
153 outputOptions,
154 [],
155 );
156
157 childCompiler.context = parentCompiler.context;
158 childCompiler.inputFileSystem = parentCompiler.inputFileSystem;
159 childCompiler.outputFileSystem = parentCompiler.outputFileSystem;
160
161 if (Array.isArray(this.config.webpackCompilationPlugins)) {
162 for (const plugin of this.config.webpackCompilationPlugins) {
163 // plugin has a generic type, eslint complains for an unsafe
164 // assign and unsafe use
165 // eslint-disable-next-line
166 plugin.apply(childCompiler);
167 }
168 }
169
170 new SingleEntryPlugin(
171 parentCompiler.context,
172 this.config.swSrc,
173 this.constructor.name,
174 ).apply(childCompiler);
175
176 await new Promise<void>((resolve, reject) => {
177 childCompiler.runAsChild((error, _entries, childCompilation) => {
178 if (error) {
179 reject(error);
180 } else {
181 compilation.warnings = compilation.warnings.concat(
182 childCompilation?.warnings ?? [],
183 );
184 compilation.errors = compilation.errors.concat(
185 childCompilation?.errors ?? [],
186 );
187
188 resolve();
189 }
190 });
191 });
192 }
193
194 /**
195 * @param {Object} compilation The webpack compilation.
196 * @param {Object} parentCompiler The webpack parent compiler.
197 *
198 * @private
199 */
200 addSrcToAssets(
201 compilation: webpack.Compilation,
202 parentCompiler: webpack.Compiler,
203 ): void {
204 // eslint-disable-next-line
205 const source = (parentCompiler.inputFileSystem as any).readFileSync(
206 this.config.swSrc,
207 );
208 compilation.emitAsset(this.config.swDest!, new RawSource(source));
209 }
210
211 /**
212 * @param {Object} compilation The webpack compilation.
213 * @param {Object} parentCompiler The webpack parent compiler.
214 *
215 * @private
216 */
217 async handleMake(
218 compilation: webpack.Compilation,
219 parentCompiler: webpack.Compiler,
220 ): Promise<void> {
221 try {
222 this.config = validateWebpackInjectManifestOptions(this.config);
223 } catch (error) {
224 if (error instanceof Error) {
225 throw new Error(
226 `Please check your ${this.constructor.name} plugin ` +
227 `configuration:\n${error.message}`,
228 );
229 }
230 }
231
232 this.config.swDest = relativeToOutputPath(compilation, this.config.swDest!);
233 _generatedAssetNames.add(this.config.swDest);
234
235 if (this.config.compileSrc) {
236 await this.performChildCompilation(compilation, parentCompiler);
237 } else {
238 this.addSrcToAssets(compilation, parentCompiler);
239 // This used to be a fatal error, but just warn at runtime because we
240 // can't validate it easily.
241 if (
242 Array.isArray(this.config.webpackCompilationPlugins) &&
243 this.config.webpackCompilationPlugins.length > 0
244 ) {
245 compilation.warnings.push(
246 new Error(
247 'compileSrc is false, so the ' +
248 'webpackCompilationPlugins option will be ignored.',
249 ) as webpack.WebpackError,
250 );
251 }
252 }
253 }
254
255 /**
256 * @param {Object} compilation The webpack compilation.
257 *
258 * @private
259 */
260 async addAssets(compilation: webpack.Compilation): Promise<void> {
261 // See https://github.com/GoogleChrome/workbox/issues/1790
262 if (this.alreadyCalled) {
263 const warningMessage =
264 `${this.constructor.name} has been called ` +
265 `multiple times, perhaps due to running webpack in --watch mode. The ` +
266 `precache manifest generated after the first call may be inaccurate! ` +
267 `Please see https://github.com/GoogleChrome/workbox/issues/1790 for ` +
268 `more information.`;
269
270 if (
271 !compilation.warnings.some(
272 (warning) =>
273 warning instanceof Error && warning.message === warningMessage,
274 )
275 ) {
276 compilation.warnings.push(
277 new Error(warningMessage) as webpack.WebpackError,
278 );
279 }
280 } else {
281 this.alreadyCalled = true;
282 }
283
284 const config = Object.assign({}, this.config);
285
286 // Ensure that we don't precache any of the assets generated by *any*
287 // instance of this plugin.
288 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
289 config.exclude!.push(({asset}) => _generatedAssetNames.has(asset.name));
290
291 // See https://webpack.js.org/contribute/plugin-patterns/#monitoring-the-watch-graph
292 const absoluteSwSrc = upath.resolve(this.config.swSrc);
293 compilation.fileDependencies.add(absoluteSwSrc);
294
295 const swAsset = compilation.getAsset(config.swDest!);
296 const swAssetString = swAsset!.source.source().toString();
297
298 const globalRegexp = new RegExp(escapeRegExp(config.injectionPoint!), 'g');
299 const injectionResults = swAssetString.match(globalRegexp);
300
301 if (!injectionResults) {
302 throw new Error(
303 `Can't find ${config.injectionPoint ?? ''} in your SW source.`,
304 );
305 }
306 if (injectionResults.length !== 1) {
307 throw new Error(
308 `Multiple instances of ${config.injectionPoint ?? ''} were ` +
309 `found in your SW source. Include it only once. For more info, see ` +
310 `https://github.com/GoogleChrome/workbox/issues/2681`,
311 );
312 }
313
314 const {size, sortedEntries} = await getManifestEntriesFromCompilation(
315 compilation,
316 config,
317 );
318
319 let manifestString = stringify(sortedEntries);
320 if (
321 this.config.compileSrc &&
322 // See https://github.com/GoogleChrome/workbox/issues/2729
323 !(
324 compilation.options?.devtool === 'eval-cheap-source-map' &&
325 compilation.options.optimization?.minimize
326 )
327 ) {
328 // See https://github.com/GoogleChrome/workbox/issues/2263
329 manifestString = manifestString.replace(/"/g, `'`);
330 }
331
332 const sourcemapAssetName = getSourcemapAssetName(
333 compilation,
334 swAssetString,
335 config.swDest!,
336 );
337
338 if (sourcemapAssetName) {
339 _generatedAssetNames.add(sourcemapAssetName);
340 const sourcemapAsset = compilation.getAsset(sourcemapAssetName);
341 const {source, map} = await replaceAndUpdateSourceMap({
342 jsFilename: config.swDest!,
343 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
344 originalMap: JSON.parse(sourcemapAsset!.source.source().toString()),
345 originalSource: swAssetString,
346 replaceString: manifestString,
347 searchString: config.injectionPoint!,
348 });
349
350 compilation.updateAsset(sourcemapAssetName, new RawSource(map));
351 compilation.updateAsset(config.swDest!, new RawSource(source));
352 } else {
353 // If there's no sourcemap associated with swDest, a simple string
354 // replacement will suffice.
355 compilation.updateAsset(
356 config.swDest!,
357 new RawSource(
358 swAssetString.replace(config.injectionPoint!, manifestString),
359 ),
360 );
361 }
362
363 if (compilation.getLogger) {
364 const logger = compilation.getLogger(this.constructor.name);
365 logger.info(`The service worker at ${config.swDest ?? ''} will precache
366 ${sortedEntries.length} URLs, totaling ${prettyBytes(size)}.`);
367 }
368 }
369}
370
371export {InjectManifest};
Note: See TracBrowser for help on using the repository browser.