Merge pull request #11956 from webpack/feature/asset-processing

allow to process assets that are added during processAssets
This commit is contained in:
Tobias Koppers 2020-11-26 18:49:05 +01:00 committed by GitHub
commit 2aacd90fa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 533 additions and 58 deletions

View File

@ -289,10 +289,122 @@ class Compilation {
*/ */
constructor(compiler) { constructor(compiler) {
const getNormalModuleLoader = () => deprecatedNormalModuleLoaderHook(this); const getNormalModuleLoader = () => deprecatedNormalModuleLoaderHook(this);
/** @type {AsyncSeriesHook<[CompilationAssets]>} */ /** @typedef {{ additionalAssets?: true | Function }} ProcessAssetsAdditionalOptions */
/** @type {AsyncSeriesHook<[CompilationAssets], ProcessAssetsAdditionalOptions>} */
const processAssetsHook = new AsyncSeriesHook(["assets"]); const processAssetsHook = new AsyncSeriesHook(["assets"]);
let savedAssets = new Set();
const popNewAssets = assets => {
let newAssets = undefined;
for (const file of Object.keys(assets)) {
if (savedAssets.has(file)) continue;
if (newAssets === undefined) {
newAssets = Object.create(null);
}
newAssets[file] = assets[file];
savedAssets.add(file);
}
return newAssets;
};
processAssetsHook.intercept({
name: "Compilation",
call: () => {
savedAssets.clear();
},
register: tap => {
const { type, name } = tap;
const { fn, additionalAssets, ...remainingTap } = tap;
const additionalAssetsFn =
additionalAssets === true ? fn : additionalAssets;
let processedAssets = undefined;
switch (type) {
case "sync":
if (additionalAssetsFn) {
this.hooks.processAdditionalAssets.tap(name, assets => {
if (processedAssets === this.assets) additionalAssetsFn(assets);
});
}
return {
...remainingTap,
type: "async",
fn: (assets, callback) => {
try {
fn(assets);
} catch (e) {
return callback(e);
}
processedAssets = this.assets;
const newAssets = popNewAssets(assets);
if (newAssets !== undefined) {
this.hooks.processAdditionalAssets.callAsync(
newAssets,
callback
);
return;
}
callback();
}
};
case "async":
if (additionalAssetsFn) {
this.hooks.processAdditionalAssets.tapAsync(
name,
(assets, callback) => {
if (processedAssets === this.assets)
return additionalAssetsFn(assets, callback);
callback();
}
);
}
return {
...remainingTap,
fn: (assets, callback) => {
fn(assets, err => {
if (err) return callback(err);
processedAssets = this.assets;
const newAssets = popNewAssets(assets);
if (newAssets !== undefined) {
this.hooks.processAdditionalAssets.callAsync(
newAssets,
callback
);
return;
}
callback();
});
}
};
case "promise":
if (additionalAssetsFn) {
this.hooks.processAdditionalAssets.tapPromise(name, assets => {
if (processedAssets === this.assets)
return additionalAssetsFn(assets);
return Promise.resolve();
});
}
return {
...remainingTap,
fn: assets => {
const p = fn(assets);
if (!p || !p.then) return p;
return p.then(() => {
processedAssets = this.assets;
const newAssets = popNewAssets(assets);
if (newAssets !== undefined) {
return this.hooks.processAdditionalAssets.promise(
newAssets
);
}
});
}
};
}
}
});
/** @type {SyncHook<[CompilationAssets]>} */ /** @type {SyncHook<[CompilationAssets]>} */
const afterProcessAssetsHook = new SyncHook(["assets"]); const afterProcessAssetsHook = new SyncHook(["assets"]);
/** /**
* @template T * @template T
* @param {string} name name of the hook * @param {string} name name of the hook
@ -544,6 +656,8 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
processAssets: processAssetsHook, processAssets: processAssetsHook,
afterProcessAssets: afterProcessAssetsHook, afterProcessAssets: afterProcessAssetsHook,
/** @type {AsyncSeriesHook<[CompilationAssets]>} */
processAdditionalAssets: new AsyncSeriesHook(["assets"]),
/** @type {SyncBailHook<[], boolean>} */ /** @type {SyncBailHook<[], boolean>} */
needAdditionalSeal: new SyncBailHook([]), needAdditionalSeal: new SyncBailHook([]),
@ -3439,6 +3553,8 @@ Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE = 100;
/** /**
* Optimize the count of existing assets, e. g. by merging them. * Optimize the count of existing assets, e. g. by merging them.
* Only assets of the same type should be merged.
* For assets of different types see PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE.
*/ */
Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT = 200; Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT = 200;
@ -3453,17 +3569,23 @@ Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY = 300;
Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE = 400; Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE = 400;
/** /**
* Summarize the list of existing assets. * Add development tooling to assets, e. g. by extracting a SourceMap.
* When creating new assets from this they should be fully optimized. */
Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING = 500;
/**
* Optimize the count of existing assets, e. g. by inlining assets of into other assets.
* Only assets of different types should be inlined.
* For assets of the same type see PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT.
*/
Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE = 700;
/**
* Summarize the list of existing assets
* e. g. creating an assets manifest of Service Workers. * e. g. creating an assets manifest of Service Workers.
*/ */
Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE = 1000; Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE = 1000;
/**
* Add development tooling to assets, e. g. by extracting a SourceMap.
*/
Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING = 2000;
/** /**
* Optimize the hashes of the assets, e. g. by generating real hashes of the asset content. * Optimize the hashes of the assets, e. g. by generating real hashes of the asset content.
*/ */

View File

@ -152,9 +152,9 @@ class Compiler {
beforeCompile: new AsyncSeriesHook(["params"]), beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<[CompilationParams]>} */ /** @type {SyncHook<[CompilationParams]>} */
compile: new SyncHook(["params"]), compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<[Compilation], Module>} */ /** @type {AsyncParallelHook<[Compilation]>} */
make: new AsyncParallelHook(["compilation"]), make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncParallelHook<[Compilation], Module>} */ /** @type {AsyncParallelHook<[Compilation]>} */
finishMake: new AsyncSeriesHook(["compilation"]), finishMake: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[Compilation]>} */ /** @type {AsyncSeriesHook<[Compilation]>} */
afterCompile: new AsyncSeriesHook(["compilation"]), afterCompile: new AsyncSeriesHook(["compilation"]),

View File

@ -162,7 +162,8 @@ class SourceMapDevToolPlugin {
compilation.hooks.processAssets.tapAsync( compilation.hooks.processAssets.tapAsync(
{ {
name: "SourceMapDevToolPlugin", name: "SourceMapDevToolPlugin",
stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
additionalAssets: true
}, },
(assets, callback) => { (assets, callback) => {
const chunkGraph = compilation.chunkGraph; const chunkGraph = compilation.chunkGraph;

View File

@ -355,6 +355,9 @@ module.exports = mergeExports(fn, {
get ModuleConcatenationPlugin() { get ModuleConcatenationPlugin() {
return require("./optimize/ModuleConcatenationPlugin"); return require("./optimize/ModuleConcatenationPlugin");
}, },
get RealContentHashPlugin() {
return require("./optimize/RealContentHashPlugin");
},
get RuntimeChunkPlugin() { get RuntimeChunkPlugin() {
return require("./optimize/RuntimeChunkPlugin"); return require("./optimize/RuntimeChunkPlugin");
}, },

View File

@ -5,12 +5,15 @@
"use strict"; "use strict";
const { SyncBailHook } = require("tapable");
const { RawSource, CachedSource, CompatSource } = require("webpack-sources"); const { RawSource, CachedSource, CompatSource } = require("webpack-sources");
const Compilation = require("../Compilation"); const Compilation = require("../Compilation");
const WebpackError = require("../WebpackError"); const WebpackError = require("../WebpackError");
const { compareSelect, compareStrings } = require("../util/comparators"); const { compareSelect, compareStrings } = require("../util/comparators");
const createHash = require("../util/createHash"); const createHash = require("../util/createHash");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
/** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Compiler")} Compiler */
const EMPTY_SET = new Set(); const EMPTY_SET = new Set();
@ -47,7 +50,50 @@ const toCachedSource = source => {
return newSource; return newSource;
}; };
/**
* @typedef {Object} AssetInfoForRealContentHash
* @property {string} name
* @property {AssetInfo} info
* @property {Source} source
* @property {RawSource | undefined} newSource
* @property {RawSource | undefined} newSourceWithoutOwn
* @property {string} content
* @property {Set<string>} ownHashes
* @property {Promise} contentComputePromise
* @property {Promise} contentComputeWithoutOwnPromise
* @property {Set<string>} referencedHashes
* @property {Set<string>} hashes
*/
/**
* @typedef {Object} CompilationHooks
* @property {SyncBailHook<[Buffer[], string], string>} updateHash
*/
/** @type {WeakMap<Compilation, CompilationHooks>} */
const compilationHooksMap = new WeakMap();
class RealContentHashPlugin { class RealContentHashPlugin {
/**
* @param {Compilation} compilation the compilation
* @returns {CompilationHooks} the attached hooks
*/
static getCompilationHooks(compilation) {
if (!(compilation instanceof Compilation)) {
throw new TypeError(
"The 'compilation' argument must be an instance of Compilation"
);
}
let hooks = compilationHooksMap.get(compilation);
if (hooks === undefined) {
hooks = {
updateHash: new SyncBailHook(["content", "oldHash"])
};
compilationHooksMap.set(compilation, hooks);
}
return hooks;
}
constructor({ hashFunction, hashDigest }) { constructor({ hashFunction, hashDigest }) {
this._hashFunction = hashFunction; this._hashFunction = hashFunction;
this._hashDigest = hashDigest; this._hashDigest = hashDigest;
@ -66,6 +112,7 @@ class RealContentHashPlugin {
const cacheGenerate = compilation.getCache( const cacheGenerate = compilation.getCache(
"RealContentHashPlugin|generate" "RealContentHashPlugin|generate"
); );
const hooks = RealContentHashPlugin.getCompilationHooks(compilation);
compilation.hooks.processAssets.tapPromise( compilation.hooks.processAssets.tapPromise(
{ {
name: "RealContentHashPlugin", name: "RealContentHashPlugin",
@ -73,6 +120,7 @@ class RealContentHashPlugin {
}, },
async () => { async () => {
const assets = compilation.getAssets(); const assets = compilation.getAssets();
/** @type {AssetInfoForRealContentHash[]} */
const assetsWithInfo = []; const assetsWithInfo = [];
const hashToAssets = new Map(); const hashToAssets = new Map();
for (const { source, info, name } of assets) { for (const { source, info, name } of assets) {
@ -87,9 +135,13 @@ class RealContentHashPlugin {
source: cachedSource, source: cachedSource,
/** @type {RawSource | undefined} */ /** @type {RawSource | undefined} */
newSource: undefined, newSource: undefined,
/** @type {RawSource | undefined} */
newSourceWithoutOwn: undefined,
content, content,
hasOwnHash: false, /** @type {Set<string>} */
contentComputePromise: false, ownHashes: undefined,
contentComputePromise: undefined,
contentComputeWithoutOwnPromise: undefined,
/** @type {Set<string>} */ /** @type {Set<string>} */
referencedHashes: undefined, referencedHashes: undefined,
hashes hashes
@ -114,6 +166,7 @@ class RealContentHashPlugin {
const { name, source, content, hashes } = asset; const { name, source, content, hashes } = asset;
if (Buffer.isBuffer(content)) { if (Buffer.isBuffer(content)) {
asset.referencedHashes = EMPTY_SET; asset.referencedHashes = EMPTY_SET;
asset.ownHashes = EMPTY_SET;
return; return;
} }
const etag = cacheAnalyse.mergeEtags( const etag = cacheAnalyse.mergeEtags(
@ -122,21 +175,21 @@ class RealContentHashPlugin {
); );
[ [
asset.referencedHashes, asset.referencedHashes,
asset.hasOwnHash asset.ownHashes
] = await cacheAnalyse.providePromise(name, etag, () => { ] = await cacheAnalyse.providePromise(name, etag, () => {
const referencedHashes = new Set(); const referencedHashes = new Set();
let hasOwnHash = false; let ownHashes = new Set();
const inContent = content.match(hashRegExp); const inContent = content.match(hashRegExp);
if (inContent) { if (inContent) {
for (const hash of inContent) { for (const hash of inContent) {
if (hashes.has(hash)) { if (hashes.has(hash)) {
hasOwnHash = true; ownHashes.add(hash);
continue; continue;
} }
referencedHashes.add(hash); referencedHashes.add(hash);
} }
} }
return [referencedHashes, hasOwnHash]; return [referencedHashes, ownHashes];
}); });
}) })
); );
@ -163,7 +216,12 @@ ${referencingAssets
return undefined; return undefined;
} }
const hashes = new Set(); const hashes = new Set();
for (const { referencedHashes } of assets) { for (const { referencedHashes, ownHashes } of assets) {
if (!ownHashes.has(hash)) {
for (const hash of ownHashes) {
hashes.add(hash);
}
}
for (const hash of referencedHashes) { for (const hash of referencedHashes) {
hashes.add(hash); hashes.add(hash);
} }
@ -199,32 +257,57 @@ ${referencingAssets
add(hash, new Set()); add(hash, new Set());
} }
const hashToNewHash = new Map(); const hashToNewHash = new Map();
const computeNewContent = (asset, includeOwn) => { const getEtag = asset =>
cacheGenerate.mergeEtags(
cacheGenerate.getLazyHashedEtag(asset.source),
Array.from(asset.referencedHashes, hash =>
hashToNewHash.get(hash)
).join("|")
);
const computeNewContent = asset => {
if (asset.contentComputePromise) return asset.contentComputePromise; if (asset.contentComputePromise) return asset.contentComputePromise;
return (asset.contentComputePromise = (async () => { return (asset.contentComputePromise = (async () => {
if ( if (
asset.hasOwnHash || asset.ownHashes.size > 0 ||
Array.from(asset.referencedHashes).some( Array.from(asset.referencedHashes).some(
hash => hashToNewHash.get(hash) !== hash hash => hashToNewHash.get(hash) !== hash
) )
) { ) {
const identifier = const identifier = asset.name;
asset.name + const etag = getEtag(asset);
(includeOwn && asset.hasOwnHash ? "|with-own" : "");
const etag = cacheGenerate.mergeEtags(
cacheGenerate.getLazyHashedEtag(asset.source),
Array.from(asset.referencedHashes, hash =>
hashToNewHash.get(hash)
).join("|")
);
asset.newSource = await cacheGenerate.providePromise( asset.newSource = await cacheGenerate.providePromise(
identifier,
etag,
() => {
const newContent = asset.content.replace(hashRegExp, hash =>
hashToNewHash.get(hash)
);
return new RawSource(newContent);
}
);
}
})());
};
const computeNewContentWithoutOwn = asset => {
if (asset.contentComputeWithoutOwnPromise)
return asset.contentComputeWithoutOwnPromise;
return (asset.contentComputeWithoutOwnPromise = (async () => {
if (
asset.ownHashes.size > 0 ||
Array.from(asset.referencedHashes).some(
hash => hashToNewHash.get(hash) !== hash
)
) {
const identifier = asset.name + "|without-own";
const etag = getEtag(asset);
asset.newSourceWithoutOwn = await cacheGenerate.providePromise(
identifier, identifier,
etag, etag,
() => { () => {
const newContent = asset.content.replace( const newContent = asset.content.replace(
hashRegExp, hashRegExp,
hash => { hash => {
if (!includeOwn && asset.hashes.has(hash)) { if (asset.ownHashes.has(hash)) {
return ""; return "";
} }
return hashToNewHash.get(hash); return hashToNewHash.get(hash);
@ -241,25 +324,37 @@ ${referencingAssets
const assets = hashToAssets.get(oldHash); const assets = hashToAssets.get(oldHash);
assets.sort(comparator); assets.sort(comparator);
const hash = createHash(this._hashFunction); const hash = createHash(this._hashFunction);
await Promise.all(assets.map(computeNewContent)); await Promise.all(
for (const asset of assets) { assets.map(asset =>
hash.update( asset.ownHashes.has(oldHash)
asset.newSource ? computeNewContentWithoutOwn(asset)
: computeNewContent(asset)
)
);
const assetsContent = assets.map(asset => {
if (asset.ownHashes.has(oldHash)) {
return asset.newSourceWithoutOwn
? asset.newSourceWithoutOwn.buffer()
: asset.source.buffer();
} else {
return asset.newSource
? asset.newSource.buffer() ? asset.newSource.buffer()
: asset.source.buffer() : asset.source.buffer();
); }
});
let newHash = hooks.updateHash.call(assetsContent, oldHash);
if (!newHash) {
for (const content of assetsContent) {
hash.update(content);
}
const digest = hash.digest(this._hashDigest);
newHash = /** @type {string} */ (digest.slice(0, oldHash.length));
} }
const digest = hash.digest(this._hashDigest);
const newHash = digest.slice(0, oldHash.length);
hashToNewHash.set(oldHash, newHash); hashToNewHash.set(oldHash, newHash);
} }
await Promise.all( await Promise.all(
assetsWithInfo.map(async asset => { assetsWithInfo.map(async asset => {
// recomputed content with it's own hash await computeNewContent(asset);
if (asset.hasOwnHash) {
asset.contentComputePromise = undefined;
}
await computeNewContent(asset, true);
const newName = asset.name.replace(hashRegExp, hash => const newName = asset.name.replace(hashRegExp, hash =>
hashToNewHash.get(hash) hashToNewHash.get(hash)
); );

View File

@ -25,7 +25,7 @@
"neo-async": "^2.6.2", "neo-async": "^2.6.2",
"pkg-dir": "^4.2.0", "pkg-dir": "^4.2.0",
"schema-utils": "^3.0.0", "schema-utils": "^3.0.0",
"tapable": "^2.0.0", "tapable": "^2.1.1",
"terser-webpack-plugin": "^5.0.3", "terser-webpack-plugin": "^5.0.3",
"watchpack": "^2.0.0", "watchpack": "^2.0.0",
"webpack-sources": "^2.1.1" "webpack-sources": "^2.1.1"

View File

@ -0,0 +1,28 @@
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
it("should result in the correct HTML", () => {
const content = fs.readFileSync(
path.resolve(__dirname, "index.html"),
"utf-8"
);
// check minimized
expect(content).toMatch(/<\/script> <script/);
// check inlined js is minimized
expect(content).toMatch(/For license information please see inline-/);
// contains references to normal-[contenthash].js
expect(content).toMatch(/normal-.{20}\.js/);
const [filename] = /normal-.{20}\.js/.exec(content);
const normalJs = fs.readFileSync(path.resolve(__dirname, filename));
const hash = crypto.createHash("sha512");
hash.update(normalJs);
const digest = hash.digest("base64");
// SRI has been updated and matched content
expect(content).toContain(digest);
});

View File

@ -0,0 +1 @@
console.log("inline");

View File

@ -0,0 +1 @@
console.log("normal");

View File

@ -0,0 +1,5 @@
module.exports = {
findBundle: function () {
return "./test.js";
}
};

View File

@ -0,0 +1,189 @@
const {
sources: { RawSource, OriginalSource, ReplaceSource },
Compilation,
util: { createHash },
optimize: { RealContentHashPlugin }
} = require("../../../../");
class HtmlPlugin {
constructor(entrypoints) {
this.entrypoints = entrypoints;
}
apply(compiler) {
compiler.hooks.compilation.tap("html-plugin", compilation => {
compilation.hooks.processAssets.tap(
{
name: "html-plugin",
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
() => {
const publicPath = compilation.outputOptions.publicPath;
const files = [];
for (const name of this.entrypoints) {
for (const file of compilation.entrypoints.get(name).getFiles())
files.push(file);
}
const toScriptTag = (file, extra) => {
const asset = compilation.getAsset(file);
const hash = createHash("sha512");
hash.update(asset.source.source());
const integrity = `sha512-${hash.digest("base64")}`;
compilation.updateAsset(
file,
x => x,
assetInfo => ({
...assetInfo,
contenthash: Array.isArray(assetInfo.contenthash)
? [...new Set([...assetInfo.contenthash, integrity])]
: assetInfo.contenthash
? [assetInfo.contenthash, integrity]
: integrity
})
);
return `<script src="${
publicPath === "auto" ? "" : publicPath
}${file}" integrity="${integrity}"></script>`;
};
compilation.emitAsset(
"index.html",
new OriginalSource(
`<html>
<body>
${files.map(file => ` ${toScriptTag(file)}`).join("\n")}
</body>
</html>`,
"index.html"
)
);
}
);
});
}
}
class HtmlInlinePlugin {
constructor(inline) {
this.inline = inline;
}
apply(compiler) {
compiler.hooks.compilation.tap("html-inline-plugin", compilation => {
compilation.hooks.processAssets.tap(
{
name: "html-inline-plugin",
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
additionalAssets: true
},
assets => {
const publicPath = compilation.outputOptions.publicPath;
for (const name of Object.keys(assets)) {
if (/\.html$/.test(name)) {
const asset = compilation.getAsset(name);
const content = asset.source.source();
const matches = [];
const regExp = /<script\s+src\s*=\s*"([^"]+)"(?:\s+[^"=\s]+(?:\s*=\s*(?:"[^"]*"|[^\s]+))?)*\s*><\/script>/g;
let match = regExp.exec(content);
while (match) {
let url = match[1];
if (url.startsWith(publicPath))
url = url.slice(publicPath.length);
if (this.inline.test(url)) {
const asset = compilation.getAsset(url);
matches.push({
start: match.index,
length: match[0].length,
asset
});
}
match = regExp.exec(content);
}
if (matches.length > 0) {
const newSource = new ReplaceSource(asset.source, name);
for (const { start, length, asset } of matches) {
newSource.replace(
start,
start + length - 1,
`<script>${asset.source.source()}</script>`
);
}
compilation.updateAsset(name, newSource);
}
}
}
}
);
});
}
}
class SriHashSupportPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("sri-hash-support-plugin", compilation => {
RealContentHashPlugin.getCompilationHooks(compilation).updateHash.tap(
"sri-hash-support-plugin",
(input, oldHash) => {
if (/^sha512-.{88}$/.test(oldHash) && input.length === 1) {
const hash = createHash("sha512");
hash.update(input[0]);
return `sha512-${hash.digest("base64")}`;
}
}
);
});
}
}
class HtmlMinimizePlugin {
apply(compiler) {
compiler.hooks.compilation.tap("html-minimize-plugin", compilation => {
compilation.hooks.processAssets.tap(
{
name: "html-minimize-plugin",
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
additionalAssets: true
},
assets => {
for (const name of Object.keys(assets)) {
if (/\.html$/.test(name)) {
compilation.updateAsset(
name,
source => new RawSource(source.source().replace(/\s+/g, " ")),
assetInfo => ({
...assetInfo,
minimized: true
})
);
}
}
}
);
});
}
}
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "production",
entry: {
test: { import: "./index.js", filename: "test.js" },
inline: "./inline.js",
normal: "./normal.js"
},
output: {
filename: "[name]-[contenthash].js"
},
optimization: {
minimize: true,
minimizer: ["...", new HtmlMinimizePlugin()]
},
node: {
__dirname: false,
__filename: false
},
plugins: [
new HtmlPlugin(["inline", "normal"]),
new HtmlInlinePlugin(/inline/),
new SriHashSupportPlugin()
]
};

50
types.d.ts vendored
View File

@ -1229,10 +1229,17 @@ declare class Compilation {
"tap" | "tapAsync" | "tapPromise" | "name" "tap" | "tapAsync" | "tapPromise" | "name"
> & > &
FakeHookMarker; FakeHookMarker;
optimizeAssets: AsyncSeriesHook<[Record<string, Source>]>; optimizeAssets: AsyncSeriesHook<
[Record<string, Source>],
{ additionalAssets?: true | Function }
>;
afterOptimizeAssets: SyncHook<[Record<string, Source>]>; afterOptimizeAssets: SyncHook<[Record<string, Source>]>;
processAssets: AsyncSeriesHook<[Record<string, Source>]>; processAssets: AsyncSeriesHook<
[Record<string, Source>],
{ additionalAssets?: true | Function }
>;
afterProcessAssets: SyncHook<[Record<string, Source>]>; afterProcessAssets: SyncHook<[Record<string, Source>]>;
processAdditionalAssets: AsyncSeriesHook<[Record<string, Source>]>;
needAdditionalSeal: SyncBailHook<[], boolean>; needAdditionalSeal: SyncBailHook<[], boolean>;
afterSeal: AsyncSeriesHook<[]>; afterSeal: AsyncSeriesHook<[]>;
renderManifest: SyncWaterfallHook< renderManifest: SyncWaterfallHook<
@ -1536,6 +1543,8 @@ declare class Compilation {
/** /**
* Optimize the count of existing assets, e. g. by merging them. * Optimize the count of existing assets, e. g. by merging them.
* Only assets of the same type should be merged.
* For assets of different types see PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE.
*/ */
static PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT: number; static PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT: number;
@ -1549,18 +1558,24 @@ declare class Compilation {
*/ */
static PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE: number; static PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE: number;
/**
* Summarize the list of existing assets.
* When creating new assets from this they should be fully optimized.
* e. g. creating an assets manifest of Service Workers.
*/
static PROCESS_ASSETS_STAGE_SUMMARIZE: number;
/** /**
* Add development tooling to assets, e. g. by extracting a SourceMap. * Add development tooling to assets, e. g. by extracting a SourceMap.
*/ */
static PROCESS_ASSETS_STAGE_DEV_TOOLING: number; static PROCESS_ASSETS_STAGE_DEV_TOOLING: number;
/**
* Optimize the count of existing assets, e. g. by inlining assets of into other assets.
* Only assets of different types should be inlined.
* For assets of the same type see PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT.
*/
static PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE: number;
/**
* Summarize the list of existing assets
* e. g. creating an assets manifest of Service Workers.
*/
static PROCESS_ASSETS_STAGE_SUMMARIZE: number;
/** /**
* Optimize the hashes of the assets, e. g. by generating real hashes of the asset content. * Optimize the hashes of the assets, e. g. by generating real hashes of the asset content.
*/ */
@ -1597,6 +1612,9 @@ declare interface CompilationHooksJavascriptModulesPlugin {
chunkHash: SyncHook<[Chunk, Hash, ChunkHashContext]>; chunkHash: SyncHook<[Chunk, Hash, ChunkHashContext]>;
useSourceMap: SyncBailHook<[Chunk, RenderContextObject], boolean>; useSourceMap: SyncBailHook<[Chunk, RenderContextObject], boolean>;
} }
declare interface CompilationHooksRealContentHashPlugin {
updateHash: SyncBailHook<[Buffer[], string], string>;
}
declare interface CompilationParams { declare interface CompilationParams {
normalModuleFactory: NormalModuleFactory; normalModuleFactory: NormalModuleFactory;
contextModuleFactory: ContextModuleFactory; contextModuleFactory: ContextModuleFactory;
@ -5665,7 +5683,7 @@ declare abstract class NormalModuleFactory extends ModuleFactory {
beforeResolve: AsyncSeriesBailHook<[ResolveData], any>; beforeResolve: AsyncSeriesBailHook<[ResolveData], any>;
afterResolve: AsyncSeriesBailHook<[ResolveData], any>; afterResolve: AsyncSeriesBailHook<[ResolveData], any>;
createModule: AsyncSeriesBailHook<[any, ResolveData], any>; createModule: AsyncSeriesBailHook<[any, ResolveData], any>;
module: SyncWaterfallHook<[Module, any, ResolveData]>; module: SyncWaterfallHook<[Module, any, ResolveData], any>;
createParser: HookMap<SyncBailHook<any, any>>; createParser: HookMap<SyncBailHook<any, any>>;
parser: HookMap<SyncHook<any>>; parser: HookMap<SyncHook<any>>;
createGenerator: HookMap<SyncBailHook<any, any>>; createGenerator: HookMap<SyncBailHook<any, any>>;
@ -6888,6 +6906,17 @@ declare class ReadFileCompileWasmPlugin {
*/ */
apply(compiler: Compiler): void; apply(compiler: Compiler): void;
} }
declare class RealContentHashPlugin {
constructor(__0: { hashFunction: any; hashDigest: any });
/**
* Apply the plugin
*/
apply(compiler: Compiler): void;
static getCompilationHooks(
compilation: Compilation
): CompilationHooksRealContentHashPlugin;
}
declare interface RealDependencyLocation { declare interface RealDependencyLocation {
start: SourcePosition; start: SourcePosition;
end?: SourcePosition; end?: SourcePosition;
@ -10220,6 +10249,7 @@ declare namespace exports {
LimitChunkCountPlugin, LimitChunkCountPlugin,
MinChunkSizePlugin, MinChunkSizePlugin,
ModuleConcatenationPlugin, ModuleConcatenationPlugin,
RealContentHashPlugin,
RuntimeChunkPlugin, RuntimeChunkPlugin,
SideEffectsFlagPlugin, SideEffectsFlagPlugin,
SplitChunksPlugin SplitChunksPlugin

View File

@ -6221,10 +6221,10 @@ tapable@^1.0.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tapable@^2.0.0: tapable@^2.0.0, tapable@^2.1.1:
version "2.0.0" version "2.1.1"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.0.0.tgz#a49c3d6a8a2bb606e7db372b82904c970d537a08" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.1.1.tgz#b01cc1902d42a7bb30514e320ce21c456f72fd3f"
integrity sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg== integrity sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ==
temp-dir@^1.0.0: temp-dir@^1.0.0:
version "1.0.0" version "1.0.0"