@serwist/webpack-plugin 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.internal.d.ts +3 -0
- package/dist/index.internal.d.ts.map +1 -0
- package/dist/index.internal.js +68 -0
- package/dist/index.internal.old.cjs +70 -0
- package/dist/index.js +494 -0
- package/dist/index.old.cjs +496 -0
- package/dist/inject-manifest.d.ts +83 -0
- package/dist/inject-manifest.d.ts.map +1 -0
- package/dist/lib/child-compilation-plugin.d.ts +21 -0
- package/dist/lib/child-compilation-plugin.d.ts.map +1 -0
- package/dist/lib/get-asset-hash.d.ts +9 -0
- package/dist/lib/get-asset-hash.d.ts.map +1 -0
- package/dist/lib/get-manifest-entries-from-compilation.d.ts +7 -0
- package/dist/lib/get-manifest-entries-from-compilation.d.ts.map +1 -0
- package/dist/lib/get-script-files-for-chunks.d.ts +3 -0
- package/dist/lib/get-script-files-for-chunks.d.ts.map +1 -0
- package/dist/lib/get-sourcemap-asset-name.d.ts +20 -0
- package/dist/lib/get-sourcemap-asset-name.d.ts.map +1 -0
- package/dist/lib/relative-to-output-path.d.ts +12 -0
- package/dist/lib/relative-to-output-path.d.ts.map +1 -0
- package/dist/lib/resolve-webpack-url.d.ts +13 -0
- package/dist/lib/resolve-webpack-url.d.ts.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Google LLC, 2019 ShadowWalker w@weiw.io https://weiw.io, 2023 Serwist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This module's documentation can be found at https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.internal.d.ts","sourceRoot":"","sources":["../src/index.internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import webpack from 'webpack';
|
|
2
|
+
import upath from 'upath';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param compilation The webpack compilation.
|
|
6
|
+
* @param swDest The original swDest value.
|
|
7
|
+
*
|
|
8
|
+
* @returns If swDest was not absolute, the returns swDest as-is.
|
|
9
|
+
* Otherwise, returns swDest relative to the compilation's output path.
|
|
10
|
+
*
|
|
11
|
+
* @private
|
|
12
|
+
*/ function relativeToOutputPath(compilation, swDest) {
|
|
13
|
+
// See https://github.com/jantimon/html-webpack-plugin/pull/266/files#diff-168726dbe96b3ce427e7fedce31bb0bcR38
|
|
14
|
+
if (upath.resolve(swDest) === upath.normalize(swDest)) {
|
|
15
|
+
return upath.relative(compilation.options.output.path, swDest);
|
|
16
|
+
}
|
|
17
|
+
// Otherwise, return swDest as-is.
|
|
18
|
+
return swDest;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compile a file by creating a child of the hooked compiler.
|
|
23
|
+
*
|
|
24
|
+
* @private
|
|
25
|
+
*/ class ChildCompilationPlugin {
|
|
26
|
+
src;
|
|
27
|
+
dest;
|
|
28
|
+
plugins;
|
|
29
|
+
constructor({ src, dest, plugins }){
|
|
30
|
+
this.src = src;
|
|
31
|
+
this.dest = dest;
|
|
32
|
+
this.plugins = plugins;
|
|
33
|
+
}
|
|
34
|
+
apply(compiler) {
|
|
35
|
+
compiler.hooks.make.tapPromise(this.constructor.name, (compilation)=>this.performChildCompilation(compilation, compiler).catch((error)=>{
|
|
36
|
+
compilation.errors.push(error);
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
async performChildCompilation(compilation, parentCompiler) {
|
|
40
|
+
const resolvedDest = relativeToOutputPath(compilation, this.dest);
|
|
41
|
+
const outputOptions = {
|
|
42
|
+
filename: resolvedDest
|
|
43
|
+
};
|
|
44
|
+
const childCompiler = compilation.createChildCompiler(this.constructor.name, outputOptions, []);
|
|
45
|
+
childCompiler.context = parentCompiler.context;
|
|
46
|
+
childCompiler.inputFileSystem = parentCompiler.inputFileSystem;
|
|
47
|
+
childCompiler.outputFileSystem = parentCompiler.outputFileSystem;
|
|
48
|
+
if (this.plugins !== undefined) {
|
|
49
|
+
for (const plugin of this.plugins){
|
|
50
|
+
plugin?.apply(childCompiler);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
new webpack.EntryPlugin(parentCompiler.context, this.src, this.constructor.name).apply(childCompiler);
|
|
54
|
+
await new Promise((resolve, reject)=>{
|
|
55
|
+
childCompiler.runAsChild((error, _entries, childCompilation)=>{
|
|
56
|
+
if (error) {
|
|
57
|
+
reject(error);
|
|
58
|
+
} else {
|
|
59
|
+
compilation.warnings = compilation.warnings.concat(childCompilation?.warnings ?? []);
|
|
60
|
+
compilation.errors = compilation.errors.concat(childCompilation?.errors ?? []);
|
|
61
|
+
resolve();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { ChildCompilationPlugin };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var webpack = require('webpack');
|
|
4
|
+
var upath = require('upath');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param compilation The webpack compilation.
|
|
8
|
+
* @param swDest The original swDest value.
|
|
9
|
+
*
|
|
10
|
+
* @returns If swDest was not absolute, the returns swDest as-is.
|
|
11
|
+
* Otherwise, returns swDest relative to the compilation's output path.
|
|
12
|
+
*
|
|
13
|
+
* @private
|
|
14
|
+
*/ function relativeToOutputPath(compilation, swDest) {
|
|
15
|
+
// See https://github.com/jantimon/html-webpack-plugin/pull/266/files#diff-168726dbe96b3ce427e7fedce31bb0bcR38
|
|
16
|
+
if (upath.resolve(swDest) === upath.normalize(swDest)) {
|
|
17
|
+
return upath.relative(compilation.options.output.path, swDest);
|
|
18
|
+
}
|
|
19
|
+
// Otherwise, return swDest as-is.
|
|
20
|
+
return swDest;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Compile a file by creating a child of the hooked compiler.
|
|
25
|
+
*
|
|
26
|
+
* @private
|
|
27
|
+
*/ class ChildCompilationPlugin {
|
|
28
|
+
src;
|
|
29
|
+
dest;
|
|
30
|
+
plugins;
|
|
31
|
+
constructor({ src, dest, plugins }){
|
|
32
|
+
this.src = src;
|
|
33
|
+
this.dest = dest;
|
|
34
|
+
this.plugins = plugins;
|
|
35
|
+
}
|
|
36
|
+
apply(compiler) {
|
|
37
|
+
compiler.hooks.make.tapPromise(this.constructor.name, (compilation)=>this.performChildCompilation(compilation, compiler).catch((error)=>{
|
|
38
|
+
compilation.errors.push(error);
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
async performChildCompilation(compilation, parentCompiler) {
|
|
42
|
+
const resolvedDest = relativeToOutputPath(compilation, this.dest);
|
|
43
|
+
const outputOptions = {
|
|
44
|
+
filename: resolvedDest
|
|
45
|
+
};
|
|
46
|
+
const childCompiler = compilation.createChildCompiler(this.constructor.name, outputOptions, []);
|
|
47
|
+
childCompiler.context = parentCompiler.context;
|
|
48
|
+
childCompiler.inputFileSystem = parentCompiler.inputFileSystem;
|
|
49
|
+
childCompiler.outputFileSystem = parentCompiler.outputFileSystem;
|
|
50
|
+
if (this.plugins !== undefined) {
|
|
51
|
+
for (const plugin of this.plugins){
|
|
52
|
+
plugin?.apply(childCompiler);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
new webpack.EntryPlugin(parentCompiler.context, this.src, this.constructor.name).apply(childCompiler);
|
|
56
|
+
await new Promise((resolve, reject)=>{
|
|
57
|
+
childCompiler.runAsChild((error, _entries, childCompilation)=>{
|
|
58
|
+
if (error) {
|
|
59
|
+
reject(error);
|
|
60
|
+
} else {
|
|
61
|
+
compilation.warnings = compilation.warnings.concat(childCompilation?.warnings ?? []);
|
|
62
|
+
compilation.errors = compilation.errors.concat(childCompilation?.errors ?? []);
|
|
63
|
+
resolve();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exports.ChildCompilationPlugin = ChildCompilationPlugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { transformManifest, getSourceMapURL, validateWebpackInjectManifestOptions, escapeRegExp, replaceAndUpdateSourceMap } from '@serwist/build';
|
|
3
|
+
import stringify from 'fast-json-stable-stringify';
|
|
4
|
+
import prettyBytes from 'pretty-bytes';
|
|
5
|
+
import upath from 'upath';
|
|
6
|
+
import webpack from 'webpack';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param asset
|
|
11
|
+
* @returns The MD5 hash of the asset's source.
|
|
12
|
+
*
|
|
13
|
+
* @private
|
|
14
|
+
*/ function getAssetHash(asset) {
|
|
15
|
+
// If webpack has the asset marked as immutable, then we don't need to
|
|
16
|
+
// use an out-of-band revision for it.
|
|
17
|
+
// See https://github.com/webpack/webpack/issues/9038
|
|
18
|
+
if (asset.info && asset.info.immutable) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return crypto.createHash("md5").update(Buffer.from(asset.source.source())).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
Copyright 2018 Google LLC
|
|
26
|
+
|
|
27
|
+
Use of this source code is governed by an MIT-style
|
|
28
|
+
license that can be found in the LICENSE file or at
|
|
29
|
+
https://opensource.org/licenses/MIT.
|
|
30
|
+
*/ /**
|
|
31
|
+
* Resolves a url in the way that webpack would (with string concatenation)
|
|
32
|
+
*
|
|
33
|
+
* Use publicPath + filePath instead of url.resolve(publicPath, filePath) see:
|
|
34
|
+
* https://webpack.js.org/configuration/output/#output-publicpath
|
|
35
|
+
*
|
|
36
|
+
* @param publicPath The publicPath value from webpack's compilation.
|
|
37
|
+
* @param paths File paths to join
|
|
38
|
+
* @returns Joined file path
|
|
39
|
+
* @private
|
|
40
|
+
*/ function resolveWebpackURL(publicPath, ...paths) {
|
|
41
|
+
// This is a change in webpack v5.
|
|
42
|
+
// See https://github.com/jantimon/html-webpack-plugin/pull/1516
|
|
43
|
+
if (publicPath === "auto") {
|
|
44
|
+
return paths.join("");
|
|
45
|
+
} else {
|
|
46
|
+
return [
|
|
47
|
+
publicPath,
|
|
48
|
+
...paths
|
|
49
|
+
].join("");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* For a given asset, checks whether at least one of the conditions matches.
|
|
55
|
+
*
|
|
56
|
+
* @param asset The webpack asset in question. This will be passed
|
|
57
|
+
* to any functions that are listed as conditions.
|
|
58
|
+
* @param compilation The webpack compilation. This will be passed
|
|
59
|
+
* to any functions that are listed as conditions.
|
|
60
|
+
* @param conditions
|
|
61
|
+
* @returns Whether or not at least one condition matches.
|
|
62
|
+
* @private
|
|
63
|
+
*/ function checkConditions(asset, compilation, conditions = []) {
|
|
64
|
+
for (const condition of conditions){
|
|
65
|
+
if (typeof condition === "function") {
|
|
66
|
+
return condition({
|
|
67
|
+
asset,
|
|
68
|
+
compilation
|
|
69
|
+
});
|
|
70
|
+
//return compilation !== null;
|
|
71
|
+
} else {
|
|
72
|
+
if (webpack.ModuleFilenameHelpers.matchPart(asset.name, condition)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// We'll only get here if none of the conditions applied.
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns the names of all the assets in all the chunks in a chunk group,
|
|
82
|
+
* if provided a chunk group name.
|
|
83
|
+
* Otherwise, if provided a chunk name, return all the assets in that chunk.
|
|
84
|
+
* Otherwise, if there isn't a chunk group or chunk with that name, return null.
|
|
85
|
+
*
|
|
86
|
+
* @param compilation
|
|
87
|
+
* @param chunkOrGroup
|
|
88
|
+
* @returns
|
|
89
|
+
* @private
|
|
90
|
+
*/ function getNamesOfAssetsInChunkOrGroup(compilation, chunkOrGroup) {
|
|
91
|
+
const chunkGroup = compilation.namedChunkGroups && compilation.namedChunkGroups.get(chunkOrGroup);
|
|
92
|
+
if (chunkGroup) {
|
|
93
|
+
const assetNames = [];
|
|
94
|
+
for (const chunk of chunkGroup.chunks){
|
|
95
|
+
assetNames.push(...getNamesOfAssetsInChunk(chunk));
|
|
96
|
+
}
|
|
97
|
+
return assetNames;
|
|
98
|
+
} else {
|
|
99
|
+
const chunk = compilation.namedChunks && compilation.namedChunks.get(chunkOrGroup);
|
|
100
|
+
if (chunk) {
|
|
101
|
+
return getNamesOfAssetsInChunk(chunk);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// If we get here, there's no chunkGroup or chunk with that name.
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns the names of all the assets in a chunk.
|
|
109
|
+
*
|
|
110
|
+
* @param chunk
|
|
111
|
+
* @returns
|
|
112
|
+
* @private
|
|
113
|
+
*/ function getNamesOfAssetsInChunk(chunk) {
|
|
114
|
+
const assetNames = [];
|
|
115
|
+
assetNames.push(...chunk.files);
|
|
116
|
+
// This only appears to be set in webpack v5.
|
|
117
|
+
if (chunk.auxiliaryFiles) {
|
|
118
|
+
assetNames.push(...chunk.auxiliaryFiles);
|
|
119
|
+
}
|
|
120
|
+
return assetNames;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Filters the set of assets out, based on the configuration options provided:
|
|
124
|
+
* - chunks and excludeChunks, for chunkName-based criteria.
|
|
125
|
+
* - include and exclude, for more general criteria.
|
|
126
|
+
*
|
|
127
|
+
* @param compilation The webpack compilation.
|
|
128
|
+
* @param config The validated configuration, obtained from the plugin.
|
|
129
|
+
* @returns The assets that should be included in the manifest,
|
|
130
|
+
* based on the criteria provided.
|
|
131
|
+
* @private
|
|
132
|
+
*/ function filterAssets(compilation, config) {
|
|
133
|
+
const filteredAssets = new Set();
|
|
134
|
+
const assets = compilation.getAssets();
|
|
135
|
+
const allowedAssetNames = new Set();
|
|
136
|
+
// See https://github.com/GoogleChrome/workbox/issues/1287
|
|
137
|
+
if (Array.isArray(config.chunks)) {
|
|
138
|
+
for (const name of config.chunks){
|
|
139
|
+
// See https://github.com/GoogleChrome/workbox/issues/2717
|
|
140
|
+
const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(compilation, name);
|
|
141
|
+
if (assetsInChunkOrGroup) {
|
|
142
|
+
for (const assetName of assetsInChunkOrGroup){
|
|
143
|
+
allowedAssetNames.add(assetName);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
compilation.warnings.push(new Error(`The chunk '${name}' was ` + `provided in your Serwist chunks config, but was not found in the ` + `compilation.`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const deniedAssetNames = new Set();
|
|
151
|
+
if (Array.isArray(config.excludeChunks)) {
|
|
152
|
+
for (const name of config.excludeChunks){
|
|
153
|
+
// See https://github.com/GoogleChrome/workbox/issues/2717
|
|
154
|
+
const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(compilation, name);
|
|
155
|
+
if (assetsInChunkOrGroup) {
|
|
156
|
+
for (const assetName of assetsInChunkOrGroup){
|
|
157
|
+
deniedAssetNames.add(assetName);
|
|
158
|
+
}
|
|
159
|
+
} // Don't warn if the chunk group isn't found.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const asset of assets){
|
|
163
|
+
// chunk based filtering is funky because:
|
|
164
|
+
// - Each asset might belong to one or more chunks.
|
|
165
|
+
// - If *any* of those chunk names match our config.excludeChunks,
|
|
166
|
+
// then we skip that asset.
|
|
167
|
+
// - If the config.chunks is defined *and* there's no match
|
|
168
|
+
// between at least one of the chunkNames and one entry, then
|
|
169
|
+
// we skip that assets as well.
|
|
170
|
+
if (deniedAssetNames.has(asset.name)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(config.chunks) && !allowedAssetNames.has(asset.name)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// Next, check asset-level checks via includes/excludes:
|
|
177
|
+
const isExcluded = checkConditions(asset, compilation, config.exclude);
|
|
178
|
+
if (isExcluded) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Treat an empty config.includes as an implicit inclusion.
|
|
182
|
+
const isIncluded = !Array.isArray(config.include) || checkConditions(asset, compilation, config.include);
|
|
183
|
+
if (!isIncluded) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// If we've gotten this far, then add the asset.
|
|
187
|
+
filteredAssets.add(asset);
|
|
188
|
+
}
|
|
189
|
+
return filteredAssets;
|
|
190
|
+
}
|
|
191
|
+
async function getManifestEntriesFromCompilation(compilation, config) {
|
|
192
|
+
const filteredAssets = filterAssets(compilation, config);
|
|
193
|
+
const { publicPath } = compilation.options.output;
|
|
194
|
+
const fileDetails = Array.from(filteredAssets).map((asset)=>{
|
|
195
|
+
return {
|
|
196
|
+
file: resolveWebpackURL(publicPath, asset.name),
|
|
197
|
+
hash: getAssetHash(asset),
|
|
198
|
+
size: asset.source.size() || 0
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
const { manifestEntries, size, warnings } = await transformManifest({
|
|
202
|
+
fileDetails,
|
|
203
|
+
additionalPrecacheEntries: config.additionalPrecacheEntries,
|
|
204
|
+
dontCacheBustURLsMatching: config.dontCacheBustURLsMatching,
|
|
205
|
+
manifestTransforms: config.manifestTransforms,
|
|
206
|
+
maximumFileSizeToCacheInBytes: config.maximumFileSizeToCacheInBytes,
|
|
207
|
+
modifyURLPrefix: config.modifyURLPrefix,
|
|
208
|
+
transformParam: compilation
|
|
209
|
+
});
|
|
210
|
+
// See https://github.com/GoogleChrome/workbox/issues/2790
|
|
211
|
+
for (const warning of warnings){
|
|
212
|
+
compilation.warnings.push(new Error(warning));
|
|
213
|
+
}
|
|
214
|
+
// Ensure that the entries are properly sorted by URL.
|
|
215
|
+
const sortedEntries = manifestEntries.sort((a, b)=>a.url === b.url ? 0 : a.url > b.url ? 1 : -1);
|
|
216
|
+
return {
|
|
217
|
+
size,
|
|
218
|
+
sortedEntries
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* If our bundled swDest file contains a sourcemap, we would invalidate that
|
|
224
|
+
* mapping if we just replaced injectionPoint with the stringified manifest.
|
|
225
|
+
* Instead, we need to update the swDest contents as well as the sourcemap
|
|
226
|
+
* at the same time.
|
|
227
|
+
*
|
|
228
|
+
* See https://github.com/GoogleChrome/workbox/issues/2235
|
|
229
|
+
*
|
|
230
|
+
* @param compilation The current webpack compilation.
|
|
231
|
+
* @param swContents The contents of the swSrc file, which may or
|
|
232
|
+
* may not include a valid sourcemap comment.
|
|
233
|
+
* @param swDest The configured swDest value.
|
|
234
|
+
* @returns If the swContents contains a valid sourcemap
|
|
235
|
+
* comment pointing to an asset present in the compilation, this will return the
|
|
236
|
+
* name of that asset. Otherwise, it will return undefined.
|
|
237
|
+
* @private
|
|
238
|
+
*/ function getSourcemapAssetName(compilation, swContents, swDest) {
|
|
239
|
+
const url = getSourceMapURL(swContents);
|
|
240
|
+
if (url) {
|
|
241
|
+
// Translate the relative URL to what the presumed name for the webpack
|
|
242
|
+
// asset should be.
|
|
243
|
+
// This *might* not be a valid asset if the sourcemap URL that was found
|
|
244
|
+
// was added by another module incidentally.
|
|
245
|
+
// See https://github.com/GoogleChrome/workbox/issues/2250
|
|
246
|
+
const swAssetDirname = upath.dirname(swDest);
|
|
247
|
+
const sourcemapURLAssetName = upath.normalize(upath.join(swAssetDirname, url));
|
|
248
|
+
// Not sure if there's a better way to check for asset existence?
|
|
249
|
+
if (compilation.getAsset(sourcemapURLAssetName)) {
|
|
250
|
+
return sourcemapURLAssetName;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param compilation The webpack compilation.
|
|
258
|
+
* @param swDest The original swDest value.
|
|
259
|
+
*
|
|
260
|
+
* @returns If swDest was not absolute, the returns swDest as-is.
|
|
261
|
+
* Otherwise, returns swDest relative to the compilation's output path.
|
|
262
|
+
*
|
|
263
|
+
* @private
|
|
264
|
+
*/ function relativeToOutputPath(compilation, swDest) {
|
|
265
|
+
// See https://github.com/jantimon/html-webpack-plugin/pull/266/files#diff-168726dbe96b3ce427e7fedce31bb0bcR38
|
|
266
|
+
if (upath.resolve(swDest) === upath.normalize(swDest)) {
|
|
267
|
+
return upath.relative(compilation.options.output.path, swDest);
|
|
268
|
+
}
|
|
269
|
+
// Otherwise, return swDest as-is.
|
|
270
|
+
return swDest;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Used to keep track of swDest files written by *any* instance of this plugin.
|
|
274
|
+
// See https://github.com/GoogleChrome/workbox/issues/2181
|
|
275
|
+
const _generatedAssetNames = new Set();
|
|
276
|
+
/**
|
|
277
|
+
* This class supports compiling a service worker file provided via `swSrc`,
|
|
278
|
+
* and injecting into that service worker a list of URLs and revision
|
|
279
|
+
* information for precaching based on the webpack asset pipeline.
|
|
280
|
+
*
|
|
281
|
+
* Use an instance of `InjectManifest` in the
|
|
282
|
+
* [`plugins` array](https://webpack.js.org/concepts/plugins/#usage) of a
|
|
283
|
+
* webpack config.
|
|
284
|
+
*
|
|
285
|
+
* In addition to injecting the manifest, this plugin will perform a compilation
|
|
286
|
+
* of the `swSrc` file, using the options from the main webpack configuration.
|
|
287
|
+
*
|
|
288
|
+
* ```
|
|
289
|
+
* // The following lists some common options; see the rest of the documentation
|
|
290
|
+
* // for the full set of options and defaults.
|
|
291
|
+
* new InjectManifest({
|
|
292
|
+
* exclude: [/.../, '...'],
|
|
293
|
+
* maximumFileSizeToCacheInBytes: ...,
|
|
294
|
+
* swSrc: '...',
|
|
295
|
+
* });
|
|
296
|
+
* ```
|
|
297
|
+
*/ class InjectManifest {
|
|
298
|
+
config;
|
|
299
|
+
alreadyCalled;
|
|
300
|
+
/**
|
|
301
|
+
* Creates an instance of InjectManifest.
|
|
302
|
+
*/ constructor(config){
|
|
303
|
+
this.config = config;
|
|
304
|
+
this.alreadyCalled = false;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* @param compiler default compiler object passed from webpack
|
|
308
|
+
*
|
|
309
|
+
* @private
|
|
310
|
+
*/ propagateWebpackConfig(compiler) {
|
|
311
|
+
const parsedSwSrc = upath.parse(this.config.swSrc);
|
|
312
|
+
// Because this.config is listed last, properties that are already set
|
|
313
|
+
// there take precedence over derived properties from the compiler.
|
|
314
|
+
this.config = Object.assign({
|
|
315
|
+
mode: compiler.options.mode,
|
|
316
|
+
// Use swSrc with a hardcoded .js extension, in case swSrc is a .ts file.
|
|
317
|
+
swDest: path.join(parsedSwSrc.dir, parsedSwSrc.name + ".js")
|
|
318
|
+
}, this.config);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* `getManifestEntriesFromCompilation` with a few additional checks.
|
|
322
|
+
*
|
|
323
|
+
* @private
|
|
324
|
+
*/ async getManifestEntries(compilation, config) {
|
|
325
|
+
if (config.disablePrecacheManifest) {
|
|
326
|
+
return {
|
|
327
|
+
size: 0,
|
|
328
|
+
sortedEntries: [],
|
|
329
|
+
manifestString: "undefined"
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
// See https://github.com/GoogleChrome/workbox/issues/1790
|
|
333
|
+
if (this.alreadyCalled) {
|
|
334
|
+
const warningMessage = `${this.constructor.name} has been called ` + `multiple times, perhaps due to running webpack in --watch mode. The ` + `precache manifest generated after the first call may be inaccurate! ` + `Please see https://github.com/GoogleChrome/workbox/issues/1790 for ` + `more information.`;
|
|
335
|
+
if (!compilation.warnings.some((warning)=>warning instanceof Error && warning.message === warningMessage)) {
|
|
336
|
+
compilation.warnings.push(new Error(warningMessage));
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
this.alreadyCalled = true;
|
|
340
|
+
}
|
|
341
|
+
// Ensure that we don't precache any of the assets generated by *any*
|
|
342
|
+
// instance of this plugin.
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
344
|
+
config.exclude.push(({ asset })=>_generatedAssetNames.has(asset.name));
|
|
345
|
+
const { size, sortedEntries } = await getManifestEntriesFromCompilation(compilation, config);
|
|
346
|
+
let manifestString = stringify(sortedEntries);
|
|
347
|
+
if (this.config.compileSrc && // See https://github.com/GoogleChrome/workbox/issues/2729
|
|
348
|
+
!(compilation.options?.devtool === "eval-cheap-source-map" && compilation.options.optimization?.minimize)) {
|
|
349
|
+
// See https://github.com/GoogleChrome/workbox/issues/2263
|
|
350
|
+
manifestString = manifestString.replace(/"/g, `'`);
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
size,
|
|
354
|
+
sortedEntries,
|
|
355
|
+
manifestString
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @param compiler default compiler object passed from webpack
|
|
360
|
+
*
|
|
361
|
+
* @private
|
|
362
|
+
*/ apply(compiler) {
|
|
363
|
+
this.propagateWebpackConfig(compiler);
|
|
364
|
+
compiler.hooks.make.tapPromise(this.constructor.name, (compilation)=>this.handleMake(compilation, compiler).catch((error)=>{
|
|
365
|
+
compilation.errors.push(error);
|
|
366
|
+
}));
|
|
367
|
+
const { PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER } = webpack.Compilation;
|
|
368
|
+
// Specifically hook into thisCompilation, as per
|
|
369
|
+
// https://github.com/webpack/webpack/issues/11425#issuecomment-690547848
|
|
370
|
+
compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation)=>{
|
|
371
|
+
compilation.hooks.processAssets.tapPromise({
|
|
372
|
+
name: this.constructor.name,
|
|
373
|
+
// TODO(jeffposnick): This may need to change eventually.
|
|
374
|
+
// See https://github.com/webpack/webpack/issues/11822#issuecomment-726184972
|
|
375
|
+
stage: PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 10
|
|
376
|
+
}, ()=>this.addAssets(compilation).catch((error)=>{
|
|
377
|
+
compilation.errors.push(error);
|
|
378
|
+
}));
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* @param compilation The webpack compilation.
|
|
383
|
+
* @param parentCompiler The webpack parent compiler.
|
|
384
|
+
*
|
|
385
|
+
* @private
|
|
386
|
+
*/ async performChildCompilation(compilation, parentCompiler) {
|
|
387
|
+
const outputOptions = {
|
|
388
|
+
filename: this.config.swDest
|
|
389
|
+
};
|
|
390
|
+
const childCompiler = compilation.createChildCompiler(this.constructor.name, outputOptions, []);
|
|
391
|
+
childCompiler.context = parentCompiler.context;
|
|
392
|
+
childCompiler.inputFileSystem = parentCompiler.inputFileSystem;
|
|
393
|
+
childCompiler.outputFileSystem = parentCompiler.outputFileSystem;
|
|
394
|
+
if (Array.isArray(this.config.webpackCompilationPlugins)) {
|
|
395
|
+
for (const plugin of this.config.webpackCompilationPlugins){
|
|
396
|
+
plugin.apply(childCompiler);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
new webpack.EntryPlugin(parentCompiler.context, this.config.swSrc, this.constructor.name).apply(childCompiler);
|
|
400
|
+
await new Promise((resolve, reject)=>{
|
|
401
|
+
childCompiler.runAsChild((error, _entries, childCompilation)=>{
|
|
402
|
+
if (error) {
|
|
403
|
+
reject(error);
|
|
404
|
+
} else {
|
|
405
|
+
compilation.warnings = compilation.warnings.concat(childCompilation?.warnings ?? []);
|
|
406
|
+
compilation.errors = compilation.errors.concat(childCompilation?.errors ?? []);
|
|
407
|
+
resolve();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* @param compilation The webpack compilation.
|
|
414
|
+
* @param parentCompiler The webpack parent compiler.
|
|
415
|
+
*
|
|
416
|
+
* @private
|
|
417
|
+
*/ addSrcToAssets(compilation, parentCompiler) {
|
|
418
|
+
// eslint-disable-next-line
|
|
419
|
+
const source = parentCompiler.inputFileSystem.readFileSync(this.config.swSrc);
|
|
420
|
+
compilation.emitAsset(this.config.swDest, new webpack.sources.RawSource(source));
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* @param compilation The webpack compilation.
|
|
424
|
+
* @param parentCompiler The webpack parent compiler.
|
|
425
|
+
*
|
|
426
|
+
* @private
|
|
427
|
+
*/ async handleMake(compilation, parentCompiler) {
|
|
428
|
+
try {
|
|
429
|
+
this.config = validateWebpackInjectManifestOptions(this.config);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
if (error instanceof Error) {
|
|
432
|
+
throw new Error(`Please check your ${this.constructor.name} plugin ` + `configuration:\n${error.message}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
this.config.swDest = relativeToOutputPath(compilation, this.config.swDest);
|
|
436
|
+
_generatedAssetNames.add(this.config.swDest);
|
|
437
|
+
if (this.config.compileSrc) {
|
|
438
|
+
await this.performChildCompilation(compilation, parentCompiler);
|
|
439
|
+
} else {
|
|
440
|
+
this.addSrcToAssets(compilation, parentCompiler);
|
|
441
|
+
// This used to be a fatal error, but just warn at runtime because we
|
|
442
|
+
// can't validate it easily.
|
|
443
|
+
if (Array.isArray(this.config.webpackCompilationPlugins) && this.config.webpackCompilationPlugins.length > 0) {
|
|
444
|
+
compilation.warnings.push(new Error("compileSrc is false, so the " + "webpackCompilationPlugins option will be ignored."));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* @param compilation The webpack compilation.
|
|
450
|
+
*
|
|
451
|
+
* @private
|
|
452
|
+
*/ async addAssets(compilation) {
|
|
453
|
+
const config = Object.assign({}, this.config);
|
|
454
|
+
const { size, sortedEntries, manifestString } = await this.getManifestEntries(compilation, config);
|
|
455
|
+
// See https://webpack.js.org/contribute/plugin-patterns/#monitoring-the-watch-graph
|
|
456
|
+
const absoluteSwSrc = upath.resolve(config.swSrc);
|
|
457
|
+
compilation.fileDependencies.add(absoluteSwSrc);
|
|
458
|
+
const swAsset = compilation.getAsset(config.swDest);
|
|
459
|
+
const swAssetString = swAsset.source.source().toString();
|
|
460
|
+
const globalRegexp = new RegExp(escapeRegExp(config.injectionPoint), "g");
|
|
461
|
+
const injectionResults = swAssetString.match(globalRegexp);
|
|
462
|
+
if (!injectionResults) {
|
|
463
|
+
throw new Error(`Can't find ${config.injectionPoint} in your SW source.`);
|
|
464
|
+
}
|
|
465
|
+
if (injectionResults.length !== 1) {
|
|
466
|
+
throw new Error(`Multiple instances of ${config.injectionPoint} were ` + "found in your SW source. Include it only once. For more info, see " + "https://github.com/GoogleChrome/workbox/issues/2681");
|
|
467
|
+
}
|
|
468
|
+
const sourcemapAssetName = getSourcemapAssetName(compilation, swAssetString, config.swDest);
|
|
469
|
+
if (sourcemapAssetName) {
|
|
470
|
+
_generatedAssetNames.add(sourcemapAssetName);
|
|
471
|
+
const sourcemapAsset = compilation.getAsset(sourcemapAssetName);
|
|
472
|
+
const { source, map } = await replaceAndUpdateSourceMap({
|
|
473
|
+
jsFilename: config.swDest,
|
|
474
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
475
|
+
originalMap: JSON.parse(sourcemapAsset.source.source().toString()),
|
|
476
|
+
originalSource: swAssetString,
|
|
477
|
+
replaceString: manifestString,
|
|
478
|
+
searchString: config.injectionPoint
|
|
479
|
+
});
|
|
480
|
+
compilation.updateAsset(sourcemapAssetName, new webpack.sources.RawSource(map));
|
|
481
|
+
compilation.updateAsset(config.swDest, new webpack.sources.RawSource(source));
|
|
482
|
+
} else {
|
|
483
|
+
// If there's no sourcemap associated with swDest, a simple string
|
|
484
|
+
// replacement will suffice.
|
|
485
|
+
compilation.updateAsset(config.swDest, new webpack.sources.RawSource(swAssetString.replace(config.injectionPoint, manifestString)));
|
|
486
|
+
}
|
|
487
|
+
if (compilation.getLogger) {
|
|
488
|
+
const logger = compilation.getLogger(this.constructor.name);
|
|
489
|
+
logger.info(`The service worker at ${config.swDest ?? ""} will precache ${sortedEntries.length} URLs, totaling ${prettyBytes(size)}.`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export { InjectManifest };
|