| 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 {
|
|---|
| 10 | Asset,
|
|---|
| 11 | Chunk,
|
|---|
| 12 | Compilation,
|
|---|
| 13 | ModuleFilenameHelpers,
|
|---|
| 14 | WebpackError,
|
|---|
| 15 | } from 'webpack';
|
|---|
| 16 | import {transformManifest} from 'workbox-build/build/lib/transform-manifest';
|
|---|
| 17 |
|
|---|
| 18 | import {
|
|---|
| 19 | WebpackGenerateSWOptions,
|
|---|
| 20 | WebpackInjectManifestOptions,
|
|---|
| 21 | ManifestEntry,
|
|---|
| 22 | FileDetails,
|
|---|
| 23 | } from 'workbox-build';
|
|---|
| 24 | import {getAssetHash} from './get-asset-hash';
|
|---|
| 25 | import {resolveWebpackURL} from './resolve-webpack-url';
|
|---|
| 26 |
|
|---|
| 27 | /**
|
|---|
| 28 | * For a given asset, checks whether at least one of the conditions matches.
|
|---|
| 29 | *
|
|---|
| 30 | * @param {Asset} asset The webpack asset in question. This will be passed
|
|---|
| 31 | * to any functions that are listed as conditions.
|
|---|
| 32 | * @param {Compilation} compilation The webpack compilation. This will be passed
|
|---|
| 33 | * to any functions that are listed as conditions.
|
|---|
| 34 | * @param {Array<string|RegExp|Function>} conditions
|
|---|
| 35 | * @return {boolean} Whether or not at least one condition matches.
|
|---|
| 36 | * @private
|
|---|
| 37 | */
|
|---|
| 38 | function checkConditions(
|
|---|
| 39 | asset: Asset,
|
|---|
| 40 | compilation: Compilation,
|
|---|
| 41 |
|
|---|
| 42 | conditions: Array<
|
|---|
| 43 | //eslint-disable-next-line @typescript-eslint/ban-types
|
|---|
| 44 | string | RegExp | ((arg0: any) => boolean)
|
|---|
| 45 | > = [],
|
|---|
| 46 | ): boolean {
|
|---|
| 47 | for (const condition of conditions) {
|
|---|
| 48 | if (typeof condition === 'function') {
|
|---|
| 49 | return condition({asset, compilation});
|
|---|
| 50 | //return compilation !== null;
|
|---|
| 51 | } else {
|
|---|
| 52 | if (ModuleFilenameHelpers.matchPart(asset.name, condition)) {
|
|---|
| 53 | return true;
|
|---|
| 54 | }
|
|---|
| 55 | }
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | // We'll only get here if none of the conditions applied.
|
|---|
| 59 | return false;
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | /**
|
|---|
| 63 | * Returns the names of all the assets in all the chunks in a chunk group,
|
|---|
| 64 | * if provided a chunk group name.
|
|---|
| 65 | * Otherwise, if provided a chunk name, return all the assets in that chunk.
|
|---|
| 66 | * Otherwise, if there isn't a chunk group or chunk with that name, return null.
|
|---|
| 67 | *
|
|---|
| 68 | * @param {Compilation} compilation
|
|---|
| 69 | * @param {string} chunkOrGroup
|
|---|
| 70 | * @return {Array<string>|null}
|
|---|
| 71 | * @private
|
|---|
| 72 | */
|
|---|
| 73 | function getNamesOfAssetsInChunkOrGroup(
|
|---|
| 74 | compilation: Compilation,
|
|---|
| 75 | chunkOrGroup: string,
|
|---|
| 76 | ): Array<string> | null {
|
|---|
| 77 | const chunkGroup =
|
|---|
| 78 | compilation.namedChunkGroups &&
|
|---|
| 79 | compilation.namedChunkGroups.get(chunkOrGroup);
|
|---|
| 80 | if (chunkGroup) {
|
|---|
| 81 | const assetNames = [];
|
|---|
| 82 | for (const chunk of chunkGroup.chunks) {
|
|---|
| 83 | assetNames.push(...getNamesOfAssetsInChunk(chunk));
|
|---|
| 84 | }
|
|---|
| 85 | return assetNames;
|
|---|
| 86 | } else {
|
|---|
| 87 | const chunk =
|
|---|
| 88 | compilation.namedChunks && compilation.namedChunks.get(chunkOrGroup);
|
|---|
| 89 | if (chunk) {
|
|---|
| 90 | return getNamesOfAssetsInChunk(chunk);
|
|---|
| 91 | }
|
|---|
| 92 | }
|
|---|
| 93 |
|
|---|
| 94 | // If we get here, there's no chunkGroup or chunk with that name.
|
|---|
| 95 | return null;
|
|---|
| 96 | }
|
|---|
| 97 |
|
|---|
| 98 | /**
|
|---|
| 99 | * Returns the names of all the assets in a chunk.
|
|---|
| 100 | *
|
|---|
| 101 | * @param {Chunk} chunk
|
|---|
| 102 | * @return {Array<string>}
|
|---|
| 103 | * @private
|
|---|
| 104 | */
|
|---|
| 105 | function getNamesOfAssetsInChunk(chunk: Chunk): Array<string> {
|
|---|
| 106 | const assetNames: Array<string> = [];
|
|---|
| 107 |
|
|---|
| 108 | assetNames.push(...chunk.files);
|
|---|
| 109 |
|
|---|
| 110 | // This only appears to be set in webpack v5.
|
|---|
| 111 | if (chunk.auxiliaryFiles) {
|
|---|
| 112 | assetNames.push(...chunk.auxiliaryFiles);
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | return assetNames;
|
|---|
| 116 | }
|
|---|
| 117 |
|
|---|
| 118 | /**
|
|---|
| 119 | * Filters the set of assets out, based on the configuration options provided:
|
|---|
| 120 | * - chunks and excludeChunks, for chunkName-based criteria.
|
|---|
| 121 | * - include and exclude, for more general criteria.
|
|---|
| 122 | *
|
|---|
| 123 | * @param {Compilation} compilation The webpack compilation.
|
|---|
| 124 | * @param {Object} config The validated configuration, obtained from the plugin.
|
|---|
| 125 | * @return {Set<Asset>} The assets that should be included in the manifest,
|
|---|
| 126 | * based on the criteria provided.
|
|---|
| 127 | * @private
|
|---|
| 128 | */
|
|---|
| 129 | function filterAssets(
|
|---|
| 130 | compilation: Compilation,
|
|---|
| 131 | config: WebpackInjectManifestOptions | WebpackGenerateSWOptions,
|
|---|
| 132 | ): Set<Asset> {
|
|---|
| 133 | const filteredAssets = new Set<Asset>();
|
|---|
| 134 | const assets = compilation.getAssets();
|
|---|
| 135 |
|
|---|
| 136 | const allowedAssetNames = new Set<string>();
|
|---|
| 137 | // See https://github.com/GoogleChrome/workbox/issues/1287
|
|---|
| 138 | if (Array.isArray(config.chunks)) {
|
|---|
| 139 | for (const name of config.chunks) {
|
|---|
| 140 | // See https://github.com/GoogleChrome/workbox/issues/2717
|
|---|
| 141 | const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(
|
|---|
| 142 | compilation,
|
|---|
| 143 | name,
|
|---|
| 144 | );
|
|---|
| 145 | if (assetsInChunkOrGroup) {
|
|---|
| 146 | for (const assetName of assetsInChunkOrGroup) {
|
|---|
| 147 | allowedAssetNames.add(assetName);
|
|---|
| 148 | }
|
|---|
| 149 | } else {
|
|---|
| 150 | compilation.warnings.push(
|
|---|
| 151 | new Error(
|
|---|
| 152 | `The chunk '${name}' was ` +
|
|---|
| 153 | `provided in your Workbox chunks config, but was not found in the ` +
|
|---|
| 154 | `compilation.`,
|
|---|
| 155 | ) as WebpackError,
|
|---|
| 156 | );
|
|---|
| 157 | }
|
|---|
| 158 | }
|
|---|
| 159 | }
|
|---|
| 160 |
|
|---|
| 161 | const deniedAssetNames = new Set();
|
|---|
| 162 | if (Array.isArray(config.excludeChunks)) {
|
|---|
| 163 | for (const name of config.excludeChunks) {
|
|---|
| 164 | // See https://github.com/GoogleChrome/workbox/issues/2717
|
|---|
| 165 | const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(
|
|---|
| 166 | compilation,
|
|---|
| 167 | name,
|
|---|
| 168 | );
|
|---|
| 169 | if (assetsInChunkOrGroup) {
|
|---|
| 170 | for (const assetName of assetsInChunkOrGroup) {
|
|---|
| 171 | deniedAssetNames.add(assetName);
|
|---|
| 172 | }
|
|---|
| 173 | } // Don't warn if the chunk group isn't found.
|
|---|
| 174 | }
|
|---|
| 175 | }
|
|---|
| 176 |
|
|---|
| 177 | for (const asset of assets) {
|
|---|
| 178 | // chunk based filtering is funky because:
|
|---|
| 179 | // - Each asset might belong to one or more chunks.
|
|---|
| 180 | // - If *any* of those chunk names match our config.excludeChunks,
|
|---|
| 181 | // then we skip that asset.
|
|---|
| 182 | // - If the config.chunks is defined *and* there's no match
|
|---|
| 183 | // between at least one of the chunkNames and one entry, then
|
|---|
| 184 | // we skip that assets as well.
|
|---|
| 185 |
|
|---|
| 186 | if (deniedAssetNames.has(asset.name)) {
|
|---|
| 187 | continue;
|
|---|
| 188 | }
|
|---|
| 189 |
|
|---|
| 190 | if (Array.isArray(config.chunks) && !allowedAssetNames.has(asset.name)) {
|
|---|
| 191 | continue;
|
|---|
| 192 | }
|
|---|
| 193 |
|
|---|
| 194 | // Next, check asset-level checks via includes/excludes:
|
|---|
| 195 | const isExcluded = checkConditions(asset, compilation, config.exclude);
|
|---|
| 196 | if (isExcluded) {
|
|---|
| 197 | continue;
|
|---|
| 198 | }
|
|---|
| 199 |
|
|---|
| 200 | // Treat an empty config.includes as an implicit inclusion.
|
|---|
| 201 | const isIncluded =
|
|---|
| 202 | !Array.isArray(config.include) ||
|
|---|
| 203 | checkConditions(asset, compilation, config.include);
|
|---|
| 204 | if (!isIncluded) {
|
|---|
| 205 | continue;
|
|---|
| 206 | }
|
|---|
| 207 |
|
|---|
| 208 | // If we've gotten this far, then add the asset.
|
|---|
| 209 | filteredAssets.add(asset);
|
|---|
| 210 | }
|
|---|
| 211 |
|
|---|
| 212 | return filteredAssets;
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | export async function getManifestEntriesFromCompilation(
|
|---|
| 216 | compilation: Compilation,
|
|---|
| 217 | config: WebpackGenerateSWOptions | WebpackInjectManifestOptions,
|
|---|
| 218 | ): Promise<{size: number; sortedEntries: ManifestEntry[]}> {
|
|---|
| 219 | const filteredAssets = filterAssets(compilation, config);
|
|---|
| 220 |
|
|---|
| 221 | const {publicPath} = compilation.options.output;
|
|---|
| 222 |
|
|---|
| 223 | const fileDetails = Array.from(filteredAssets).map((asset) => {
|
|---|
| 224 | return {
|
|---|
| 225 | file: resolveWebpackURL(publicPath as string, asset.name),
|
|---|
| 226 | hash: getAssetHash(asset),
|
|---|
| 227 | size: asset.source.size() || 0,
|
|---|
| 228 | } as FileDetails;
|
|---|
| 229 | });
|
|---|
| 230 |
|
|---|
| 231 | const {manifestEntries, size, warnings} = await transformManifest({
|
|---|
| 232 | fileDetails,
|
|---|
| 233 | additionalManifestEntries: config.additionalManifestEntries,
|
|---|
| 234 | dontCacheBustURLsMatching: config.dontCacheBustURLsMatching,
|
|---|
| 235 | manifestTransforms: config.manifestTransforms,
|
|---|
| 236 | maximumFileSizeToCacheInBytes: config.maximumFileSizeToCacheInBytes,
|
|---|
| 237 | modifyURLPrefix: config.modifyURLPrefix,
|
|---|
| 238 | transformParam: compilation,
|
|---|
| 239 | });
|
|---|
| 240 |
|
|---|
| 241 | // See https://github.com/GoogleChrome/workbox/issues/2790
|
|---|
| 242 | for (const warning of warnings) {
|
|---|
| 243 | compilation.warnings.push(new Error(warning) as WebpackError);
|
|---|
| 244 | }
|
|---|
| 245 |
|
|---|
| 246 | // Ensure that the entries are properly sorted by URL.
|
|---|
| 247 | const sortedEntries = manifestEntries.sort((a, b) =>
|
|---|
| 248 | a.url === b.url ? 0 : a.url > b.url ? 1 : -1,
|
|---|
| 249 | );
|
|---|
| 250 |
|
|---|
| 251 | return {size, sortedEntries};
|
|---|
| 252 | }
|
|---|