/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/configCases/process-assets/html-plugin/webpack.config.js
282 строки
7 KB
Alexander Akait
feat(minify): safe HTML minify transforms, driven by the spec grammars (#21586)
02 авг 2026, 23:06
Не верифицирован
02 авг 2026, 23:06
99dd2c7
Код
Авторство
О чём код?
"use strict"; const { sources: { RawSource, OriginalSource, ReplaceSource }, Compilation, html: { syntax: { NodeType, SourceProcessor } }, util: { createHash }, optimize: { RealContentHashPlugin } } = require("../../../../"); /** @typedef {import("../../../../").Compiler} Compiler */ /** @typedef {import("../../../../").Asset} Asset */ /** @typedef {import("../../../../").AssetInfo} AssetInfo */ /** @typedef {import("../../../../").ChunkGroup} Entrypoint */ class VerifyAdditionalAssetsPlugin { /** * @param {number} stage stage */ constructor(stage) { this.stage = stage; } /** * @param {Compiler} compiler compiler */ apply(compiler) { compiler.hooks.compilation.tap( "VerifyAdditionalAssetsPlugin", (compilation) => { const alreadySeenAssets = new Set(); compilation.hooks.processAssets.tap( { name: "VerifyAdditionalAssetsPlugin", stage: this.stage, additionalAssets: true }, (assets) => { for (const asset of Object.keys(assets)) { expect(alreadySeenAssets).not.toContain(asset); alreadySeenAssets.add(asset); } } ); } ); } } class HtmlPlugin { /** * @param {string[]} entrypoints entrypoints */ constructor(entrypoints) { this.entrypoints = entrypoints; } /** * @param {Compiler} compiler compiler */ 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 /** @type {Entrypoint} */ ( compilation.entrypoints.get(name) ).getFiles()) { files.push(file); } } /** * @param {string} file file * @returns {string} content of script tag */ const toScriptTag = (file) => { const asset = /** @type {Asset} */ (compilation.getAsset(file)); const hash = createHash("sha512"); hash.update(asset.source.source()); const integrity = `sha512-${hash.digest("base64")}`; compilation.updateAsset( file, (x) => x, /** * @param {AssetInfo} assetInfo asset info * @returns {AssetInfo} new asset info */ (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 { /** * @param {RegExp} inline inline */ constructor(inline) { this.inline = inline; } /** * @param {Compiler} compiler compiler */ 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 = /** @type {string} */ (compilation.outputOptions.publicPath); for (const name of Object.keys(assets)) { if (/\.html$/.test(name)) { const asset = /** @type {Asset} */ (compilation.getAsset(name)); const content = /** @type {string} */ (asset.source.source()); /** @type {{ start: number, length: number, asset: Asset }[]} */ const matches = []; // Minification runs first (`OPTIMIZE_SIZE`) and rewrites the tag // — quoting, spacing, casing — so the scripts are read out of // webpack's own parser rather than matched with a regex. new SourceProcessor() .use({ [NodeType.Element]: (path) => { if (path.tagName() !== "script") return; let url; for (const attribute of path.attributes()) { if (attribute.name === "src") url = attribute.value; } if (url === undefined) return; if (url.startsWith(publicPath)) { url = url.slice(publicPath.length); } if (!this.inline.test(url)) return; matches.push({ start: path.start(), length: path.end() - path.start(), asset: /** @type {Asset} */ (compilation.getAsset(url)) }); } }) .process(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 { /** * @param {Compiler} compiler compiler */ 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 { /** * @param {Compiler} compiler compiler */ 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( /** @type {string} */ (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 VerifyAdditionalAssetsPlugin( Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL - 1 ), // new VerifyAdditionalAssetsPlugin(Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE), // new VerifyAdditionalAssetsPlugin(Compilation.PROCESS_ASSETS_STAGE_REPORT), new HtmlPlugin(["inline", "normal"]), new HtmlInlinePlugin(/inline/), new SriHashSupportPlugin() ] };