@serwist/webpack-plugin
Advanced tools
Comparing version 9.0.0-preview.0 to 9.0.0-preview.1
import upath from 'upath'; | ||
/** | ||
* @param compilation The webpack compilation. | ||
* @param path The original path value. | ||
* | ||
* @returns If path was not absolute, the returns path as-is. | ||
* Otherwise, returns path relative to the compilation's output path. | ||
* | ||
* @private | ||
*/ const relativeToOutputPath = (compilation, path)=>{ | ||
// See https://github.com/jantimon/html-webpack-plugin/pull/266/files#diff-168726dbe96b3ce427e7fedce31bb0bcR38 | ||
const relativeToOutputPath = (compilation, path)=>{ | ||
if (upath.resolve(path) === upath.normalize(path)) { | ||
return upath.relative(compilation.options.output.path, path); | ||
} | ||
// Otherwise, return swDest as-is. | ||
return path; | ||
@@ -18,0 +8,0 @@ }; |
@@ -5,7 +5,3 @@ import webpack from 'webpack'; | ||
/** | ||
* Compile a file by creating a child of the hooked compiler. | ||
* | ||
* @private | ||
*/ class ChildCompilationPlugin { | ||
class ChildCompilationPlugin { | ||
src; | ||
@@ -12,0 +8,0 @@ dest; |
import { transformManifest, getSourceMapURL, validateWebpackInjectManifestOptions, escapeRegExp, replaceAndUpdateSourceMap } from '@serwist/build'; | ||
import stringify from 'fast-json-stable-stringify'; | ||
import prettyBytes from 'pretty-bytes'; | ||
import upath from 'upath'; | ||
@@ -8,115 +9,3 @@ import webpack from 'webpack'; | ||
const BYTE_UNITS = [ | ||
'B', | ||
'kB', | ||
'MB', | ||
'GB', | ||
'TB', | ||
'PB', | ||
'EB', | ||
'ZB', | ||
'YB' | ||
]; | ||
const BIBYTE_UNITS = [ | ||
'B', | ||
'KiB', | ||
'MiB', | ||
'GiB', | ||
'TiB', | ||
'PiB', | ||
'EiB', | ||
'ZiB', | ||
'YiB' | ||
]; | ||
const BIT_UNITS = [ | ||
'b', | ||
'kbit', | ||
'Mbit', | ||
'Gbit', | ||
'Tbit', | ||
'Pbit', | ||
'Ebit', | ||
'Zbit', | ||
'Ybit' | ||
]; | ||
const BIBIT_UNITS = [ | ||
'b', | ||
'kibit', | ||
'Mibit', | ||
'Gibit', | ||
'Tibit', | ||
'Pibit', | ||
'Eibit', | ||
'Zibit', | ||
'Yibit' | ||
]; | ||
/* | ||
Formats the given number using `Number#toLocaleString`. | ||
- If locale is a string, the value is expected to be a locale-key (for example: `de`). | ||
- If locale is true, the system default locale is used for translation. | ||
- If no value for locale is specified, the number is returned unmodified. | ||
*/ const toLocaleString = (number, locale, options)=>{ | ||
let result = number; | ||
if (typeof locale === 'string' || Array.isArray(locale)) { | ||
result = number.toLocaleString(locale, options); | ||
} else if (locale === true || options !== undefined) { | ||
result = number.toLocaleString(undefined, options); | ||
} | ||
return result; | ||
}; | ||
function prettyBytes(number, options) { | ||
if (!Number.isFinite(number)) { | ||
throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`); | ||
} | ||
options = { | ||
bits: false, | ||
binary: false, | ||
space: true, | ||
...options | ||
}; | ||
const UNITS = options.bits ? options.binary ? BIBIT_UNITS : BIT_UNITS : options.binary ? BIBYTE_UNITS : BYTE_UNITS; | ||
const separator = options.space ? ' ' : ''; | ||
if (options.signed && number === 0) { | ||
return ` 0${separator}${UNITS[0]}`; | ||
} | ||
const isNegative = number < 0; | ||
const prefix = isNegative ? '-' : options.signed ? '+' : ''; | ||
if (isNegative) { | ||
number = -number; | ||
} | ||
let localeOptions; | ||
if (options.minimumFractionDigits !== undefined) { | ||
localeOptions = { | ||
minimumFractionDigits: options.minimumFractionDigits | ||
}; | ||
} | ||
if (options.maximumFractionDigits !== undefined) { | ||
localeOptions = { | ||
maximumFractionDigits: options.maximumFractionDigits, | ||
...localeOptions | ||
}; | ||
} | ||
if (number < 1) { | ||
const numberString = toLocaleString(number, options.locale, localeOptions); | ||
return prefix + numberString + separator + UNITS[0]; | ||
} | ||
const exponent = Math.min(Math.floor(options.binary ? Math.log(number) / Math.log(1024) : Math.log10(number) / 3), UNITS.length - 1); | ||
number /= (options.binary ? 1024 : 1000) ** exponent; | ||
if (!localeOptions) { | ||
number = number.toPrecision(3); | ||
} | ||
const numberString = toLocaleString(Number(number), options.locale, localeOptions); | ||
const unit = UNITS[exponent]; | ||
return prefix + numberString + separator + unit; | ||
} | ||
/** | ||
* @param asset | ||
* @returns The MD5 hash of the asset's source. | ||
* | ||
* @private | ||
*/ const getAssetHash = (asset)=>{ | ||
// If webpack has the asset marked as immutable, then we don't need to | ||
// use an out-of-band revision for it. | ||
// See https://github.com/webpack/webpack/issues/9038 | ||
const getAssetHash = (asset)=>{ | ||
if (asset.info?.immutable) { | ||
@@ -128,21 +17,3 @@ return null; | ||
/* | ||
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. | ||
*/ /** | ||
* Resolves a url in the way that webpack would (with string concatenation) | ||
* | ||
* Use publicPath + filePath instead of url.resolve(publicPath, filePath) see: | ||
* https://webpack.js.org/configuration/output/#output-publicpath | ||
* | ||
* @param publicPath The publicPath value from webpack's compilation. | ||
* @param paths File paths to join | ||
* @returns Joined file path | ||
* @private | ||
*/ const resolveWebpackURL = (publicPath, ...paths)=>{ | ||
// This is a change in webpack v5. | ||
// See https://github.com/jantimon/html-webpack-plugin/pull/1516 | ||
const resolveWebpackURL = (publicPath, ...paths)=>{ | ||
if (publicPath === "auto") { | ||
@@ -157,13 +28,3 @@ return paths.join(""); | ||
/** | ||
* For a given asset, checks whether at least one of the conditions matches. | ||
* | ||
* @param asset The webpack asset in question. This will be passed | ||
* to any functions that are listed as conditions. | ||
* @param compilation The webpack compilation. This will be passed | ||
* to any functions that are listed as conditions. | ||
* @param conditions | ||
* @returns Whether or not at least one condition matches. | ||
* @private | ||
*/ const checkConditions = (asset, compilation, conditions = [])=>{ | ||
const checkConditions = (asset, compilation, conditions = [])=>{ | ||
for (const condition of conditions){ | ||
@@ -175,3 +36,2 @@ if (typeof condition === "function") { | ||
}); | ||
//return compilation !== null; | ||
} | ||
@@ -182,16 +42,5 @@ if (webpack.ModuleFilenameHelpers.matchPart(asset.name, condition)) { | ||
} | ||
// We'll only get here if none of the conditions applied. | ||
return false; | ||
}; | ||
/** | ||
* Returns the names of all the assets in all the chunks in a chunk group, | ||
* if provided a chunk group name. | ||
* Otherwise, if provided a chunk name, return all the assets in that chunk. | ||
* Otherwise, if there isn't a chunk group or chunk with that name, return null. | ||
* | ||
* @param compilation | ||
* @param chunkOrGroup | ||
* @returns | ||
* @private | ||
*/ const getNamesOfAssetsInChunkOrGroup = (compilation, chunkOrGroup)=>{ | ||
const getNamesOfAssetsInChunkOrGroup = (compilation, chunkOrGroup)=>{ | ||
const chunkGroup = compilation.namedChunkGroups?.get(chunkOrGroup); | ||
@@ -209,15 +58,7 @@ if (chunkGroup) { | ||
} | ||
// If we get here, there's no chunkGroup or chunk with that name. | ||
return null; | ||
}; | ||
/** | ||
* Returns the names of all the assets in a chunk. | ||
* | ||
* @param chunk | ||
* @returns | ||
* @private | ||
*/ const getNamesOfAssetsInChunk = (chunk)=>{ | ||
const getNamesOfAssetsInChunk = (chunk)=>{ | ||
const assetNames = []; | ||
assetNames.push(...chunk.files); | ||
// This only appears to be set in webpack v5. | ||
if (chunk.auxiliaryFiles) { | ||
@@ -228,20 +69,8 @@ assetNames.push(...chunk.auxiliaryFiles); | ||
}; | ||
/** | ||
* 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 The webpack compilation. | ||
* @param config The validated configuration, obtained from the plugin. | ||
* @returns The assets that should be included in the manifest, | ||
* based on the criteria provided. | ||
* @private | ||
*/ const filterAssets = (compilation, config)=>{ | ||
const filterAssets = (compilation, config)=>{ | ||
const filteredAssets = new Set(); | ||
const assets = compilation.getAssets(); | ||
const allowedAssetNames = new Set(); | ||
// See https://github.com/GoogleChrome/workbox/issues/1287 | ||
if (Array.isArray(config.chunks)) { | ||
for (const name of config.chunks){ | ||
// See https://github.com/GoogleChrome/workbox/issues/2717 | ||
const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(compilation, name); | ||
@@ -260,3 +89,2 @@ if (assetsInChunkOrGroup) { | ||
for (const name of config.excludeChunks){ | ||
// See https://github.com/GoogleChrome/workbox/issues/2717 | ||
const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(compilation, name); | ||
@@ -267,13 +95,6 @@ if (assetsInChunkOrGroup) { | ||
} | ||
} // Don't warn if the chunk group isn't found. | ||
} | ||
} | ||
} | ||
for (const asset of assets){ | ||
// chunk based filtering is funky because: | ||
// - Each asset might belong to one or more chunks. | ||
// - 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. | ||
if (deniedAssetNames.has(asset.name)) { | ||
@@ -285,3 +106,2 @@ continue; | ||
} | ||
// Next, check asset-level checks via includes/excludes: | ||
const isExcluded = checkConditions(asset, compilation, config.exclude); | ||
@@ -291,3 +111,2 @@ if (isExcluded) { | ||
} | ||
// Treat an empty config.includes as an implicit inclusion. | ||
const isIncluded = !Array.isArray(config.include) || checkConditions(asset, compilation, config.include); | ||
@@ -297,3 +116,2 @@ if (!isIncluded) { | ||
} | ||
// If we've gotten this far, then add the asset. | ||
filteredAssets.add(asset); | ||
@@ -322,7 +140,5 @@ } | ||
}); | ||
// See https://github.com/GoogleChrome/workbox/issues/2790 | ||
for (const warning of warnings){ | ||
compilation.warnings.push(new Error(warning)); | ||
} | ||
// 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); | ||
@@ -335,29 +151,7 @@ return { | ||
/** | ||
* If our bundled swDest file contains a sourcemap, we would invalidate that | ||
* mapping if we just replaced injectionPoint with the stringified manifest. | ||
* Instead, we need to update the swDest contents as well as the sourcemap | ||
* at the same time. | ||
* | ||
* See https://github.com/GoogleChrome/workbox/issues/2235 | ||
* | ||
* @param compilation The current webpack compilation. | ||
* @param swContents The contents of the swSrc file, which may or | ||
* may not include a valid sourcemap comment. | ||
* @param swDest The configured swDest value. | ||
* @returns If the swContents contains a valid sourcemap | ||
* comment pointing to an asset present in the compilation, this will return the | ||
* name of that asset. Otherwise, it will return undefined. | ||
* @private | ||
*/ const getSourcemapAssetName = (compilation, swContents, swDest)=>{ | ||
const getSourcemapAssetName = (compilation, swContents, swDest)=>{ | ||
const url = getSourceMapURL(swContents); | ||
if (url) { | ||
// Translate the relative URL to what the presumed name for the webpack | ||
// asset should be. | ||
// This *might* not be a valid asset if the sourcemap URL that was found | ||
// was added by another module incidentally. | ||
// See https://github.com/GoogleChrome/workbox/issues/2250 | ||
const swAssetDirname = upath.dirname(swDest); | ||
const sourcemapURLAssetName = upath.normalize(upath.join(swAssetDirname, url)); | ||
// Not sure if there's a better way to check for asset existence? | ||
if (compilation.getAsset(sourcemapURLAssetName)) { | ||
@@ -370,54 +164,18 @@ return sourcemapURLAssetName; | ||
// Used to keep track of swDest files written by *any* instance of this plugin. | ||
// See https://github.com/GoogleChrome/workbox/issues/2181 | ||
const _generatedAssetNames = new Set(); | ||
/** | ||
* This class supports compiling a service worker file provided via `swSrc`, | ||
* and injecting into that service worker a list of URLs and revision | ||
* information for precaching based on the webpack asset pipeline. | ||
* | ||
* Use an instance of `InjectManifest` in the | ||
* [`plugins` array](https://webpack.js.org/concepts/plugins/#usage) of a | ||
* webpack config. | ||
* | ||
* In addition to injecting the manifest, this plugin will perform a compilation | ||
* of the `swSrc` file, using the options from the main webpack configuration. | ||
* | ||
* ``` | ||
* // The following lists some common options; see the rest of the documentation | ||
* // for the full set of options and defaults. | ||
* new InjectManifest({ | ||
* exclude: [/.../, '...'], | ||
* maximumFileSizeToCacheInBytes: ..., | ||
* swSrc: '...', | ||
* }); | ||
* ``` | ||
*/ class InjectManifest { | ||
class InjectManifest { | ||
config; | ||
alreadyCalled; | ||
/** | ||
* Creates an instance of InjectManifest. | ||
*/ constructor(config){ | ||
constructor(config){ | ||
this.config = config; | ||
this.alreadyCalled = false; | ||
} | ||
/** | ||
* @param compiler default compiler object passed from webpack | ||
* | ||
* @private | ||
*/ propagateWebpackConfig(compiler) { | ||
propagateWebpackConfig(compiler) { | ||
const parsedSwSrc = upath.parse(this.config.swSrc); | ||
// Because this.config is listed last, properties that are already set | ||
// there take precedence over derived properties from the compiler. | ||
this.config = Object.assign({ | ||
mode: compiler.options.mode, | ||
// Use swSrc with a hardcoded .js extension, in case swSrc is a .ts file. | ||
swDest: `${parsedSwSrc.name}.js` | ||
}, this.config); | ||
} | ||
/** | ||
* `getManifestEntriesFromCompilation` with a few additional checks. | ||
* | ||
* @private | ||
*/ async getManifestEntries(compilation, config) { | ||
async getManifestEntries(compilation, config) { | ||
if (config.disablePrecacheManifest) { | ||
@@ -430,3 +188,2 @@ return { | ||
} | ||
// See https://github.com/GoogleChrome/workbox/issues/1790 | ||
if (this.alreadyCalled) { | ||
@@ -440,11 +197,6 @@ 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.`; | ||
} | ||
// Ensure that we don't precache any of the assets generated by *any* | ||
// instance of this plugin. | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
config.exclude.push(({ asset })=>_generatedAssetNames.has(asset.name)); | ||
const { size, sortedEntries } = await getManifestEntriesFromCompilation(compilation, config); | ||
let manifestString = stringify(sortedEntries); | ||
if (this.config.compileSrc && // See https://github.com/GoogleChrome/workbox/issues/2729 | ||
!(compilation.options?.devtool === "eval-cheap-source-map" && compilation.options.optimization?.minimize)) { | ||
// See https://github.com/GoogleChrome/workbox/issues/2263 | ||
if (this.config.compileSrc && !(compilation.options?.devtool === "eval-cheap-source-map" && compilation.options.optimization?.minimize)) { | ||
manifestString = manifestString.replace(/"/g, `'`); | ||
@@ -458,7 +210,3 @@ } | ||
} | ||
/** | ||
* @param compiler default compiler object passed from webpack | ||
* | ||
* @private | ||
*/ apply(compiler) { | ||
apply(compiler) { | ||
this.propagateWebpackConfig(compiler); | ||
@@ -469,9 +217,5 @@ compiler.hooks.make.tapPromise(this.constructor.name, (compilation)=>this.handleMake(compilation, compiler).catch((error)=>{ | ||
const { PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER } = webpack.Compilation; | ||
// Specifically hook into thisCompilation, as per | ||
// https://github.com/webpack/webpack/issues/11425#issuecomment-690547848 | ||
compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation)=>{ | ||
compilation.hooks.processAssets.tapPromise({ | ||
name: this.constructor.name, | ||
// TODO(jeffposnick): This may need to change eventually. | ||
// See https://github.com/webpack/webpack/issues/11822#issuecomment-726184972 | ||
stage: PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 10 | ||
@@ -483,8 +227,3 @@ }, ()=>this.addAssets(compilation).catch((error)=>{ | ||
} | ||
/** | ||
* @param compilation The webpack compilation. | ||
* @param parentCompiler The webpack parent compiler. | ||
* | ||
* @private | ||
*/ async performChildCompilation(compilation, parentCompiler) { | ||
async performChildCompilation(compilation, parentCompiler) { | ||
const outputOptions = { | ||
@@ -515,18 +254,7 @@ filename: this.config.swDest | ||
} | ||
/** | ||
* @param compilation The webpack compilation. | ||
* @param parentCompiler The webpack parent compiler. | ||
* | ||
* @private | ||
*/ addSrcToAssets(compilation, parentCompiler) { | ||
// eslint-disable-next-line | ||
addSrcToAssets(compilation, parentCompiler) { | ||
const source = parentCompiler.inputFileSystem.readFileSync(this.config.swSrc); | ||
compilation.emitAsset(this.config.swDest, new webpack.sources.RawSource(source)); | ||
} | ||
/** | ||
* @param compilation The webpack compilation. | ||
* @param parentCompiler The webpack parent compiler. | ||
* | ||
* @private | ||
*/ async handleMake(compilation, parentCompiler) { | ||
async handleMake(compilation, parentCompiler) { | ||
this.config = await validateWebpackInjectManifestOptions(this.config); | ||
@@ -539,4 +267,2 @@ this.config.swDest = relativeToOutputPath(compilation, this.config.swDest); | ||
this.addSrcToAssets(compilation, parentCompiler); | ||
// This used to be a fatal error, but just warn at runtime because we | ||
// can't validate it easily. | ||
if (Array.isArray(this.config.webpackCompilationPlugins) && this.config.webpackCompilationPlugins.length > 0) { | ||
@@ -547,10 +273,5 @@ compilation.warnings.push(new Error("compileSrc is false, so the " + "webpackCompilationPlugins option will be ignored.")); | ||
} | ||
/** | ||
* @param compilation The webpack compilation. | ||
* | ||
* @private | ||
*/ async addAssets(compilation) { | ||
async addAssets(compilation) { | ||
const config = Object.assign({}, this.config); | ||
const { size, sortedEntries, manifestString } = await this.getManifestEntries(compilation, config); | ||
// See https://webpack.js.org/contribute/plugin-patterns/#monitoring-the-watch-graph | ||
const absoluteSwSrc = upath.resolve(config.swSrc); | ||
@@ -574,3 +295,2 @@ compilation.fileDependencies.add(absoluteSwSrc); | ||
jsFilename: config.swDest, | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
originalMap: JSON.parse(sourcemapAsset.source.source().toString()), | ||
@@ -584,4 +304,2 @@ originalSource: swAssetString, | ||
} else { | ||
// If there's no sourcemap associated with swDest, a simple string | ||
// replacement will suffice. | ||
compilation.updateAsset(config.swDest, new webpack.sources.RawSource(swAssetString.replace(config.injectionPoint, manifestString))); | ||
@@ -588,0 +306,0 @@ } |
{ | ||
"name": "@serwist/webpack-plugin", | ||
"version": "9.0.0-preview.0", | ||
"version": "9.0.0-preview.1", | ||
"type": "module", | ||
@@ -50,4 +50,5 @@ "description": "A plugin for your Webpack build process, helping you generate a manifest of local files that should be precached.", | ||
"fast-json-stable-stringify": "2.1.0", | ||
"pretty-bytes": "6.1.1", | ||
"upath": "2.0.1", | ||
"@serwist/build": "9.0.0-preview.0" | ||
"@serwist/build": "9.0.0-preview.1" | ||
}, | ||
@@ -57,7 +58,6 @@ "devDependencies": { | ||
"@types/webpack": "5.28.5", | ||
"pretty-bytes": "6.1.1", | ||
"rollup": "4.9.6", | ||
"typescript": "5.4.0-dev.20240203", | ||
"webpack": "5.90.1", | ||
"@serwist/constants": "9.0.0-preview.0" | ||
"@serwist/constants": "9.0.0-preview.1" | ||
}, | ||
@@ -64,0 +64,0 @@ "peerDependencies": { |
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
6
0
54773
6
1156
+ Addedpretty-bytes@6.1.1
+ Added@serwist/background-sync@9.0.0-preview.1(transitive)
+ Added@serwist/broadcast-update@9.0.0-preview.1(transitive)
+ Added@serwist/build@9.0.0-preview.1(transitive)
+ Added@serwist/cacheable-response@9.0.0-preview.1(transitive)
+ Added@serwist/core@9.0.0-preview.1(transitive)
+ Added@serwist/expiration@9.0.0-preview.1(transitive)
+ Added@serwist/google-analytics@9.0.0-preview.1(transitive)
+ Added@serwist/precaching@9.0.0-preview.1(transitive)
+ Added@serwist/routing@9.0.0-preview.1(transitive)
+ Added@serwist/strategies@9.0.0-preview.1(transitive)
+ Addedpretty-bytes@6.1.1(transitive)
- Removed@serwist/background-sync@9.0.0-preview.0(transitive)
- Removed@serwist/broadcast-update@9.0.0-preview.0(transitive)
- Removed@serwist/build@9.0.0-preview.0(transitive)
- Removed@serwist/cacheable-response@9.0.0-preview.0(transitive)
- Removed@serwist/core@9.0.0-preview.0(transitive)
- Removed@serwist/expiration@9.0.0-preview.0(transitive)
- Removed@serwist/google-analytics@9.0.0-preview.0(transitive)
- Removed@serwist/precaching@9.0.0-preview.0(transitive)
- Removed@serwist/routing@9.0.0-preview.0(transitive)
- Removed@serwist/strategies@9.0.0-preview.0(transitive)