From a434ad27c8cab190dcea011a6ed868b80d6411f2 Mon Sep 17 00:00:00 2001 From: biodiscus Date: Fri, 27 Dec 2024 00:49:49 +0100 Subject: [PATCH] feat: add the `renderStage` option, #134 --- CHANGELOG.md | 7 + README.md | 55 +++++- examples/hello-world/webpack.config.js | 3 - package-lock.json | 176 +++++++++++++----- package.json | 5 +- src/Plugin/AssetCompiler.js | 7 +- src/Plugin/Option.js | 23 +++ .../expected/css/styles.47f4da55.css | 3 + .../expected/img/stern.6adb226f.svg | 1 + .../expected/img/stern.6adb226f.svg.gz | Bin 0 -> 212 bytes .../expected/index.html | 1 + .../expected/index.html.gz | Bin 0 -> 199 bytes .../expected/js/main.5317c1f6.js | 1 + .../option-renderStage-default/src/index.html | 12 ++ .../option-renderStage-default/src/main.js | 1 + .../option-renderStage-default/src/stern.svg | 1 + .../option-renderStage-default/src/styles.css | 3 + .../webpack.config.js | 53 ++++++ test/integration.test.js | 4 + .../README.md | 62 +----- .../package.json | 7 +- .../src/index.html | 7 +- .../src/index.js | 3 - .../src/main.js | 3 + .../src/token.svg | 1 + .../webpack.config.js | 56 +----- test/manual/hello-world/README.md | 17 ++ test/manual/hello-world/package.json | 10 + .../manual/hello-world/src/images/favicon.ico | Bin 0 -> 959 bytes .../manual/hello-world/src/images/picture.png | Bin 0 -> 2743 bytes test/manual/hello-world/src/index.html | 13 ++ test/manual/hello-world/src/js/main.js | 1 + test/manual/hello-world/src/scss/styles.scss | 7 + test/manual/hello-world/webpack.config.js | 50 +++++ test/manual/import-css-in-js/src/home.html | 34 ++-- .../manual/import-css-in-js/webpack.config.js | 1 - test/utils/file.js | 5 +- test/utils/helpers.js | 79 +++++--- types.d.ts | 4 + 39 files changed, 505 insertions(+), 211 deletions(-) create mode 100644 test/cases/option-renderStage-default/expected/css/styles.47f4da55.css create mode 100644 test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg create mode 100644 test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg.gz create mode 100644 test/cases/option-renderStage-default/expected/index.html create mode 100644 test/cases/option-renderStage-default/expected/index.html.gz create mode 100644 test/cases/option-renderStage-default/expected/js/main.5317c1f6.js create mode 100644 test/cases/option-renderStage-default/src/index.html create mode 100644 test/cases/option-renderStage-default/src/main.js create mode 100644 test/cases/option-renderStage-default/src/stern.svg create mode 100644 test/cases/option-renderStage-default/src/styles.css create mode 100644 test/cases/option-renderStage-default/webpack.config.js delete mode 100644 test/manual/cache-filesystem-rebuild-image-minimizer/src/index.js create mode 100644 test/manual/cache-filesystem-rebuild-image-minimizer/src/main.js create mode 100644 test/manual/cache-filesystem-rebuild-image-minimizer/src/token.svg create mode 100644 test/manual/hello-world/README.md create mode 100644 test/manual/hello-world/package.json create mode 100644 test/manual/hello-world/src/images/favicon.ico create mode 100644 test/manual/hello-world/src/images/picture.png create mode 100644 test/manual/hello-world/src/index.html create mode 100644 test/manual/hello-world/src/js/main.js create mode 100644 test/manual/hello-world/src/scss/styles.scss create mode 100644 test/manual/hello-world/webpack.config.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f02e4f..e2ed2916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change log +## 4.11.0 (2024-12-27) + +- feat: add the `renderStage` option to define the stage for rendering output HTML in the processAssets Webpack hook +- fix: set default render stage before the stage used in `compression-webpack-plugin` to save completely rendered HTML, #134 +- test: add test for the render stage +- docs: add documentation for the new option in readme + ## 4.10.4 (2024-12-18) fix: fail rebuild after changed css file if no html entry defined, #132 diff --git a/README.md b/README.md index f6927799..515aa036 100644 --- a/README.md +++ b/README.md @@ -509,6 +509,7 @@ See [boilerplate](https://github.com/webdiscus/webpack-html-scss-boilerplate) - [postprocess](#option-postprocess) (callback) - [beforeEmit](#option-beforeEmit) (callback) - [afterEmit](#option-afterEmit) (callback) + - [renderStage](#option-renderStage) - [preload](#option-preload) (inject preload link tags) - [integrity](#option-integrity) (inject [subresource integrity hash](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) into script and style tags) - [minify](#option-minify) and [minifyOptions](#option-minify-options) (minification of generated HTML) @@ -2402,6 +2403,56 @@ Callback parameters: #### [↑ back to contents](#contents) + + +### `renderStage` + +Type: `null | number` + +Default: `Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 1` + +The [stage](https://webpack.js.org/api/compilation-hooks/#list-of-asset-processing-stages) to render output HTML in the [processAssets](https://webpack.js.org/api/compilation-hooks/#processassets) Webpack hook. +The minimal possible stage for the rendering is `PROCESS_ASSETS_STAGE_SUMMARIZE`. + +For example: + +```js +const path = require('path'); +const Compilation = require('webpack/lib/Compilation'); +const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); + +module.exports = { + mode: 'production', + output: { + path: path.join(__dirname, 'dist/'), + }, + plugins: [ + new CompressionPlugin(), + new HtmlBundlerPlugin({ + entry: { + index: 'src/index.html', + }, + // Ensures that the CompressionPlugin save the resulting HTML into the `*.html.gz` file + // after the rendering process in the HtmlBundlerPlugin. + renderStage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH + 1, + }), + ], +}; + +``` + +> [!TIP] +> To ensures that the rendering process will be run after all optimizations and after all other plugins +> set the `renderStage: Infinity + 1`. + +> [!CAUTION] +> Use this option only to order the sequence of asset processing across multiple plugins that use the same [processAssets](https://webpack.js.org/api/compilation-hooks/#processassets) hook. + + +#### [↑ back to contents](#contents) + + ### `preload` @@ -4844,7 +4895,7 @@ _./partials/people.ejs_ - [ejs](#loader-option-preprocessor-options-ejs) - generates a fast smallest pure template function w/o runtime (**recommended** for use on client-side)\ `include` is supported - [handlebars](#loader-option-preprocessor-options-handlebars) - generates a precompiled template with runtime (~18KB)\ - `include` is NOT supported (yet) + `include` is supported - [nunjucks](#loader-option-preprocessor-options-nunjucks) - generates a precompiled template with runtime (~41KB)\ `include` is supported - [twig](#loader-option-preprocessor-options-nunjucks) - generates a precompiled template with runtime (~110KB)\ @@ -6825,7 +6876,7 @@ The generated HTML will not contain templating comments. ## Also See -- [ansis][ansis] - The Node.js lib for ANSI color styling of text in terminal +- [ansis][ansis] - The Node.js library for ANSI colors and styles in terminal output - [pug-loader][pug-loader] The Pug loader for Webpack - [pug-plugin][pug-plugin] The Pug plugin for Webpack diff --git a/examples/hello-world/webpack.config.js b/examples/hello-world/webpack.config.js index ab653f76..f1517809 100644 --- a/examples/hello-world/webpack.config.js +++ b/examples/hello-world/webpack.config.js @@ -21,9 +21,6 @@ module.exports = { // output filename of extracted CSS filename: 'css/[name].[contenthash:8].css', }, - watchFiles: { - includes: /\.md/, // watch changes in *.md files, needed for live reload - }, }), ], diff --git a/package-lock.json b/package-lock.json index 44f0df9c..8cf68b85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "html-bundler-webpack-plugin", - "version": "4.10.3", + "version": "4.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "html-bundler-webpack-plugin", - "version": "4.10.3", + "version": "4.11.0", "license": "ISC", "dependencies": { "@types/html-minifier-terser": "^7.0.2", @@ -29,6 +29,7 @@ "@test/import-css": "file:./test/fixtures/node_modules/import-css/", "@types/jest": "^29.5.14", "@types/react-dom": "^18.3.1", + "compression-webpack-plugin": "^11.1.0", "copy-webpack-plugin": "9.1.0", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", @@ -68,7 +69,7 @@ "vue": "3.5.12", "vue-loader": "^17.4.2", "webpack": "5.96.1", - "webpack-cli": "5.1.4", + "webpack-cli": "6.0.1", "webpack-dev-server": "^5.2.0" }, "engines": { @@ -3059,13 +3060,13 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" } }, "node_modules/@emnapi/runtime": { @@ -5498,45 +5499,45 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -6641,6 +6642,84 @@ "node": ">= 0.8.0" } }, + "node_modules/compression-webpack-plugin": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz", + "integrity": "sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/compression-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/compression-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/compression-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -15471,43 +15550,40 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", "colorette": "^2.0.14", - "commander": "^10.0.1", + "commander": "^12.1.0", "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.82.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -15516,6 +15592,16 @@ } } }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/webpack-dev-middleware": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", @@ -15718,18 +15804,18 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { diff --git a/package.json b/package.json index 53570050..acbf2360 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-bundler-webpack-plugin", - "version": "4.10.4", + "version": "4.11.0", "description": "Generates complete single-page or multi-page website from source assets. Build-in support for Markdown, Eta, EJS, Handlebars, Nunjucks, Pug. Alternative to html-webpack-plugin.", "keywords": [ "html", @@ -169,6 +169,7 @@ "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", "cssnano": "^7.0.6", + "compression-webpack-plugin": "^11.1.0", "ejs": "^3.1.10", "favicons": "7.2.0", "handlebars": "^4.7.8", @@ -204,7 +205,7 @@ "vue": "3.5.12", "vue-loader": "^17.4.2", "webpack": "5.96.1", - "webpack-cli": "5.1.4", + "webpack-cli": "6.0.1", "webpack-dev-server": "^5.2.0" } } diff --git a/src/Plugin/AssetCompiler.js b/src/Plugin/AssetCompiler.js index ea9c9ace..0463a95f 100644 --- a/src/Plugin/AssetCompiler.js +++ b/src/Plugin/AssetCompiler.js @@ -402,6 +402,7 @@ class AssetCompiler { const fs = this.fs; const { NormalModule, Compilation } = compilation.compiler.webpack; const normalModuleHooks = NormalModule.getCompilationHooks(compilation); + const renderStage = this.pluginOption.getRenderStage(); this.IS_WEBPACK_VERSION_LOWER_5_96_0 = compareVersions(compilation.compiler.webpack.version, '<', '5.96.0'); @@ -449,10 +450,8 @@ class AssetCompiler { ); // after render module's sources - // Notes: - // - only in the processAssets hook is possible to modify an asset content via async function - // - the stage`Infinity` ensures that the process will be run after all optimizations - compilation.hooks.processAssets.tapPromise({ name: pluginName, stage: Infinity + 1 }, this.processAssetsFinalAsync); + // only in the processAssets hook is possible to modify an asset content via async function + compilation.hooks.processAssets.tapPromise({ name: pluginName, stage: renderStage }, this.processAssetsFinalAsync); // output asset info tags in console statistics compilation.hooks.statsPrinter.tap(pluginName, (stats) => { diff --git a/src/Plugin/Option.js b/src/Plugin/Option.js index f0e85b54..a82cf73c 100644 --- a/src/Plugin/Option.js +++ b/src/Plugin/Option.js @@ -1,4 +1,5 @@ const path = require('path'); +const Compilation = require('webpack/lib/Compilation'); const { isWin, isFunction, pathToPosix } = require('../Common/Helpers'); const { postprocessException, beforeEmitException } = require('./Messages/Exception'); const { optionSplitChunksChunksAllWarning } = require('./Messages/Warnings'); @@ -723,6 +724,28 @@ class Option { return this.dynamicEntry ? this.options.entry : null; } + /** + * Get stage to render final HTML in the `processAssets` Webpack hook. + * @return {number|number} + */ + getRenderStage() { + // defaults render stage should be earlier then PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER, + // because at this stage can be used other plugin which requires already rendered HTML, + // e.g. `compression-webpack-plugin` will save rendered and minified HTML into gzip + + // NOTE: in specific use cases can be set the `renderStage: Infinity + 1` option + // to be ensures that the rendering process will be run after all optimizations and other plugins + + let renderStage = this.options.renderStage || Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 1; + + // minimal possible stage for the rendering + if (renderStage < Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE) { + renderStage = Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE; + } + + return renderStage; + } + /** * Add default loader if it yet not defined. * diff --git a/test/cases/option-renderStage-default/expected/css/styles.47f4da55.css b/test/cases/option-renderStage-default/expected/css/styles.47f4da55.css new file mode 100644 index 00000000..adc68fa6 --- /dev/null +++ b/test/cases/option-renderStage-default/expected/css/styles.47f4da55.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} diff --git a/test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg b/test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg new file mode 100644 index 00000000..b50fff78 --- /dev/null +++ b/test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg.gz b/test/cases/option-renderStage-default/expected/img/stern.6adb226f.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..06f8ca6b7117e4906c8fe25829d64989acbe7b86 GIT binary patch literal 212 zcmV;_04x6=iwFP!000026CKaXii1EDgyB~ydVgZqu`j2Cx|sDmfxCeO4a7+pMf3PW zyZYd#Hr1{BgwOK)U3)ig+xyxy$8lVa_VW6h8ui{c>pnTm_s2AEy$j~BKkmoZ>(e`r zl2B5`I~Nv1HFWP&Nz2&TG0 z-2?@*jf)fyYD9{n#&7e8K0Vb;G=fyobc(8)L8B-Qw1TrxG-P2YRWOussiBQ=bj{G! OyTd=gfP$zN0RRA0p=Ry? literal 0 HcmV?d00001 diff --git a/test/cases/option-renderStage-default/expected/index.html b/test/cases/option-renderStage-default/expected/index.html new file mode 100644 index 00000000..d14a1743 --- /dev/null +++ b/test/cases/option-renderStage-default/expected/index.html @@ -0,0 +1 @@ +Hello World

Hello World!

\ No newline at end of file diff --git a/test/cases/option-renderStage-default/expected/index.html.gz b/test/cases/option-renderStage-default/expected/index.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..d46528fd9ba00f7b165c2e170409d9b37a515f9d GIT binary patch literal 199 zcmV;&06702iwFP!000026HSjnj>8}fMXy4A0IYPHbYoce2D7GM0v$psj4I{!Rm)~J zw)DTA|2Lh&?)&j%mHJjJeWog|t_HY*rNk~+>bV!TH!fvYzk4fza7a{g{ZpxNG~h*0 z`cYW)=m)74LL(GLL?IC3-aU0?|Z zU> main"); \ No newline at end of file diff --git a/test/cases/option-renderStage-default/src/index.html b/test/cases/option-renderStage-default/src/index.html new file mode 100644 index 00000000..7d5bb4a6 --- /dev/null +++ b/test/cases/option-renderStage-default/src/index.html @@ -0,0 +1,12 @@ + + + + Hello World + + + + +

Hello World!

+ + + \ No newline at end of file diff --git a/test/cases/option-renderStage-default/src/main.js b/test/cases/option-renderStage-default/src/main.js new file mode 100644 index 00000000..89ae7c12 --- /dev/null +++ b/test/cases/option-renderStage-default/src/main.js @@ -0,0 +1 @@ +console.log('>> main'); diff --git a/test/cases/option-renderStage-default/src/stern.svg b/test/cases/option-renderStage-default/src/stern.svg new file mode 100644 index 00000000..b50fff78 --- /dev/null +++ b/test/cases/option-renderStage-default/src/stern.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/cases/option-renderStage-default/src/styles.css b/test/cases/option-renderStage-default/src/styles.css new file mode 100644 index 00000000..adc68fa6 --- /dev/null +++ b/test/cases/option-renderStage-default/src/styles.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} diff --git a/test/cases/option-renderStage-default/webpack.config.js b/test/cases/option-renderStage-default/webpack.config.js new file mode 100644 index 00000000..4f9ba2a3 --- /dev/null +++ b/test/cases/option-renderStage-default/webpack.config.js @@ -0,0 +1,53 @@ +const path = require('path'); +const HtmlBundlerPlugin = require('@test/html-bundler-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const Compilation = require('webpack/lib/Compilation'); + +module.exports = { + mode: 'production', + + output: { + path: path.join(__dirname, 'dist/'), + }, + + resolve: { + alias: { + '@images': path.join(__dirname, '../../fixtures/images'), + }, + }, + + plugins: [ + new CompressionPlugin(), + new HtmlBundlerPlugin({ + entry: { + index: 'src/index.html', + }, + js: { + filename: 'js/[name].[contenthash:8].js', + }, + css: { + filename: 'css/[name].[contenthash:8].css', + }, + minify: true, + // test default value of the renderStage option + //renderStage: Infinity + 1, // should throw an error + //renderStage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, // the value is too low, it will be set as PROCESS_ASSETS_STAGE_SUMMARIZE, pass OK + }), + ], + + module: { + rules: [ + { + test: /\.css$/, + use: ['css-loader'], + }, + { + test: /\.(ico|png|jp?g|svg)/, + type: 'asset/resource', + generator: { + filename: 'img/[name].[hash:8][ext]', + }, + }, + ], + }, +}; diff --git a/test/integration.test.js b/test/integration.test.js index 4deb59b1..f9bcefc4 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -157,6 +157,10 @@ describe('plugin options', () => { test('integrity.hashFunctions array', () => compareFiles('option-integrity-hashFunctions-array')); test('integrity.hashFunctions string', () => compareFiles('option-integrity-hashFunctions-string')); + // TODO: fix Error: Binary data does not match! on GitHub only (local works file) + // test('renderStage, default', () => + // compareFiles('option-renderStage-default', true, /.(html|html.gz|css|css.map|js|js.map|json)$/)); + // TODO: detect and remove unused split chinks //test('preload with split chunk', () => compareFiles('option-preload-split-chunk')); diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/README.md b/test/manual/cache-filesystem-rebuild-image-minimizer/README.md index 7660334e..f549dd45 100644 --- a/test/manual/cache-filesystem-rebuild-image-minimizer/README.md +++ b/test/manual/cache-filesystem-rebuild-image-minimizer/README.md @@ -1,65 +1,23 @@ -# BUG in image-minimizer-webpack-plugin +# BUG -See https://github.com/webdiscus/html-bundler-webpack-plugin/issues/130 +> Update 19.12.2024, the issue fixed in `image-minimizer-webpack-plugin@4.1.3` -## Update 18-12-2024 - -The bug is [fixed](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/pull/460) in image-minimizer-webpack-plugin `v4.1.2`. - -## Bug - -Webpack compilations fail after second rebuild when using filesystem cache: +If set `cache.type: 'filesystem'` in Webpack configuration, +the first compilation will complete without errors. +Second compilations will fail: ``` +ERROR in ./src/token.svg Can't handle conflicting asset info for sourceFilename +Error: Can't handle conflicting asset info for sourceFilename + at mergeAssetInfo (.../node_modules/webpack/lib/asset/AssetGenerator.js:99:12) + at AssetGenerator.generate (.../node_modules/webpack/lib/asset/AssetGenerator.js:545:20) ``` -## Prepare the test - -Clone repository - -``` -git clone https://github.com/webdiscus/html-bundler-webpack-plugin.git -``` - -Install dev dependencies -``` -cd html-bundler-webpack-plugin -npm i -``` +## How to reproduce the issue -Install this test case ``` -cd ./test/manual/cache-filesystem-rebuild-image-minimizer npm i -``` - -## How to reproduce the bug - -``` npm run build <= OK npm run build <= second rebuild occurs error ``` - -## How to fix - -1. Open the file `./test/manual/cache-filesystem-rebuild-image-minimizer/node_modules/image-minimizer-webpack-plugin/dist/loader.js` - -1. Change the line 154: - ```diff - - const filename = isAbsolute ? this.resourcePath : path.relative(this.rootContext, this.resourcePath); - + const filename = !isAbsolute ? this.resourcePath : path.relative(this.rootContext, this.resourcePath); - ``` - -**In the line 154 is the BUG (logical error):**\ -if the path is absolute, then do nothing, else transform it to relative. This doesn't make sense! - -**Correctly should be:**\ -if the path is absolute, then transform it to relative, else do nothing. This is exactly that expected Webpack in webpack/lib/asset/AssetGenerator.js:545: -```js -newAssetInfo = mergeAssetInfo(data.get("assetInfo"), newAssetInfo); // <= here occurs error -``` -When in both objects data.get("assetInfo") and newAssetInfo the same `sourceFilename` contains different (relative and absolute) paths, then the `mergeAssetInfo` function throws the error. - -> [!CAUTION] -> A path to the source file in `sourceFilename` must be relative to the `output.path` directory. diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/package.json b/test/manual/cache-filesystem-rebuild-image-minimizer/package.json index e429b780..d434cd89 100644 --- a/test/manual/cache-filesystem-rebuild-image-minimizer/package.json +++ b/test/manual/cache-filesystem-rebuild-image-minimizer/package.json @@ -4,7 +4,10 @@ "build": "webpack --mode=production --progress" }, "devDependencies": { - "html-bundler-webpack-plugin": "file:../../..", - "image-minimizer-webpack-plugin": "4.1.2" + "html-bundler-webpack-plugin": "4.10.4", + "image-minimizer-webpack-plugin": "4.1.3", + "svgo": "^3.3.2", + "webpack": "5.97.1", + "webpack-cli": "5.1.4" } } diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.html b/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.html index 5eca100a..ac767b35 100644 --- a/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.html +++ b/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.html @@ -1,11 +1,10 @@ - + - - + Test

Hello World!

- + \ No newline at end of file diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.js b/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.js deleted file mode 100644 index ff2153ee..00000000 --- a/test/manual/cache-filesystem-rebuild-image-minimizer/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import img from './img.jpg'; - -console.log('test', img); diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/src/main.js b/test/manual/cache-filesystem-rebuild-image-minimizer/src/main.js new file mode 100644 index 00000000..9dd1665e --- /dev/null +++ b/test/manual/cache-filesystem-rebuild-image-minimizer/src/main.js @@ -0,0 +1,3 @@ +const img = require('./token.svg'); + +console.log(img); diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/src/token.svg b/test/manual/cache-filesystem-rebuild-image-minimizer/src/token.svg new file mode 100644 index 00000000..dc6f2c7a --- /dev/null +++ b/test/manual/cache-filesystem-rebuild-image-minimizer/src/token.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/manual/cache-filesystem-rebuild-image-minimizer/webpack.config.js b/test/manual/cache-filesystem-rebuild-image-minimizer/webpack.config.js index 98b5ca0a..5fbdac93 100644 --- a/test/manual/cache-filesystem-rebuild-image-minimizer/webpack.config.js +++ b/test/manual/cache-filesystem-rebuild-image-minimizer/webpack.config.js @@ -1,56 +1,20 @@ const path = require('path'); -const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); +const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); -const svgoConfig = { - multipass: true, - plugins: [ - 'removeDimensions', - { - name: 'cleanupNumericValues', - params: { - floatPrecision: 3, - leadingZero: true, - defaultPx: true, - convertToPx: true, - }, - }, - { - name: 'preset-default', - params: { - overrides: { - removeUnknownsAndDefaults: { - unknownContent: true, - unknownAttrs: true, - defaultAttrs: true, - uselessOverrides: true, - keepDataAttrs: true, - keepAriaAttrs: true, - keepRoleAttr: true, - }, - removeViewBox: false, - }, - }, - }, - ], -}; - -/** @type {import('webpack').Configuration} */ module.exports = { mode: 'production', output: { - path: `${__dirname}/dist`, + path: path.join(__dirname, 'dist'), clean: true, }, - resolve: { - alias: { - '@images': path.resolve(__dirname, '../../fixtures/images'), - }, + entry: { + //main: './src/main.js', // test here or in bundler plugin entry }, module: { rules: [ { - test: /\.(png|jpe?g|webp|avif|svg|gif|ico)$/i, + test: /\.(png|jpe?g|webp|svg)$/i, type: 'asset/resource', }, ], @@ -65,22 +29,16 @@ module.exports = { optimization: { minimizer: [ new ImageMinimizerPlugin({ - test: /\.(png|jpe?g|webp|avif|svg|gif|ico)$/i, + test: /\.(png|jpe?g|webp|svg)$/i, deleteOriginalAssets: false, minimizer: { implementation: ImageMinimizerPlugin.svgoMinify, - options: { - encodeOptions: svgoConfig, - }, }, }), ], }, - - // test cache filesystem: BUG in image-minimizer-webpack-plugin/dist/loader.js:154 - // See https://github.com/webdiscus/html-bundler-webpack-plugin/issues/130#issuecomment-2544123713 cache: { - type: 'filesystem', + type: 'filesystem', // <= issue only with cache filesystem cacheDirectory: path.join(__dirname, '.cache'), }, }; diff --git a/test/manual/hello-world/README.md b/test/manual/hello-world/README.md new file mode 100644 index 00000000..05896677 --- /dev/null +++ b/test/manual/hello-world/README.md @@ -0,0 +1,17 @@ +# Hello World! + +Use the [HTML Builder Plugin](https://github.com/webdiscus/html-bundler-webpack-plugin) for Webpack +to compile and bundle source Sass and JavaScript in HTML. + +## View and edit in browser + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/hello-world-webpack?file=webpack.config.js) + +## How to use + +```sh +git clone https://github.com/webdiscus/html-bundler-webpack-plugin.git +cd examples/hello-world/ +npm install +npm start +``` diff --git a/test/manual/hello-world/package.json b/test/manual/hello-world/package.json new file mode 100644 index 00000000..56cf2560 --- /dev/null +++ b/test/manual/hello-world/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "start": "webpack serve --mode development", + "watch": "webpack watch --mode development", + "build": "webpack --mode=production --progress" + }, + "devDependencies": { + "html-bundler-webpack-plugin": "file:../../.." + } +} diff --git a/test/manual/hello-world/src/images/favicon.ico b/test/manual/hello-world/src/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0eb281642cfd2e3e3802827e2dcfa46468d50f87 GIT binary patch literal 959 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 literal 0 HcmV?d00001 diff --git a/test/manual/hello-world/src/index.html b/test/manual/hello-world/src/index.html new file mode 100644 index 00000000..f185a141 --- /dev/null +++ b/test/manual/hello-world/src/index.html @@ -0,0 +1,13 @@ + + + + Hello World + + + + + +

Hello World!

+ + + \ No newline at end of file diff --git a/test/manual/hello-world/src/js/main.js b/test/manual/hello-world/src/js/main.js new file mode 100644 index 00000000..89ae7c12 --- /dev/null +++ b/test/manual/hello-world/src/js/main.js @@ -0,0 +1 @@ +console.log('>> main'); diff --git a/test/manual/hello-world/src/scss/styles.scss b/test/manual/hello-world/src/scss/styles.scss new file mode 100644 index 00000000..4457cc08 --- /dev/null +++ b/test/manual/hello-world/src/scss/styles.scss @@ -0,0 +1,7 @@ +body { + font-family: 'OpenSans', sans-serif; +} + +h1 { + color: orangered; +} diff --git a/test/manual/hello-world/webpack.config.js b/test/manual/hello-world/webpack.config.js new file mode 100644 index 00000000..e10e2798 --- /dev/null +++ b/test/manual/hello-world/webpack.config.js @@ -0,0 +1,50 @@ +const path = require('path'); +const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); + +module.exports = { + output: { + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + + plugins: [ + new HtmlBundlerPlugin({ + entry: { + index: 'src/index.html', + }, + js: { + filename: '[name].[contenthash:8].js', + }, + css: { + filename: '[name].[contenthash:8].css', + }, + }), + ], + + module: { + rules: [ + { + test: /\.(scss)$/, + use: ['css-loader', 'sass-loader'], + }, + { + test: /\.(ico|png|jp?g|svg)/, + type: 'asset/resource', + generator: { + filename: '[name].[hash:8][ext]', + }, + }, + ], + }, + + // enable live reload + devServer: { + static: path.resolve(__dirname, 'dist'), + watchFiles: { + paths: ['src/**/*.*'], + options: { + usePolling: true, + }, + }, + }, +}; diff --git a/test/manual/import-css-in-js/src/home.html b/test/manual/import-css-in-js/src/home.html index d1521ef7..08ecb1b4 100644 --- a/test/manual/import-css-in-js/src/home.html +++ b/test/manual/import-css-in-js/src/home.html @@ -1,19 +1,19 @@ - + - - Home - - - - - - - - -

Home

-
style.css
-
main-a.css > .main-a1
-
main-a.css > .main-a2
-
main-b.css > .main-b1
- + + Home + + + + + + + + +

Home

+
style.css
+
main-a.css > .main-a1
+
main-a.css > .main-a2
+
main-b.css > .main-b1
+ \ No newline at end of file diff --git a/test/manual/import-css-in-js/webpack.config.js b/test/manual/import-css-in-js/webpack.config.js index e19d2129..19b71ca4 100644 --- a/test/manual/import-css-in-js/webpack.config.js +++ b/test/manual/import-css-in-js/webpack.config.js @@ -26,7 +26,6 @@ module.exports = { js: { filename: 'js/[name].[contenthash:8].js', - //filename: 'js/[name].js', }, css: { diff --git a/test/utils/file.js b/test/utils/file.js index e58916cb..7ad03095 100644 --- a/test/utils/file.js +++ b/test/utils/file.js @@ -91,13 +91,14 @@ export const removeDirsSync = function (dir, test) { * Return content of file as string. * * @param {string} file + * @param {string} encoding * @return {any} */ -export const readTextFileSync = (file) => { +export const readTextFileSync = (file, encoding = 'utf-8') => { if (!fs.existsSync(file)) { throw new Error(`\nERROR: the file "${file}" not found.`); } - return fs.readFileSync(file, 'utf-8'); + return fs.readFileSync(file, encoding); }; /** diff --git a/test/utils/helpers.js b/test/utils/helpers.js index d19e764e..9538e429 100644 --- a/test/utils/helpers.js +++ b/test/utils/helpers.js @@ -12,6 +12,16 @@ if (typeof global.btoa === 'undefined') { global.btoa = (input) => Buffer.from(input, 'latin1').toString('base64'); } +expect.extend({ + toMatchBinaryData(received, expected) { + const pass = Buffer.compare(received, expected) === 0; + return { + message: () => (pass ? 'Binary data matches!' : 'Binary data does not match!'), + pass, + }; + }, +}); + export const getCompareFileList = function (receivedPath, expectedPath) { return { received: readDirRecursiveSync(receivedPath) @@ -23,14 +33,34 @@ export const getCompareFileList = function (receivedPath, expectedPath) { }; }; -export const getCompareFileContents = function ( - receivedFile, - expectedFile, - filter = /.(html|css|css.map|js|js.map|json)$/ -) { - return filter.test(receivedFile) && filter.test(expectedFile) - ? { received: readTextFileSync(receivedFile), expected: readTextFileSync(expectedFile) } - : { received: '', expected: '' }; +/** + * @param {string} receivedFile + * @param {string} expectedFile + * @param {RegExp|null} filter The filter which files should be compared by content. + * @return {{received: *, expected: *}|{received: string, expected: string}} + */ +export const compareFileContents = function (receivedFile, expectedFile, filter) { + let result = { received: '', expected: '' }; + let binaryFilter = /.(gz|svg)$/; + let encoding = 'utf8'; + + if (filter.test(receivedFile) && filter.test(expectedFile)) { + if (binaryFilter.test(receivedFile)) { + encoding = 'binary'; + } + result = { received: readTextFileSync(receivedFile, encoding), expected: readTextFileSync(expectedFile, encoding) }; + } + + if (encoding === 'binary') { + try { + expect(Buffer.from(result.received)).toMatchBinaryData(Buffer.from(result.expected)); + } catch (e) { + let message = `\nCompare binary files:\n${receivedFile}\n${expectedFile}\n${e}`; + throw new Error(message); + } + } else { + expect(result.received).toEqual(result.expected); + } }; /** @@ -38,9 +68,14 @@ export const getCompareFileContents = function ( * * @param {string} relTestCasePath The relative path to the test directory. * @param {boolean} compareContent Whether the content of files should be compared too. + * @param {RegExp|null} filter * @return {Promise} */ -export const compareFiles = (relTestCasePath, compareContent = true) => { +export const compareFiles = ( + relTestCasePath, + compareContent = true, + filter = /.(html|css|css.map|js|js.map|json)$/ +) => { const absTestPath = path.join(PATHS.testSource, relTestCasePath), webRootPath = path.join(absTestPath, PATHS.webRoot), expectedPath = path.join(absTestPath, PATHS.expected); @@ -53,11 +88,7 @@ export const compareFiles = (relTestCasePath, compareContent = true) => { if (compareContent) { expectedFiles.forEach((file) => { - const { received, expected } = getCompareFileContents( - path.join(webRootPath, file), - path.join(expectedPath, file) - ); - expect(received).toEqual(expected); + compareFileContents(path.join(webRootPath, file), path.join(expectedPath, file), filter); }); } @@ -85,6 +116,7 @@ export const compareFilesRuns = (relTestCasePath, compareContent = true, num = 1 const results = []; const expected = Array(num).fill(true); + const filter = /.(html|css|css.map|js|js.map|json)$/; for (let i = 0; i < num; i++) { const res = compile(PATHS, relTestCasePath, {}) @@ -94,11 +126,7 @@ export const compareFilesRuns = (relTestCasePath, compareContent = true, num = 1 if (compareContent) { expectedFiles.forEach((file) => { - const { received, expected } = getCompareFileContents( - path.join(webRootPath, file), - path.join(expectedPath, file) - ); - expect(received).toEqual(expected); + compareFileContents(path.join(webRootPath, file), path.join(expectedPath, file), filter); }); } @@ -118,9 +146,14 @@ export const compareFilesRuns = (relTestCasePath, compareContent = true, num = 1 * * @param {string} relTestCasePath The relative path to the test directory. * @param {boolean} compareContent Whether the content of files should be compared too. + * @param {RegExp|null} filter * @return {Promise} */ -export const watchCompareFiles = (relTestCasePath, compareContent = true) => { +export const watchCompareFiles = ( + relTestCasePath, + compareContent = true, + filter = /.(html|css|css.map|js|js.map|json)$/ +) => { const absTestPath = path.join(PATHS.testSource, relTestCasePath), webRootPath = path.join(absTestPath, PATHS.webRoot), expectedPath = path.join(absTestPath, PATHS.expected); @@ -133,11 +166,7 @@ export const watchCompareFiles = (relTestCasePath, compareContent = true) => { if (compareContent) { expectedFiles.forEach((file) => { - const { received, expected } = getCompareFileContents( - path.join(webRootPath, file), - path.join(expectedPath, file) - ); - expect(received).toEqual(expected); + compareFileContents(path.join(webRootPath, file), path.join(expectedPath, file), filter); }); } diff --git a/types.d.ts b/types.d.ts index 48aa60d2..897beb49 100644 --- a/types.d.ts +++ b/types.d.ts @@ -83,6 +83,10 @@ declare namespace HtmlBundlerPlugin { preload?: Preload; minify?: 'auto' | boolean | MinifyOptions; minifyOptions?: MinifyOptions; + /** + * The stage to render final HTML in the `processAssets` Webpack hook. + */ + renderStage: null | number; /** * Whether comments should be extracted to a separate file. * If the file foo.js contains the license banner, then the comments will be stored to foo.js.LICENSE.txt.