get-manifest-entries-from-compilation.js
6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"use strict";
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const {
matchPart
} = require('webpack').ModuleFilenameHelpers;
const transformManifest = require('workbox-build/build/lib/transform-manifest');
const getAssetHash = require('./get-asset-hash');
const resolveWebpackURL = require('./resolve-webpack-url');
/**
* For a given asset, checks whether at least one of the conditions matches.
*
* @param {Asset} asset The webpack asset in question. This will be passed
* to any functions that are listed as conditions.
* @param {Compilation} compilation The webpack compilation. This will be passed
* to any functions that are listed as conditions.
* @param {Array<string|RegExp|Function>} conditions
* @return {boolean} Whether or not at least one condition matches.
* @private
*/
function checkConditions(asset, compilation, conditions = []) {
for (const condition of conditions) {
if (typeof condition === 'function') {
if (condition({
asset,
compilation
})) {
return true;
}
} else {
if (matchPart(asset.name, condition)) {
return true;
}
}
} // We'll only get here if none of the conditions applied.
return false;
}
/**
* Creates a mapping of an asset name to an Set of zero or more chunk names
* that the asset is associated with.
*
* Those chunk names come from a combination of the `chunkName` property on the
* asset, as well as the `stats.namedChunkGroups` property. That is the only
* way to find out if an asset has an implicit descendent relationship with a
* chunk, if it was, e.g., created by `SplitChunksPlugin`.
*
* See https://github.com/GoogleChrome/workbox/issues/1859
* See https://github.com/webpack/webpack/issues/7073
*
* @param {Object} stats The webpack compilation stats.
* @return {object<string, Set<string>>}
* @private
*/
function assetToChunkNameMapping(stats) {
const mapping = {};
for (const asset of stats.assets) {
mapping[asset.name] = new Set(asset.chunkNames);
}
for (const [chunkName, {
assets
}] of Object.entries(stats.namedChunkGroups)) {
for (const assetName of assets) {
// See https://github.com/GoogleChrome/workbox/issues/2194
if (mapping[assetName]) {
mapping[assetName].add(chunkName);
}
}
}
return mapping;
}
/**
* Filters the set of assets out, based on the configuration options provided:
* - chunks and excludeChunks, for chunkName-based criteria.
* - include and exclude, for more general criteria.
*
* @param {Compilation} compilation The webpack compilation.
* @param {Object} config The validated configuration, obtained from the plugin.
* @return {Set<Asset>} The assets that should be included in the manifest,
* based on the criteria provided.
* @private
*/
function filterAssets(compilation, config) {
const filteredAssets = new Set(); // See https://webpack.js.org/configuration/stats/#stats
// We only need assets and chunkGroups here.
const stats = compilation.getStats().toJson({
assets: true,
chunkGroups: true
});
const assetNameToChunkNames = assetToChunkNameMapping(stats); // See https://github.com/GoogleChrome/workbox/issues/1287
if (Array.isArray(config.chunks)) {
for (const chunk of config.chunks) {
if (!(chunk in stats.namedChunkGroups)) {
compilation.warnings.push(`The chunk '${chunk}' was provided in ` + `your Workbox chunks config, but was not found in the compilation.`);
}
}
} // See https://webpack.js.org/api/stats/#asset-objects
for (const asset of stats.assets) {
// chunkName based filtering is funky because:
// - Each asset might belong to one or more chunkNames.
// - If *any* of those chunk names match our config.excludeChunks,
// then we skip that asset.
// - If the config.chunks is defined *and* there's no match
// between at least one of the chunkNames and one entry, then
// we skip that assets as well.
const isExcludedChunk = Array.isArray(config.excludeChunks) && config.excludeChunks.some(chunkName => {
return assetNameToChunkNames[asset.name].has(chunkName);
});
if (isExcludedChunk) {
continue;
}
const isIncludedChunk = !Array.isArray(config.chunks) || config.chunks.some(chunkName => {
return assetNameToChunkNames[asset.name].has(chunkName);
});
if (!isIncludedChunk) {
continue;
} // Next, check asset-level checks via includes/excludes:
const isExcluded = checkConditions(asset, compilation, config.exclude);
if (isExcluded) {
continue;
} // Treat an empty config.includes as an implicit inclusion.
const isIncluded = !Array.isArray(config.include) || checkConditions(asset, compilation, config.include);
if (!isIncluded) {
continue;
} // If we've gotten this far, then add the asset.
filteredAssets.add(asset);
}
return filteredAssets;
}
module.exports = async (compilation, config) => {
const filteredAssets = filterAssets(compilation, config);
const {
publicPath
} = compilation.options.output;
const fileDetails = [];
for (const asset of filteredAssets) {
// Not sure why this would be false, but checking just in case, since
// our original list of assets comes from compilation.getStats().toJson(),
// not from compilation.assets.
if (asset.name in compilation.assets) {
// This matches the format expected by transformManifest().
fileDetails.push({
file: resolveWebpackURL(publicPath, asset.name),
hash: getAssetHash(compilation.assets[asset.name]),
size: asset.size || 0
});
} else {
compilation.warnings.push(`Could not precache ${asset.name}, as it's ` + `missing from compilation.assets. Please open a bug against Workbox ` + `with details about your webpack config.`);
}
} // We also get back `size` and `count`, and it would be nice to log that
// somewhere, but... webpack doesn't offer info-level logs?
// https://github.com/webpack/webpack/issues/3996
const {
manifestEntries,
warnings
} = await transformManifest({
fileDetails,
additionalManifestEntries: config.additionalManifestEntries,
dontCacheBustURLsMatching: config.dontCacheBustURLsMatching,
manifestTransforms: config.manifestTransforms,
maximumFileSizeToCacheInBytes: config.maximumFileSizeToCacheInBytes,
modifyURLPrefix: config.modifyURLPrefix,
transformParam: compilation
});
compilation.warnings = compilation.warnings.concat(warnings || []); // Ensure that the entries are properly sorted by URL.
const sortedEntries = manifestEntries.sort((a, b) => a.url === b.url ? 0 : a.url > b.url ? 1 : -1);
return sortedEntries;
};