├── test ├── fixtures │ ├── async.js │ ├── empty.txt │ ├── number.js │ ├── empty.js │ ├── icon.png │ ├── sw.js │ ├── entry.js │ └── icon.svg ├── helpers │ ├── getErrors.js │ ├── getWarnings.js │ ├── compile.js │ ├── CopyPluginWithAssetInfo.js │ ├── execute.js │ ├── readAsset.js │ ├── normalizeErrors.js │ ├── ModifyExistingAsset.js │ ├── loader-with-child-compilation.js │ ├── readAssets.js │ ├── EmitNewAsset.js │ ├── index.js │ ├── getAssetsNameAndSize.js │ └── getCompiler.js ├── minRatio-option.test.js ├── include-option.test.js ├── exclude-option.test.js ├── algorithm.test.js ├── threshold-option.test.js ├── test-option.test.js ├── compressionOptions-option.test.js ├── validate-options.test.js ├── __snapshots__ │ ├── minRatio-option.test.js.snap │ ├── exclude-option.test.js.snap │ ├── include-option.test.js.snap │ ├── threshold-option.test.js.snap │ ├── test-option.test.js.snap │ ├── algorithm.test.js.snap │ ├── deleteOriginalAssets.test.js.snap │ ├── filename-option.test.js.snap │ ├── compressionOptions-option.test.js.snap │ └── validate-options.test.js.snap ├── deleteOriginalAssets.test.js ├── filename-option.test.js └── CompressionPlugin.test.js ├── .husky ├── pre-commit └── commit-msg ├── jest.config.js ├── .gitattributes ├── .prettierignore ├── lint-staged.config.js ├── commitlint.config.js ├── eslint.config.mjs ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── babel.config.js ├── .github └── workflows │ ├── dependency-review.yml │ └── nodejs.yml ├── .cspell.json ├── LICENSE ├── package.json ├── src ├── options.json └── index.js ├── types └── index.d.ts ├── README.md └── CHANGELOG.md /test/fixtures/async.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | lint-staged 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | commitlint --edit $1 2 | -------------------------------------------------------------------------------- /test/fixtures/number.js: -------------------------------------------------------------------------------- 1 | export default 100; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "node", 3 | }; 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json -diff 2 | * text=auto 3 | bin/* eol=lf 4 | yarn.lock -diff 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /test/fixtures 5 | /test/outputs 6 | CHANGELOG.md 7 | -------------------------------------------------------------------------------- /test/fixtures/empty.js: -------------------------------------------------------------------------------- 1 | import empty from './empty.txt'; 2 | 3 | const foo = 'bar'; 4 | 5 | export { empty, foo } 6 | -------------------------------------------------------------------------------- /test/fixtures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webpack/compression-webpack-plugin/main/test/fixtures/icon.png -------------------------------------------------------------------------------- /test/fixtures/sw.js: -------------------------------------------------------------------------------- 1 | import { precacheAndRoute } from 'workbox-precaching/precacheAndRoute'; 2 | 3 | precacheAndRoute(self.__WB_MANIFEST); -------------------------------------------------------------------------------- /test/helpers/getErrors.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats) => normalizeErrors(stats.compilation.errors).sort(); 4 | -------------------------------------------------------------------------------- /test/helpers/getWarnings.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats) => normalizeErrors(stats.compilation.warnings).sort(); 4 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*": [ 3 | "prettier --cache --write --ignore-unknown", 4 | "cspell --cache --no-must-find-files", 5 | ], 6 | "*.js": ["eslint --cache --fix"], 7 | }; 8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "header-max-length": [0], 5 | "body-max-line-length": [0], 6 | "footer-max-line-length": [0], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import configs from "eslint-config-webpack/configs.js"; 3 | 4 | export default defineConfig([ 5 | { 6 | extends: [configs["recommended-dirty"]], 7 | }, 8 | ]); 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /test/helpers/compile.js: -------------------------------------------------------------------------------- 1 | export default (compiler) => 2 | new Promise((resolve, reject) => { 3 | compiler.run((error, stats) => { 4 | if (error) { 5 | return reject(error); 6 | } 7 | 8 | return resolve(stats); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | .eslintcache 5 | .cspellcache 6 | /coverage 7 | /dist 8 | /local 9 | /reports 10 | /node_modules 11 | /test/outputs 12 | .DS_Store 13 | Thumbs.db 14 | .idea 15 | .vscode 16 | *.sublime-project 17 | *.sublime-workspace 18 | *.iml 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "strict": true, 8 | "types": ["node"], 9 | "resolveJsonModule": true, 10 | "allowSyntheticDefaultImports": true 11 | }, 12 | "include": ["./src/**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const MIN_BABEL_VERSION = 7; 2 | 3 | module.exports = (api) => { 4 | api.assertVersion(MIN_BABEL_VERSION); 5 | api.cache(true); 6 | 7 | return { 8 | presets: [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | targets: { 13 | node: "18.12.0", 14 | }, 15 | }, 16 | ], 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: "Dependency Review" 2 | on: [pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | dependency-review: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "Checkout Repository" 12 | uses: actions/checkout@v5 13 | - name: "Dependency Review" 14 | uses: actions/dependency-review-action@v4 15 | -------------------------------------------------------------------------------- /test/fixtures/entry.js: -------------------------------------------------------------------------------- 1 | import imagePNG from './icon.png'; 2 | import imageSVG from './icon.svg'; 3 | import number from './number'; 4 | 5 | import(/* webpackChunkName: 'async' */ './async.js').then((result) => console.log(result)); 6 | 7 | const a = 2 + 2; 8 | 9 | module.exports = function Foo() { 10 | const b = 2 + 2; 11 | 12 | console.log(a); 13 | console.log(b); 14 | console.log(imagePNG); 15 | console.log(imageSVG); 16 | console.log(number); 17 | }; 18 | -------------------------------------------------------------------------------- /test/helpers/CopyPluginWithAssetInfo.js: -------------------------------------------------------------------------------- 1 | export default class CopyPluginWithAssetInfo { 2 | apply(compiler) { 3 | const plugin = { name: this.constructor.name }; 4 | const { RawSource } = compiler.webpack.sources; 5 | 6 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => { 7 | compilation.hooks.additionalAssets.tap(plugin, () => { 8 | compilation.emitAsset("copied.js", new RawSource("Text".repeat(100)), { 9 | copied: true, 10 | }); 11 | }); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/helpers/execute.js: -------------------------------------------------------------------------------- 1 | import Module from "node:module"; 2 | import path from "node:path"; 3 | 4 | const parentModule = module; 5 | 6 | export default (code) => { 7 | const resource = "test.js"; 8 | const module = new Module(resource, parentModule); 9 | 10 | module.paths = Module._nodeModulePaths( 11 | path.resolve(__dirname, "../fixtures"), 12 | ); 13 | module.filename = resource; 14 | 15 | module._compile( 16 | `let __export__;${code};module.exports = __export__;`, 17 | resource, 18 | ); 19 | 20 | return module.exports; 21 | }; 22 | -------------------------------------------------------------------------------- /test/helpers/readAsset.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | export default (asset, compiler, stats) => { 4 | const usedFs = compiler.outputFileSystem; 5 | const outputPath = stats.compilation.outputOptions.path; 6 | 7 | let data = ""; 8 | let targetFile = asset; 9 | 10 | const queryStringIdx = targetFile.indexOf("?"); 11 | 12 | if (queryStringIdx >= 0) { 13 | targetFile = targetFile.slice(0, queryStringIdx); 14 | } 15 | 16 | try { 17 | data = usedFs.readFileSync(path.join(outputPath, targetFile)); 18 | } catch (error) { 19 | data = error.toString(); 20 | } 21 | 22 | return data; 23 | }; 24 | -------------------------------------------------------------------------------- /test/helpers/normalizeErrors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} str string 3 | * @returns {string} normalized string 4 | */ 5 | function removeCWD(str) { 6 | const isWin = process.platform === "win32"; 7 | let cwd = process.cwd(); 8 | 9 | if (isWin) { 10 | str = str.replaceAll("\\", "/"); 11 | 12 | cwd = cwd.replaceAll("\\", "/"); 13 | } 14 | 15 | return str.replaceAll(new RegExp(cwd, "g"), ""); 16 | } 17 | 18 | /** 19 | * @param {Error[]} errors errors 20 | * @returns {string[]} normalized errors 21 | */ 22 | export default (errors) => 23 | errors.map((error) => 24 | removeCWD(error.toString().split("\n").slice(0, 2).join("\n")), 25 | ); 26 | -------------------------------------------------------------------------------- /test/fixtures/icon.svg: -------------------------------------------------------------------------------- 1 | icon -------------------------------------------------------------------------------- /test/helpers/ModifyExistingAsset.js: -------------------------------------------------------------------------------- 1 | export default class ExistingCommentsFile { 2 | constructor(options = {}) { 3 | this.options = options; 4 | } 5 | 6 | apply(compiler) { 7 | const plugin = { name: this.constructor.name }; 8 | const { ConcatSource, RawSource } = compiler.webpack.sources; 9 | 10 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => { 11 | compilation.hooks.additionalAssets.tap(plugin, () => { 12 | compilation.assets[this.options.name] = new ConcatSource( 13 | new RawSource(this.options.content), 14 | compilation.assets[this.options.name], 15 | ); 16 | }); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/helpers/loader-with-child-compilation.js: -------------------------------------------------------------------------------- 1 | import CopyPluginWithAssetInfo from "./CopyPluginWithAssetInfo"; 2 | 3 | /** @typedef {import("../../../../types").LoaderDefinition} LoaderDefinition */ 4 | 5 | /** 6 | * @type {LoaderDefinition} 7 | */ 8 | export default function loader() { 9 | const callback = this.async(); 10 | 11 | const childCompiler = this._compilation.createChildCompiler( 12 | "preloader", 13 | this.options, 14 | ); 15 | 16 | new CopyPluginWithAssetInfo().apply(childCompiler); 17 | 18 | childCompiler.runAsChild((error) => { 19 | if (error) { 20 | return callback(error); 21 | } 22 | 23 | return callback(null, "export default 1"); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/helpers/readAssets.js: -------------------------------------------------------------------------------- 1 | import readAsset from "./readAsset"; 2 | 3 | /** @typedef {import("webpack").Asset} Asset */ 4 | /** @typedef {import("webpack").Compiler} Compiler */ 5 | /** @typedef {import("webpack").Stats} Stats */ 6 | /** @typedef {import("webpack").MultiStats} MultiStats */ 7 | 8 | /** 9 | * @param {Compiler} compiler compiler 10 | * @param {Stats | MultiStats} stats stats 11 | * @returns {Record} assets 12 | */ 13 | export default function readAssets(compiler, stats) { 14 | const assets = {}; 15 | 16 | for (const asset of Object.keys(stats.compilation.assets)) { 17 | assets[asset] = readAsset(asset, compiler, stats); 18 | } 19 | 20 | return assets; 21 | } 22 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en,en-gb", 4 | "words": [ 5 | "chunkhash", 6 | "commitlint", 7 | "pathdata", 8 | "sourcemaps", 9 | "Zopfli", 10 | "zopfli", 11 | "numiterations", 12 | "Brotli", 13 | "Brotli’s", 14 | "BROTLI", 15 | "brotli", 16 | "Zstandard", 17 | "Zstandard’s", 18 | "zstd", 19 | "Koppers", 20 | "sokra", 21 | "precache", 22 | "precaching", 23 | "ungzipped", 24 | "Ungzipped", 25 | "memfs" 26 | ], 27 | 28 | "ignorePaths": [ 29 | "CHANGELOG.md", 30 | "package.json", 31 | "dist/**", 32 | "**/__snapshots__/**", 33 | "package-lock.json", 34 | "node_modules", 35 | "coverage", 36 | "*.log" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/helpers/EmitNewAsset.js: -------------------------------------------------------------------------------- 1 | export default class EmitNewAsset { 2 | constructor(options = {}) { 3 | this.options = options; 4 | } 5 | 6 | apply(compiler) { 7 | const pluginName = this.constructor.name; 8 | 9 | const { RawSource } = compiler.webpack.sources; 10 | 11 | compiler.hooks.compilation.tap(pluginName, (compilation) => { 12 | compilation.hooks.processAssets.tap( 13 | { 14 | name: pluginName, 15 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, 16 | }, 17 | () => { 18 | compilation.emitAsset( 19 | this.options.name, 20 | new RawSource('console.log("content");'), 21 | ); 22 | }, 23 | ); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as getAssetsNameAndSize } from "./getAssetsNameAndSize"; 2 | export { default as CopyPluginWithAssetInfo } from "./CopyPluginWithAssetInfo"; 3 | export { default as execute } from "./execute"; 4 | export { default as normalizeErrors } from "./normalizeErrors"; 5 | export { default as readAsset } from "./readAsset"; 6 | export { default as EmitNewAsset } from "./EmitNewAsset"; 7 | export { default as compile } from "./compile"; 8 | export { default as ModifyExistingAsset } from "./ModifyExistingAsset"; 9 | export { default as getErrors } from "./getErrors"; 10 | export { default as getCompiler } from "./getCompiler"; 11 | export { default as readsAssets } from "./readAssets"; 12 | export { default as getWarnings } from "./getWarnings"; 13 | -------------------------------------------------------------------------------- /test/helpers/getAssetsNameAndSize.js: -------------------------------------------------------------------------------- 1 | import zlib from "node:zlib"; 2 | 3 | import { readAsset } from "./index"; 4 | 5 | export default (stats, compiler) => { 6 | const { assets, assetsInfo } = stats.compilation; 7 | 8 | return Object.keys(assets) 9 | .sort() 10 | .map((name) => { 11 | const info = assetsInfo.get(name); 12 | const item = [name, assets[name].size(), info]; 13 | 14 | if (info.related && info.related.gzipped) { 15 | const original = readAsset(name, compiler, stats); 16 | const gzipped = readAsset(info.related.gzipped, compiler, stats); 17 | const ungzipped = zlib.gunzipSync(gzipped); 18 | 19 | const isEquals = ungzipped.equals(original); 20 | 21 | if (!isEquals) { 22 | throw new Error( 23 | `Ungzipped version of "${name}" is not equal to original`, 24 | ); 25 | } 26 | } 27 | 28 | return item; 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/minRatio-option.test.js: -------------------------------------------------------------------------------- 1 | import CompressionPlugin from "../src/index"; 2 | 3 | import { 4 | compile, 5 | getAssetsNameAndSize, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers/index"; 10 | 11 | describe('"minRatio" option', () => { 12 | let compiler; 13 | 14 | beforeEach(() => { 15 | compiler = getCompiler("./entry.js"); 16 | }); 17 | 18 | it("matches snapshot for `0` value ({Number})", async () => { 19 | new CompressionPlugin({ 20 | minRatio: 0, 21 | }).apply(compiler); 22 | 23 | const stats = await compile(compiler); 24 | 25 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 26 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 27 | expect(getErrors(stats)).toMatchSnapshot("errors"); 28 | }); 29 | 30 | it("matches snapshot for `1` value ({Number})", async () => { 31 | new CompressionPlugin({ 32 | minRatio: 1, 33 | }).apply(compiler); 34 | 35 | const stats = await compile(compiler); 36 | 37 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 38 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 39 | expect(getErrors(stats)).toMatchSnapshot("errors"); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/helpers/getCompiler.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { Volume, createFsFromVolume } from "memfs"; 4 | import webpack from "webpack"; 5 | 6 | /** @type {import("webpack").Configuration} Configuration */ 7 | /** @type {import("../../src/index.js")} EXPECTED_ANY */ 8 | 9 | /** 10 | * @param {string} fixture fixture 11 | * @param {Record} loaderOptions loader options 12 | * @param {Configuration} config extra configuration 13 | * @returns {Configuration} configuration 14 | */ 15 | export default function getCompiler(fixture, loaderOptions = {}, config = {}) { 16 | const fullConfig = { 17 | mode: "development", 18 | devtool: config.devtool || false, 19 | context: path.resolve(__dirname, "../fixtures"), 20 | entry: path.resolve(__dirname, "../fixtures", fixture), 21 | output: { 22 | path: path.resolve(__dirname, "../outputs"), 23 | filename: "[name].[chunkhash].js", 24 | chunkFilename: "[id].[name].[chunkhash].js", 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(png|jpg|gif|svg|txt)$/i, 30 | rules: [ 31 | { 32 | loader: "file-loader", 33 | options: loaderOptions || {}, 34 | }, 35 | ], 36 | }, 37 | ], 38 | }, 39 | plugins: [], 40 | ...config, 41 | }; 42 | 43 | const compiler = webpack(fullConfig); 44 | 45 | if (!config.outputFileSystem) { 46 | compiler.outputFileSystem = createFsFromVolume(new Volume()); 47 | } 48 | 49 | return compiler; 50 | } 51 | -------------------------------------------------------------------------------- /test/include-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import CompressionPlugin from "../src/index"; 3 | 4 | import { 5 | compile, 6 | getAssetsNameAndSize, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | } from "./helpers/index"; 11 | 12 | describe('"include" option', () => { 13 | let compiler; 14 | 15 | beforeEach(() => { 16 | compiler = getCompiler( 17 | "./entry.js", 18 | {}, 19 | { 20 | output: { 21 | path: path.join(__dirname, "./dist"), 22 | filename: "[name].js?var=[hash]", 23 | chunkFilename: "[id].[name].js?ver=[hash]", 24 | }, 25 | }, 26 | ); 27 | }); 28 | 29 | it("matches snapshot for a single `include` value ({RegExp})", async () => { 30 | new CompressionPlugin({ 31 | include: /\.js(\?.*)?$/i, 32 | minRatio: 1, 33 | }).apply(compiler); 34 | 35 | const stats = await compile(compiler); 36 | 37 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 38 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 39 | expect(getErrors(stats)).toMatchSnapshot("errors"); 40 | }); 41 | 42 | it("matches snapshot for multiple `include` values ({Array})", async () => { 43 | new CompressionPlugin({ 44 | include: [/\.js(\?.*)?$/i, /\.svg(\?.*)?$/i], 45 | minRatio: 1, 46 | }).apply(compiler); 47 | 48 | const stats = await compile(compiler); 49 | 50 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 51 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 52 | expect(getErrors(stats)).toMatchSnapshot("errors"); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/exclude-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import CompressionPlugin from "../src/index"; 3 | 4 | import { 5 | compile, 6 | getAssetsNameAndSize, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | } from "./helpers/index"; 11 | 12 | describe('"exclude" option', () => { 13 | let compiler; 14 | 15 | beforeEach(() => { 16 | compiler = getCompiler( 17 | "./entry.js", 18 | {}, 19 | { 20 | output: { 21 | path: path.join(__dirname, "./dist"), 22 | filename: "[name].js?var=[hash]", 23 | chunkFilename: "[id].[name].js?ver=[hash]", 24 | }, 25 | }, 26 | ); 27 | }); 28 | 29 | it("matches snapshot for a single `exclude` value ({RegExp})", async () => { 30 | new CompressionPlugin({ 31 | exclude: /\.svg(\?.*)?$/i, 32 | minRatio: 1, 33 | }).apply(compiler); 34 | 35 | const stats = await compile(compiler); 36 | 37 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 38 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 39 | expect(getErrors(stats)).toMatchSnapshot("errors"); 40 | }); 41 | 42 | it("matches snapshot for multiple `exclude` values ({Array})", async () => { 43 | new CompressionPlugin({ 44 | exclude: [/\.svg(\?.*)?$/i, /\.png(\?.*)?$/i], 45 | minRatio: 1, 46 | }).apply(compiler); 47 | 48 | const stats = await compile(compiler); 49 | 50 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 51 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 52 | expect(getErrors(stats)).toMatchSnapshot("errors"); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/algorithm.test.js: -------------------------------------------------------------------------------- 1 | import CompressionPlugin from "../src/index"; 2 | 3 | import { 4 | compile, 5 | getAssetsNameAndSize, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers/index"; 10 | 11 | describe('"algorithm" option', () => { 12 | let compiler; 13 | 14 | beforeEach(() => { 15 | compiler = getCompiler("./entry.js"); 16 | }); 17 | 18 | it("matches snapshot for `unknown` value ({String})", () => { 19 | expect(() => { 20 | new CompressionPlugin({ 21 | minRatio: 1, 22 | algorithm: "unknown", 23 | }).apply(compiler); 24 | }).toThrowErrorMatchingSnapshot(); 25 | }); 26 | 27 | it("matches snapshot for `gzip` value ({String})", async () => { 28 | new CompressionPlugin({ 29 | minRatio: 1, 30 | algorithm: "gzip", 31 | }).apply(compiler); 32 | 33 | const stats = await compile(compiler); 34 | 35 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 36 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 37 | expect(getErrors(stats)).toMatchSnapshot("errors"); 38 | }); 39 | 40 | it("matches snapshot for custom function ({Function})", async () => { 41 | new CompressionPlugin({ 42 | minRatio: 1, 43 | algorithm(input, compressionOptions, callback) { 44 | expect(compressionOptions).toMatchSnapshot("compressionOptions"); 45 | 46 | return callback(null, input); 47 | }, 48 | }).apply(compiler); 49 | 50 | const stats = await compile(compiler); 51 | 52 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 53 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 54 | expect(getErrors(stats)).toMatchSnapshot("errors"); 55 | }); 56 | 57 | it("matches snapshot for custom function with error ({Function})", async () => { 58 | new CompressionPlugin({ 59 | minRatio: 1, 60 | algorithm(input, compressionOptions, callback) { 61 | expect(compressionOptions).toMatchSnapshot("compressionOptions"); 62 | 63 | return callback("Error", input); 64 | }, 65 | }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 70 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 71 | expect(getErrors(stats)).toMatchSnapshot("errors"); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/threshold-option.test.js: -------------------------------------------------------------------------------- 1 | import CompressionPlugin from "../src/index"; 2 | 3 | import { 4 | compile, 5 | getAssetsNameAndSize, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers/index"; 10 | 11 | describe('"threshold" option', () => { 12 | let compiler; 13 | 14 | beforeEach(() => { 15 | compiler = getCompiler("./entry.js"); 16 | }); 17 | 18 | it("matches snapshot for `0` value ({Number})", async () => { 19 | new CompressionPlugin({ 20 | minRatio: 1, 21 | threshold: 0, 22 | }).apply(compiler); 23 | 24 | const stats = await compile(compiler); 25 | 26 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 27 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 28 | expect(getErrors(stats)).toMatchSnapshot("errors"); 29 | }); 30 | 31 | it("matches snapshot for `8192` value ({Number})", async () => { 32 | new CompressionPlugin({ 33 | minRatio: 1, 34 | threshold: 8192, 35 | }).apply(compiler); 36 | 37 | const stats = await compile(compiler); 38 | 39 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 40 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 41 | expect(getErrors(stats)).toMatchSnapshot("errors"); 42 | }); 43 | 44 | it('should compress all assets including assets with "0" bytes original size', async () => { 45 | compiler = getCompiler("./empty.js"); 46 | 47 | new CompressionPlugin({ 48 | minRatio: Infinity, 49 | threshold: 0, 50 | }).apply(compiler); 51 | 52 | const stats = await compile(compiler); 53 | 54 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 55 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 56 | expect(getErrors(stats)).toMatchSnapshot("errors"); 57 | }); 58 | 59 | it('should compress all assets excluding assets with "0" bytes original size', async () => { 60 | compiler = getCompiler("./empty.js"); 61 | 62 | new CompressionPlugin({ 63 | minRatio: Number.MAX_SAFE_INTEGER, 64 | threshold: 0, 65 | }).apply(compiler); 66 | 67 | const stats = await compile(compiler); 68 | 69 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 70 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 71 | expect(getErrors(stats)).toMatchSnapshot("errors"); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/test-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import CompressionPlugin from "../src/index"; 3 | 4 | import { 5 | compile, 6 | getAssetsNameAndSize, 7 | getCompiler, 8 | getErrors, 9 | getWarnings, 10 | } from "./helpers/index"; 11 | 12 | describe('"test" option', () => { 13 | let compiler; 14 | 15 | beforeEach(() => { 16 | compiler = getCompiler( 17 | "./entry.js", 18 | {}, 19 | { 20 | output: { 21 | path: path.join(__dirname, "./dist"), 22 | filename: "[name].js?var=[hash]", 23 | chunkFilename: "[id].[name].js?ver=[hash]", 24 | }, 25 | }, 26 | ); 27 | }); 28 | 29 | it("matches snapshot with empty `test` value", async () => { 30 | new CompressionPlugin({ 31 | minRatio: 1, 32 | }).apply(compiler); 33 | 34 | const stats = await compile(compiler); 35 | 36 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 37 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 38 | expect(getErrors(stats)).toMatchSnapshot("errors"); 39 | }); 40 | 41 | it("matches snapshot for a single `test` value ({RegExp})", async () => { 42 | new CompressionPlugin({ 43 | test: /\.(png|jpg|gif)$/i, 44 | minRatio: 1, 45 | }).apply(compiler); 46 | 47 | const stats = await compile(compiler); 48 | 49 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 50 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 51 | expect(getErrors(stats)).toMatchSnapshot("errors"); 52 | }); 53 | 54 | it("matches snapshot for multiple `test` values ({Array})", async () => { 55 | new CompressionPlugin({ 56 | test: [/\.(png|jpg|gif)$/i, /\.svg/i], 57 | minRatio: 1, 58 | }).apply(compiler); 59 | 60 | const stats = await compile(compiler); 61 | 62 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 63 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 64 | expect(getErrors(stats)).toMatchSnapshot("errors"); 65 | }); 66 | 67 | it("should work when no asset to compress", async () => { 68 | new CompressionPlugin({ 69 | test: /\.(unknown)$/i, 70 | minRatio: 1, 71 | }).apply(compiler); 72 | 73 | const stats = await compile(compiler); 74 | 75 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 76 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 77 | expect(getErrors(stats)).toMatchSnapshot("errors"); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/compressionOptions-option.test.js: -------------------------------------------------------------------------------- 1 | import CompressionPlugin from "../src/index"; 2 | 3 | import { 4 | compile, 5 | getAssetsNameAndSize, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers/index"; 10 | 11 | describe('"compressionOptions" option', () => { 12 | let compiler; 13 | 14 | beforeEach(() => { 15 | compiler = getCompiler("./entry.js"); 16 | }); 17 | 18 | it("matches snapshot without values", async () => { 19 | new CompressionPlugin({ 20 | minRatio: 1, 21 | }).apply(compiler); 22 | 23 | const stats = await compile(compiler); 24 | 25 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 26 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 27 | expect(getErrors(stats)).toMatchSnapshot("errors"); 28 | }); 29 | 30 | it("matches snapshot for custom options ({Object})", async () => { 31 | new CompressionPlugin({ 32 | compressionOptions: { 33 | level: 9, 34 | }, 35 | minRatio: 1, 36 | }).apply(compiler); 37 | 38 | const stats = await compile(compiler); 39 | 40 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 41 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 42 | expect(getErrors(stats)).toMatchSnapshot("errors"); 43 | }); 44 | 45 | it("set default compression level to maximum for gzip", async () => { 46 | new CompressionPlugin({ 47 | algorithm: "gzip", 48 | }).apply(compiler); 49 | 50 | const stats = await compile(compiler); 51 | 52 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 53 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 54 | expect(getErrors(stats)).toMatchSnapshot("errors"); 55 | }); 56 | 57 | it("set default compression level to maximum for deflate", async () => { 58 | new CompressionPlugin({ 59 | algorithm: "deflate", 60 | }).apply(compiler); 61 | 62 | const stats = await compile(compiler); 63 | 64 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 65 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 66 | expect(getErrors(stats)).toMatchSnapshot("errors"); 67 | }); 68 | 69 | it("set default compression level to maximum for deflateRaw", async () => { 70 | new CompressionPlugin({ 71 | algorithm: "deflateRaw", 72 | }).apply(compiler); 73 | 74 | const stats = await compile(compiler); 75 | 76 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 77 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 78 | expect(getErrors(stats)).toMatchSnapshot("errors"); 79 | }); 80 | 81 | it("set default compression level to maximum for brotli", async () => { 82 | new CompressionPlugin({ 83 | algorithm: "brotliCompress", 84 | }).apply(compiler); 85 | 86 | const stats = await compile(compiler); 87 | 88 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 89 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 90 | expect(getErrors(stats)).toMatchSnapshot("errors"); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/validate-options.test.js: -------------------------------------------------------------------------------- 1 | import CompressionPlugin from "../src"; 2 | 3 | describe("validate options", () => { 4 | const tests = { 5 | test: { 6 | success: [ 7 | /foo/, 8 | "foo", 9 | [/foo/], 10 | [/foo/, /bar/], 11 | ["foo", "bar"], 12 | [/foo/, "bar"], 13 | ], 14 | failure: [true, [true], [/foo/, "foo", true]], 15 | }, 16 | include: { 17 | success: [ 18 | /foo/, 19 | "foo", 20 | [/foo/], 21 | [/foo/, /bar/], 22 | ["foo", "bar"], 23 | [/foo/, "bar"], 24 | ], 25 | failure: [true, [true], [/foo/, "foo", true]], 26 | }, 27 | exclude: { 28 | success: [ 29 | /foo/, 30 | "foo", 31 | [/foo/], 32 | [/foo/, /bar/], 33 | ["foo", "bar"], 34 | [/foo/, "bar"], 35 | ], 36 | failure: [true, [true], [/foo/, "foo", true]], 37 | }, 38 | filename: { 39 | success: ["[path].gz[query]", () => {}], 40 | failure: [true], 41 | }, 42 | algorithm: { 43 | success: ["gzip", () => {}], 44 | failure: [true], 45 | }, 46 | compressionOptions: { 47 | success: [{ level: 1 }, { unknown: 1 }], 48 | failure: ["1024"], 49 | }, 50 | threshold: { 51 | success: [1024], 52 | failure: ["1024"], 53 | }, 54 | minRatio: { 55 | success: [0.8], 56 | failure: ["0.8"], 57 | }, 58 | deleteOriginalAssets: { 59 | success: [true, false, "keep-source-map", () => true], 60 | failure: ["true", "unknown"], 61 | }, 62 | unknown: { 63 | success: [], 64 | failure: [1, true, false, "test", /test/, [], {}, { foo: "bar" }], 65 | }, 66 | }; 67 | 68 | function stringifyValue(value) { 69 | if ( 70 | Array.isArray(value) || 71 | (value && typeof value === "object" && value.constructor === Object) 72 | ) { 73 | return JSON.stringify(value); 74 | } 75 | 76 | return value; 77 | } 78 | 79 | async function createTestCase(key, value, type) { 80 | it(`should ${ 81 | type === "success" ? "successfully validate" : "throw an error on" 82 | } the "${key}" option with "${stringifyValue(value)}" value`, async () => { 83 | let error; 84 | 85 | try { 86 | // eslint-disable-next-line no-new 87 | new CompressionPlugin({ [key]: value }); 88 | } catch (err) { 89 | if (err.name !== "ValidationError") { 90 | throw err; 91 | } 92 | 93 | error = err; 94 | } finally { 95 | if (type === "success") { 96 | expect(error).toBeUndefined(); 97 | } else if (type === "failure") { 98 | expect(() => { 99 | throw error; 100 | }).toThrowErrorMatchingSnapshot(); 101 | } 102 | } 103 | }); 104 | } 105 | 106 | for (const [key, values] of Object.entries(tests)) { 107 | for (const type of Object.keys(values)) { 108 | for (const value of values[type]) { 109 | createTestCase(key, value, type); 110 | } 111 | } 112 | } 113 | }); 114 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: compression-webpack-plugin 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | pull_request: 9 | branches: 10 | - main 11 | - next 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | lint: 18 | name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }} 19 | 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | strategy: 24 | matrix: 25 | os: [ubuntu-latest] 26 | node-version: [lts/*] 27 | 28 | runs-on: ${{ matrix.os }} 29 | 30 | concurrency: 31 | group: lint-${{ matrix.os }}-v${{ matrix.node-version }}-${{ github.ref }} 32 | cancel-in-progress: true 33 | 34 | steps: 35 | - uses: actions/checkout@v5 36 | with: 37 | fetch-depth: 0 38 | 39 | - name: Use Node.js ${{ matrix.node-version }} 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: ${{ matrix.node-version }} 43 | cache: "npm" 44 | 45 | - name: Install dependencies 46 | run: npm ci 47 | 48 | - name: Lint 49 | run: npm run lint 50 | 51 | - name: Build types 52 | run: npm run build:types 53 | 54 | - name: Check types 55 | run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi 56 | 57 | - name: Security audit 58 | run: npm run security 59 | 60 | - name: Validate PR commits with commitlint 61 | if: github.event_name == 'pull_request' 62 | run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose 63 | 64 | test: 65 | name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }} 66 | 67 | strategy: 68 | matrix: 69 | os: [ubuntu-latest, windows-latest, macos-latest] 70 | node-version: [18.x, 20.x, 22.x, 24.x] 71 | webpack-version: [latest] 72 | 73 | runs-on: ${{ matrix.os }} 74 | 75 | concurrency: 76 | group: test-${{ matrix.os }}-v${{ matrix.node-version }}-${{ matrix.webpack-version }}-${{ github.ref }} 77 | cancel-in-progress: true 78 | 79 | steps: 80 | - name: Setup Git 81 | if: matrix.os == 'windows-latest' 82 | run: git config --global core.autocrlf input 83 | 84 | - uses: actions/checkout@v5 85 | 86 | - name: Use Node.js ${{ matrix.node-version }} 87 | uses: actions/setup-node@v4 88 | with: 89 | node-version: ${{ matrix.node-version }} 90 | cache: "npm" 91 | 92 | - name: Install dependencies 93 | run: npm ci 94 | 95 | - name: Install webpack ${{ matrix.webpack-version }} 96 | if: matrix.webpack-version != 'latest' 97 | run: npm i webpack@${{ matrix.webpack-version }} 98 | 99 | - name: Run tests for webpack version ${{ matrix.webpack-version }} 100 | run: npm run test:coverage -- --ci 101 | 102 | - name: Submit coverage data to codecov 103 | uses: codecov/codecov-action@v5 104 | with: 105 | token: ${{ secrets.CODECOV_TOKEN }} 106 | -------------------------------------------------------------------------------- /test/__snapshots__/minRatio-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"minRatio" option matches snapshot for \`0\` value ({Number}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "size": 78117, 11 | "sourceFilename": "icon.png", 12 | }, 13 | ], 14 | [ 15 | "23fc1d3ac606d117e05a140e0de79806.svg", 16 | 672, 17 | { 18 | "immutable": true, 19 | "size": 672, 20 | "sourceFilename": "icon.svg", 21 | }, 22 | ], 23 | [ 24 | "async.async.55e6e9a872bcc7d4b226.js", 25 | 194, 26 | { 27 | "chunkhash": "55e6e9a872bcc7d4b226", 28 | "immutable": true, 29 | "javascriptModule": false, 30 | "size": 194, 31 | }, 32 | ], 33 | [ 34 | "main.4d75ca3dcb0ac1fb976b.js", 35 | 16252, 36 | { 37 | "chunkhash": "4d75ca3dcb0ac1fb976b", 38 | "immutable": true, 39 | "javascriptModule": false, 40 | "size": 16252, 41 | }, 42 | ], 43 | ] 44 | `; 45 | 46 | exports[`"minRatio" option matches snapshot for \`0\` value ({Number}): errors 1`] = `[]`; 47 | 48 | exports[`"minRatio" option matches snapshot for \`0\` value ({Number}): warnings 1`] = `[]`; 49 | 50 | exports[`"minRatio" option matches snapshot for \`1\` value ({Number}): assets 1`] = ` 51 | [ 52 | [ 53 | "09a1a1112c577c2794359715edfcb5ac.png", 54 | 78117, 55 | { 56 | "immutable": true, 57 | "related": { 58 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 59 | }, 60 | "size": 78117, 61 | "sourceFilename": "icon.png", 62 | }, 63 | ], 64 | [ 65 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 66 | 73507, 67 | { 68 | "compressed": true, 69 | "immutable": true, 70 | "size": 73507, 71 | }, 72 | ], 73 | [ 74 | "23fc1d3ac606d117e05a140e0de79806.svg", 75 | 672, 76 | { 77 | "immutable": true, 78 | "related": { 79 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 80 | }, 81 | "size": 672, 82 | "sourceFilename": "icon.svg", 83 | }, 84 | ], 85 | [ 86 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 87 | 390, 88 | { 89 | "compressed": true, 90 | "immutable": true, 91 | "size": 390, 92 | }, 93 | ], 94 | [ 95 | "async.async.55e6e9a872bcc7d4b226.js", 96 | 194, 97 | { 98 | "chunkhash": "55e6e9a872bcc7d4b226", 99 | "immutable": true, 100 | "javascriptModule": false, 101 | "related": { 102 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 103 | }, 104 | "size": 194, 105 | }, 106 | ], 107 | [ 108 | "async.async.55e6e9a872bcc7d4b226.js.gz", 109 | 136, 110 | { 111 | "compressed": true, 112 | "immutable": true, 113 | "size": 136, 114 | }, 115 | ], 116 | [ 117 | "main.4d75ca3dcb0ac1fb976b.js", 118 | 16252, 119 | { 120 | "chunkhash": "4d75ca3dcb0ac1fb976b", 121 | "immutable": true, 122 | "javascriptModule": false, 123 | "related": { 124 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 125 | }, 126 | "size": 16252, 127 | }, 128 | ], 129 | [ 130 | "main.4d75ca3dcb0ac1fb976b.js.gz", 131 | 4097, 132 | { 133 | "compressed": true, 134 | "immutable": true, 135 | "size": 4097, 136 | }, 137 | ], 138 | ] 139 | `; 140 | 141 | exports[`"minRatio" option matches snapshot for \`1\` value ({Number}): errors 1`] = `[]`; 142 | 143 | exports[`"minRatio" option matches snapshot for \`1\` value ({Number}): warnings 1`] = `[]`; 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compression-webpack-plugin", 3 | "version": "11.1.0", 4 | "description": "Prepare compressed versions of assets to serve them with Content-Encoding", 5 | "keywords": [ 6 | "webpack" 7 | ], 8 | "homepage": "https://github.com/webpack/compression-webpack-plugin", 9 | "bugs": "https://github.com/webpack/compression-webpack-plugin/issues", 10 | "repository": "webpack/compression-webpack-plugin", 11 | "funding": { 12 | "type": "opencollective", 13 | "url": "https://opencollective.com/webpack" 14 | }, 15 | "license": "MIT", 16 | "author": "Tobias Koppers @sokra", 17 | "main": "dist/index.js", 18 | "types": "types/index.d.ts", 19 | "files": [ 20 | "dist", 21 | "types" 22 | ], 23 | "scripts": { 24 | "start": "npm run build -- -w", 25 | "clean": "del-cli dist types", 26 | "prebuild": "npm run clean", 27 | "build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.ts\" --write", 28 | "build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files", 29 | "build": "npm-run-all -p \"build:**\"", 30 | "commitlint": "commitlint --from=main", 31 | "security": "npm audit --production", 32 | "lint:prettier": "prettier --cache --list-different .", 33 | "lint:code": "eslint --cache .", 34 | "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"", 35 | "lint:types": "tsc --pretty --noEmit", 36 | "lint": "npm-run-all -l -p \"lint:**\"", 37 | "fix:code": "npm run lint:code -- --fix", 38 | "fix:prettier": "npm run lint:prettier -- --write", 39 | "fix": "npm-run-all -l fix:code fix:prettier", 40 | "test:only": "cross-env NODE_ENV=test jest", 41 | "test:watch": "npm run test:only -- --watch", 42 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage", 43 | "pretest": "npm run lint", 44 | "test": "npm run test:coverage", 45 | "prepare": "husky && npm run build", 46 | "release": "standard-version" 47 | }, 48 | "dependencies": { 49 | "schema-utils": "^4.2.0", 50 | "serialize-javascript": "^6.0.2" 51 | }, 52 | "devDependencies": { 53 | "@babel/cli": "^7.24.8", 54 | "@babel/core": "^7.25.2", 55 | "@babel/preset-env": "^7.25.3", 56 | "@commitlint/cli": "^19.3.0", 57 | "@commitlint/config-conventional": "^19.2.2", 58 | "@eslint/js": "^9.32.0", 59 | "@eslint/markdown": "^7.0.0", 60 | "@gfx/zopfli": "^1.0.15", 61 | "@stylistic/eslint-plugin": "^5.2.2", 62 | "@types/node": "^20.14.9", 63 | "@types/serialize-javascript": "^5.0.4", 64 | "babel-jest": "^30.0.0", 65 | "cross-env": "^7.0.3", 66 | "cspell": "^8.13.1", 67 | "del": "^7.1.0", 68 | "del-cli": "^5.1.0", 69 | "eslint": "^9.30.1", 70 | "eslint-config-prettier": "^10.1.5", 71 | "eslint-config-webpack": "^4.3.2", 72 | "eslint-plugin-import": "^2.32.0", 73 | "eslint-plugin-jest": "^29.0.1", 74 | "eslint-plugin-jsdoc": "^52.0.0", 75 | "eslint-plugin-n": "^17.21.0", 76 | "eslint-plugin-prettier": "^5.5.3", 77 | "eslint-plugin-unicorn": "^60.0.0", 78 | "file-loader": "^6.2.0", 79 | "globals": "^16.3.0", 80 | "husky": "^9.1.4", 81 | "jest": "^30.0.0", 82 | "lint-staged": "^15.2.8", 83 | "memfs": "^4.11.1", 84 | "npm-run-all": "^4.1.5", 85 | "prettier": "^3.3.3", 86 | "standard-version": "^9.5.0", 87 | "typescript": "^5.5.4", 88 | "typescript-eslint": "^8.36.0", 89 | "webpack": "^5.93.0", 90 | "webpack-stats-plugin": "^1.1.3", 91 | "workbox-webpack-plugin": "^7.1.0" 92 | }, 93 | "peerDependencies": { 94 | "webpack": "^5.1.0" 95 | }, 96 | "engines": { 97 | "node": ">= 18.12.0" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "additionalProperties": false, 4 | "definitions": { 5 | "Rule": { 6 | "description": "Filtering rule as regex or string.", 7 | "anyOf": [ 8 | { 9 | "instanceof": "RegExp", 10 | "tsType": "RegExp" 11 | }, 12 | { 13 | "type": "string", 14 | "minLength": 1 15 | } 16 | ] 17 | }, 18 | "Rules": { 19 | "description": "Filtering rules.", 20 | "anyOf": [ 21 | { 22 | "type": "array", 23 | "items": { 24 | "description": "A rule condition.", 25 | "oneOf": [ 26 | { 27 | "$ref": "#/definitions/Rule" 28 | } 29 | ] 30 | } 31 | }, 32 | { 33 | "$ref": "#/definitions/Rule" 34 | } 35 | ] 36 | } 37 | }, 38 | "properties": { 39 | "test": { 40 | "description": "Include all assets that pass test assertion.", 41 | "link": "https://github.com/webpack/compression-webpack-plugin/#test", 42 | "oneOf": [ 43 | { 44 | "$ref": "#/definitions/Rules" 45 | } 46 | ] 47 | }, 48 | "include": { 49 | "description": "Include all assets matching any of these conditions.", 50 | "link": "https://github.com/webpack/compression-webpack-plugin/#include", 51 | "oneOf": [ 52 | { 53 | "$ref": "#/definitions/Rules" 54 | } 55 | ] 56 | }, 57 | "exclude": { 58 | "description": "Exclude all assets matching any of these conditions.", 59 | "link": "https://github.com/webpack/compression-webpack-plugin/#exclude", 60 | "oneOf": [ 61 | { 62 | "$ref": "#/definitions/Rules" 63 | } 64 | ] 65 | }, 66 | "algorithm": { 67 | "description": "The compression algorithm/function.", 68 | "link": "https://github.com/webpack/compression-webpack-plugin/#algorithm", 69 | "anyOf": [ 70 | { 71 | "type": "string" 72 | }, 73 | { 74 | "instanceof": "Function" 75 | } 76 | ] 77 | }, 78 | "compressionOptions": { 79 | "description": "Compression options for `algorithm`.", 80 | "link": "https://github.com/webpack/compression-webpack-plugin/#compressionoptions", 81 | "additionalProperties": true, 82 | "type": "object" 83 | }, 84 | "threshold": { 85 | "description": "Only assets bigger than this size are processed. In bytes.", 86 | "link": "https://github.com/webpack/compression-webpack-plugin/#threshold", 87 | "type": "number" 88 | }, 89 | "minRatio": { 90 | "description": "Only assets that compress better than this ratio are processed (`minRatio = Compressed Size / Original Size`).", 91 | "link": "https://github.com/webpack/compression-webpack-plugin/#minratio", 92 | "type": "number" 93 | }, 94 | "deleteOriginalAssets": { 95 | "description": "Whether to delete the original assets or not.", 96 | "link": "https://github.com/webpack/compression-webpack-plugin/#deleteoriginalassets", 97 | "anyOf": [ 98 | { 99 | "type": "boolean" 100 | }, 101 | { 102 | "instanceof": "Function" 103 | }, 104 | { 105 | "enum": ["keep-source-map"] 106 | } 107 | ] 108 | }, 109 | "filename": { 110 | "description": "The target asset filename.", 111 | "link": "https://github.com/webpack/compression-webpack-plugin/#filename", 112 | "anyOf": [ 113 | { 114 | "type": "string" 115 | }, 116 | { 117 | "instanceof": "Function" 118 | } 119 | ] 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /test/__snapshots__/exclude-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"exclude" option matches snapshot for a single \`exclude\` value ({RegExp}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "related": { 11 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 12 | }, 13 | "size": 78117, 14 | "sourceFilename": "icon.png", 15 | }, 16 | ], 17 | [ 18 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 19 | 73507, 20 | { 21 | "compressed": true, 22 | "immutable": true, 23 | "size": 73507, 24 | }, 25 | ], 26 | [ 27 | "23fc1d3ac606d117e05a140e0de79806.svg", 28 | 672, 29 | { 30 | "immutable": true, 31 | "size": 672, 32 | "sourceFilename": "icon.svg", 33 | }, 34 | ], 35 | [ 36 | "async.async.js.gz", 37 | 136, 38 | { 39 | "compressed": true, 40 | "immutable": true, 41 | "size": 136, 42 | }, 43 | ], 44 | [ 45 | "async.async.js?ver=8bb94238bc82ebe7567e", 46 | 194, 47 | { 48 | "fullhash": "8bb94238bc82ebe7567e", 49 | "immutable": true, 50 | "javascriptModule": false, 51 | "related": { 52 | "gzipped": "async.async.js.gz", 53 | }, 54 | "size": 194, 55 | }, 56 | ], 57 | [ 58 | "main.js.gz", 59 | 4124, 60 | { 61 | "compressed": true, 62 | "immutable": true, 63 | "size": 4124, 64 | }, 65 | ], 66 | [ 67 | "main.js?var=8bb94238bc82ebe7567e", 68 | 16413, 69 | { 70 | "fullhash": "8bb94238bc82ebe7567e", 71 | "immutable": true, 72 | "javascriptModule": false, 73 | "related": { 74 | "gzipped": "main.js.gz", 75 | }, 76 | "size": 16413, 77 | }, 78 | ], 79 | ] 80 | `; 81 | 82 | exports[`"exclude" option matches snapshot for a single \`exclude\` value ({RegExp}): errors 1`] = `[]`; 83 | 84 | exports[`"exclude" option matches snapshot for a single \`exclude\` value ({RegExp}): warnings 1`] = `[]`; 85 | 86 | exports[`"exclude" option matches snapshot for multiple \`exclude\` values ({Array}): assets 1`] = ` 87 | [ 88 | [ 89 | "09a1a1112c577c2794359715edfcb5ac.png", 90 | 78117, 91 | { 92 | "immutable": true, 93 | "size": 78117, 94 | "sourceFilename": "icon.png", 95 | }, 96 | ], 97 | [ 98 | "23fc1d3ac606d117e05a140e0de79806.svg", 99 | 672, 100 | { 101 | "immutable": true, 102 | "size": 672, 103 | "sourceFilename": "icon.svg", 104 | }, 105 | ], 106 | [ 107 | "async.async.js.gz", 108 | 136, 109 | { 110 | "compressed": true, 111 | "immutable": true, 112 | "size": 136, 113 | }, 114 | ], 115 | [ 116 | "async.async.js?ver=8bb94238bc82ebe7567e", 117 | 194, 118 | { 119 | "fullhash": "8bb94238bc82ebe7567e", 120 | "immutable": true, 121 | "javascriptModule": false, 122 | "related": { 123 | "gzipped": "async.async.js.gz", 124 | }, 125 | "size": 194, 126 | }, 127 | ], 128 | [ 129 | "main.js.gz", 130 | 4124, 131 | { 132 | "compressed": true, 133 | "immutable": true, 134 | "size": 4124, 135 | }, 136 | ], 137 | [ 138 | "main.js?var=8bb94238bc82ebe7567e", 139 | 16413, 140 | { 141 | "fullhash": "8bb94238bc82ebe7567e", 142 | "immutable": true, 143 | "javascriptModule": false, 144 | "related": { 145 | "gzipped": "main.js.gz", 146 | }, 147 | "size": 16413, 148 | }, 149 | ], 150 | ] 151 | `; 152 | 153 | exports[`"exclude" option matches snapshot for multiple \`exclude\` values ({Array}): errors 1`] = `[]`; 154 | 155 | exports[`"exclude" option matches snapshot for multiple \`exclude\` values ({Array}): warnings 1`] = `[]`; 156 | -------------------------------------------------------------------------------- /test/__snapshots__/include-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"include" option matches snapshot for a single \`include\` value ({RegExp}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "size": 78117, 11 | "sourceFilename": "icon.png", 12 | }, 13 | ], 14 | [ 15 | "23fc1d3ac606d117e05a140e0de79806.svg", 16 | 672, 17 | { 18 | "immutable": true, 19 | "size": 672, 20 | "sourceFilename": "icon.svg", 21 | }, 22 | ], 23 | [ 24 | "async.async.js.gz", 25 | 136, 26 | { 27 | "compressed": true, 28 | "immutable": true, 29 | "size": 136, 30 | }, 31 | ], 32 | [ 33 | "async.async.js?ver=8bb94238bc82ebe7567e", 34 | 194, 35 | { 36 | "fullhash": "8bb94238bc82ebe7567e", 37 | "immutable": true, 38 | "javascriptModule": false, 39 | "related": { 40 | "gzipped": "async.async.js.gz", 41 | }, 42 | "size": 194, 43 | }, 44 | ], 45 | [ 46 | "main.js.gz", 47 | 4124, 48 | { 49 | "compressed": true, 50 | "immutable": true, 51 | "size": 4124, 52 | }, 53 | ], 54 | [ 55 | "main.js?var=8bb94238bc82ebe7567e", 56 | 16413, 57 | { 58 | "fullhash": "8bb94238bc82ebe7567e", 59 | "immutable": true, 60 | "javascriptModule": false, 61 | "related": { 62 | "gzipped": "main.js.gz", 63 | }, 64 | "size": 16413, 65 | }, 66 | ], 67 | ] 68 | `; 69 | 70 | exports[`"include" option matches snapshot for a single \`include\` value ({RegExp}): errors 1`] = `[]`; 71 | 72 | exports[`"include" option matches snapshot for a single \`include\` value ({RegExp}): warnings 1`] = `[]`; 73 | 74 | exports[`"include" option matches snapshot for multiple \`include\` values ({Array}): assets 1`] = ` 75 | [ 76 | [ 77 | "09a1a1112c577c2794359715edfcb5ac.png", 78 | 78117, 79 | { 80 | "immutable": true, 81 | "size": 78117, 82 | "sourceFilename": "icon.png", 83 | }, 84 | ], 85 | [ 86 | "23fc1d3ac606d117e05a140e0de79806.svg", 87 | 672, 88 | { 89 | "immutable": true, 90 | "related": { 91 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 92 | }, 93 | "size": 672, 94 | "sourceFilename": "icon.svg", 95 | }, 96 | ], 97 | [ 98 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 99 | 390, 100 | { 101 | "compressed": true, 102 | "immutable": true, 103 | "size": 390, 104 | }, 105 | ], 106 | [ 107 | "async.async.js.gz", 108 | 136, 109 | { 110 | "compressed": true, 111 | "immutable": true, 112 | "size": 136, 113 | }, 114 | ], 115 | [ 116 | "async.async.js?ver=8bb94238bc82ebe7567e", 117 | 194, 118 | { 119 | "fullhash": "8bb94238bc82ebe7567e", 120 | "immutable": true, 121 | "javascriptModule": false, 122 | "related": { 123 | "gzipped": "async.async.js.gz", 124 | }, 125 | "size": 194, 126 | }, 127 | ], 128 | [ 129 | "main.js.gz", 130 | 4124, 131 | { 132 | "compressed": true, 133 | "immutable": true, 134 | "size": 4124, 135 | }, 136 | ], 137 | [ 138 | "main.js?var=8bb94238bc82ebe7567e", 139 | 16413, 140 | { 141 | "fullhash": "8bb94238bc82ebe7567e", 142 | "immutable": true, 143 | "javascriptModule": false, 144 | "related": { 145 | "gzipped": "main.js.gz", 146 | }, 147 | "size": 16413, 148 | }, 149 | ], 150 | ] 151 | `; 152 | 153 | exports[`"include" option matches snapshot for multiple \`include\` values ({Array}): errors 1`] = `[]`; 154 | 155 | exports[`"include" option matches snapshot for multiple \`include\` values ({Array}): warnings 1`] = `[]`; 156 | -------------------------------------------------------------------------------- /test/deleteOriginalAssets.test.js: -------------------------------------------------------------------------------- 1 | import CompressionPlugin from "../src/index"; 2 | 3 | import { 4 | compile, 5 | getAssetsNameAndSize, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers/index"; 10 | 11 | describe('"deleteOriginalAssets" option', () => { 12 | let compiler; 13 | 14 | beforeEach(() => { 15 | compiler = getCompiler("./entry.js"); 16 | }); 17 | 18 | it("should work and keep original assets by default", async () => { 19 | compiler = getCompiler("./entry.js"); 20 | 21 | new CompressionPlugin().apply(compiler); 22 | 23 | const stats = await compile(compiler); 24 | 25 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 26 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 27 | expect(getErrors(stats)).toMatchSnapshot("errors"); 28 | }); 29 | 30 | it("should work and keep original assets", async () => { 31 | new CompressionPlugin({ 32 | minRatio: 1, 33 | deleteOriginalAssets: false, 34 | }).apply(compiler); 35 | 36 | const stats = await compile(compiler); 37 | 38 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 39 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 40 | expect(getErrors(stats)).toMatchSnapshot("errors"); 41 | }); 42 | 43 | it("should work and delete original assets", async () => { 44 | new CompressionPlugin({ 45 | minRatio: 1, 46 | deleteOriginalAssets: true, 47 | }).apply(compiler); 48 | 49 | const stats = await compile(compiler); 50 | 51 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 52 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 53 | expect(getErrors(stats)).toMatchSnapshot("errors"); 54 | }); 55 | 56 | it("should work and delete original assets when function used", async () => { 57 | new CompressionPlugin({ 58 | minRatio: 1, 59 | deleteOriginalAssets: (name) => { 60 | if (/\.js$/.test(name)) { 61 | return true; 62 | } 63 | 64 | return false; 65 | }, 66 | }).apply(compiler); 67 | 68 | const stats = await compile(compiler); 69 | 70 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 71 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 72 | expect(getErrors(stats)).toMatchSnapshot("errors"); 73 | }); 74 | 75 | it("should work and report errors on duplicate assets", async () => { 76 | compiler = getCompiler("./entry.js"); 77 | 78 | new CompressionPlugin({ 79 | filename: "[path][base]", 80 | }).apply(compiler); 81 | 82 | const stats = await compile(compiler); 83 | 84 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 85 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 86 | expect(getErrors(stats)).toMatchSnapshot("errors"); 87 | }); 88 | 89 | it("should work and do not report errors on duplicate assets when original assets were removed", async () => { 90 | compiler = getCompiler("./entry.js"); 91 | 92 | new CompressionPlugin({ 93 | filename: "[path][base]", 94 | deleteOriginalAssets: true, 95 | }).apply(compiler); 96 | 97 | const stats = await compile(compiler); 98 | 99 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 100 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 101 | expect(getErrors(stats)).toMatchSnapshot("errors"); 102 | }); 103 | 104 | it('should delete original assets and keep source maps with option "keep-source-map"', async () => { 105 | compiler = getCompiler( 106 | "./entry.js", 107 | {}, 108 | { 109 | devtool: "source-map", 110 | }, 111 | ); 112 | 113 | new CompressionPlugin({ 114 | filename: "[path][base]", 115 | exclude: /\.map$/, 116 | deleteOriginalAssets: "keep-source-map", 117 | }).apply(compiler); 118 | 119 | const stats = await compile(compiler); 120 | 121 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 122 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 123 | expect(getErrors(stats)).toMatchSnapshot("errors"); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/filename-option.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import CompressionPlugin from "../src/index"; 4 | 5 | import { 6 | compile, 7 | getAssetsNameAndSize, 8 | getCompiler, 9 | getErrors, 10 | getWarnings, 11 | } from "./helpers/index"; 12 | 13 | describe('"filename" option', () => { 14 | let compiler; 15 | 16 | it("show work", async () => { 17 | compiler = getCompiler( 18 | "./entry.js", 19 | {}, 20 | { 21 | output: { 22 | path: path.resolve(__dirname, "./outputs"), 23 | filename: "assets/scripts/[name].js?var=[hash]#hash", 24 | chunkFilename: "assets/scripts/[id].[name].js?ver=[hash]#hash", 25 | }, 26 | }, 27 | ); 28 | 29 | new CompressionPlugin({ 30 | minRatio: 1, 31 | }).apply(compiler); 32 | 33 | const stats = await compile(compiler); 34 | 35 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 36 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 37 | expect(getErrors(stats)).toMatchSnapshot("errors"); 38 | }); 39 | 40 | it("matches snapshot for `[path][base].super-compressed.gz[query][fragment]` value ({String})", async () => { 41 | compiler = getCompiler( 42 | "./entry.js", 43 | {}, 44 | { 45 | output: { 46 | path: path.resolve(__dirname, "./outputs"), 47 | filename: "assets/js/[name].js?var=[hash]#hash", 48 | chunkFilename: "assets/js/[id].[name].js?ver=[hash]#hash", 49 | }, 50 | }, 51 | ); 52 | 53 | new CompressionPlugin({ 54 | minRatio: 1, 55 | filename: "[path][base].super-compressed.gz[query][fragment]", 56 | }).apply(compiler); 57 | 58 | const stats = await compile(compiler); 59 | 60 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 61 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 62 | expect(getErrors(stats)).toMatchSnapshot("errors"); 63 | }); 64 | 65 | it("matches snapshot for `[name][ext].super-compressed.gz[query]` value ({String})", async () => { 66 | compiler = getCompiler( 67 | "./entry.js", 68 | {}, 69 | { 70 | output: { 71 | path: path.resolve(__dirname, "./outputs"), 72 | filename: "[name].js?var=[hash]", 73 | chunkFilename: "[id].[name].js?ver=[hash]", 74 | }, 75 | }, 76 | ); 77 | 78 | new CompressionPlugin({ 79 | minRatio: 1, 80 | filename: "[name].super-compressed[ext].gz[query]", 81 | }).apply(compiler); 82 | 83 | const stats = await compile(compiler); 84 | 85 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 86 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 87 | expect(getErrors(stats)).toMatchSnapshot("errors"); 88 | }); 89 | 90 | it("matches snapshot for custom function ({Function})", async () => { 91 | compiler = getCompiler( 92 | "./entry.js", 93 | {}, 94 | { 95 | output: { 96 | path: path.resolve(__dirname, "./outputs"), 97 | filename: "[name].js?var=[hash]#hash", 98 | chunkFilename: "[id].[name].js?ver=[hash]#hash", 99 | }, 100 | }, 101 | ); 102 | 103 | new CompressionPlugin({ 104 | minRatio: 1, 105 | filename(info) { 106 | const [, , query] = /^([^?#]*)(\?[^#]*)?(#.*)?$/.exec(info.filename); 107 | 108 | return `[name][ext].gz${query || ""}`; 109 | }, 110 | }).apply(compiler); 111 | 112 | const stats = await compile(compiler); 113 | 114 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 115 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 116 | expect(getErrors(stats)).toMatchSnapshot("errors"); 117 | }); 118 | 119 | it("matches snapshot for custom function ({Function}) and custom algorithm ({Function})", async () => { 120 | compiler = getCompiler( 121 | "./entry.js", 122 | {}, 123 | { 124 | output: { 125 | path: path.resolve(__dirname, "./outputs"), 126 | filename: "[name].js?var=[hash]#hash", 127 | chunkFilename: "[id].[name].js?ver=[hash]#hash", 128 | }, 129 | }, 130 | ); 131 | 132 | new CompressionPlugin({ 133 | minRatio: 1, 134 | filename(info) { 135 | const [, , query] = /^([^?#]*)(\?[^#]*)?(#.*)?$/.exec(info.filename); 136 | 137 | return `[name][ext].gz${query || ""}`; 138 | }, 139 | algorithm(input, compressionOptions, callback) { 140 | callback(null, input); 141 | }, 142 | }).apply(compiler); 143 | 144 | const stats = await compile(compiler); 145 | 146 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 147 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 148 | expect(getErrors(stats)).toMatchSnapshot("errors"); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /test/__snapshots__/threshold-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"threshold" option matches snapshot for \`0\` value ({Number}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "related": { 11 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 12 | }, 13 | "size": 78117, 14 | "sourceFilename": "icon.png", 15 | }, 16 | ], 17 | [ 18 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 19 | 73507, 20 | { 21 | "compressed": true, 22 | "immutable": true, 23 | "size": 73507, 24 | }, 25 | ], 26 | [ 27 | "23fc1d3ac606d117e05a140e0de79806.svg", 28 | 672, 29 | { 30 | "immutable": true, 31 | "related": { 32 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 33 | }, 34 | "size": 672, 35 | "sourceFilename": "icon.svg", 36 | }, 37 | ], 38 | [ 39 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 40 | 390, 41 | { 42 | "compressed": true, 43 | "immutable": true, 44 | "size": 390, 45 | }, 46 | ], 47 | [ 48 | "async.async.55e6e9a872bcc7d4b226.js", 49 | 194, 50 | { 51 | "chunkhash": "55e6e9a872bcc7d4b226", 52 | "immutable": true, 53 | "javascriptModule": false, 54 | "related": { 55 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 56 | }, 57 | "size": 194, 58 | }, 59 | ], 60 | [ 61 | "async.async.55e6e9a872bcc7d4b226.js.gz", 62 | 136, 63 | { 64 | "compressed": true, 65 | "immutable": true, 66 | "size": 136, 67 | }, 68 | ], 69 | [ 70 | "main.4d75ca3dcb0ac1fb976b.js", 71 | 16252, 72 | { 73 | "chunkhash": "4d75ca3dcb0ac1fb976b", 74 | "immutable": true, 75 | "javascriptModule": false, 76 | "related": { 77 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 78 | }, 79 | "size": 16252, 80 | }, 81 | ], 82 | [ 83 | "main.4d75ca3dcb0ac1fb976b.js.gz", 84 | 4097, 85 | { 86 | "compressed": true, 87 | "immutable": true, 88 | "size": 4097, 89 | }, 90 | ], 91 | ] 92 | `; 93 | 94 | exports[`"threshold" option matches snapshot for \`0\` value ({Number}): errors 1`] = `[]`; 95 | 96 | exports[`"threshold" option matches snapshot for \`0\` value ({Number}): warnings 1`] = `[]`; 97 | 98 | exports[`"threshold" option matches snapshot for \`8192\` value ({Number}): assets 1`] = ` 99 | [ 100 | [ 101 | "09a1a1112c577c2794359715edfcb5ac.png", 102 | 78117, 103 | { 104 | "immutable": true, 105 | "related": { 106 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 107 | }, 108 | "size": 78117, 109 | "sourceFilename": "icon.png", 110 | }, 111 | ], 112 | [ 113 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 114 | 73507, 115 | { 116 | "compressed": true, 117 | "immutable": true, 118 | "size": 73507, 119 | }, 120 | ], 121 | [ 122 | "23fc1d3ac606d117e05a140e0de79806.svg", 123 | 672, 124 | { 125 | "immutable": true, 126 | "size": 672, 127 | "sourceFilename": "icon.svg", 128 | }, 129 | ], 130 | [ 131 | "async.async.55e6e9a872bcc7d4b226.js", 132 | 194, 133 | { 134 | "chunkhash": "55e6e9a872bcc7d4b226", 135 | "immutable": true, 136 | "javascriptModule": false, 137 | "size": 194, 138 | }, 139 | ], 140 | [ 141 | "main.4d75ca3dcb0ac1fb976b.js", 142 | 16252, 143 | { 144 | "chunkhash": "4d75ca3dcb0ac1fb976b", 145 | "immutable": true, 146 | "javascriptModule": false, 147 | "related": { 148 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 149 | }, 150 | "size": 16252, 151 | }, 152 | ], 153 | [ 154 | "main.4d75ca3dcb0ac1fb976b.js.gz", 155 | 4097, 156 | { 157 | "compressed": true, 158 | "immutable": true, 159 | "size": 4097, 160 | }, 161 | ], 162 | ] 163 | `; 164 | 165 | exports[`"threshold" option matches snapshot for \`8192\` value ({Number}): errors 1`] = `[]`; 166 | 167 | exports[`"threshold" option matches snapshot for \`8192\` value ({Number}): warnings 1`] = `[]`; 168 | 169 | exports[`"threshold" option should compress all assets excluding assets with "0" bytes original size: assets 1`] = ` 170 | [ 171 | [ 172 | "31d6cfe0d16ae931b73c59d7e0c089c0.txt", 173 | 0, 174 | { 175 | "immutable": true, 176 | "size": 0, 177 | "sourceFilename": "empty.txt", 178 | }, 179 | ], 180 | [ 181 | "main.d6f9aecb01ad9f44759c.js", 182 | 5311, 183 | { 184 | "chunkhash": "d6f9aecb01ad9f44759c", 185 | "immutable": true, 186 | "javascriptModule": false, 187 | "related": { 188 | "gzipped": "main.d6f9aecb01ad9f44759c.js.gz", 189 | }, 190 | "size": 5311, 191 | }, 192 | ], 193 | [ 194 | "main.d6f9aecb01ad9f44759c.js.gz", 195 | 1595, 196 | { 197 | "compressed": true, 198 | "immutable": true, 199 | "size": 1595, 200 | }, 201 | ], 202 | ] 203 | `; 204 | 205 | exports[`"threshold" option should compress all assets excluding assets with "0" bytes original size: errors 1`] = `[]`; 206 | 207 | exports[`"threshold" option should compress all assets excluding assets with "0" bytes original size: warnings 1`] = `[]`; 208 | 209 | exports[`"threshold" option should compress all assets including assets with "0" bytes original size: assets 1`] = ` 210 | [ 211 | [ 212 | "31d6cfe0d16ae931b73c59d7e0c089c0.txt", 213 | 0, 214 | { 215 | "immutable": true, 216 | "related": { 217 | "gzipped": "31d6cfe0d16ae931b73c59d7e0c089c0.txt.gz", 218 | }, 219 | "size": 0, 220 | "sourceFilename": "empty.txt", 221 | }, 222 | ], 223 | [ 224 | "31d6cfe0d16ae931b73c59d7e0c089c0.txt.gz", 225 | 20, 226 | { 227 | "compressed": true, 228 | "immutable": true, 229 | "size": 20, 230 | }, 231 | ], 232 | [ 233 | "main.d6f9aecb01ad9f44759c.js", 234 | 5311, 235 | { 236 | "chunkhash": "d6f9aecb01ad9f44759c", 237 | "immutable": true, 238 | "javascriptModule": false, 239 | "related": { 240 | "gzipped": "main.d6f9aecb01ad9f44759c.js.gz", 241 | }, 242 | "size": 5311, 243 | }, 244 | ], 245 | [ 246 | "main.d6f9aecb01ad9f44759c.js.gz", 247 | 1595, 248 | { 249 | "compressed": true, 250 | "immutable": true, 251 | "size": 1595, 252 | }, 253 | ], 254 | ] 255 | `; 256 | 257 | exports[`"threshold" option should compress all assets including assets with "0" bytes original size: errors 1`] = `[]`; 258 | 259 | exports[`"threshold" option should compress all assets including assets with "0" bytes original size: warnings 1`] = `[]`; 260 | -------------------------------------------------------------------------------- /test/__snapshots__/test-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"test" option matches snapshot for a single \`test\` value ({RegExp}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "related": { 11 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 12 | }, 13 | "size": 78117, 14 | "sourceFilename": "icon.png", 15 | }, 16 | ], 17 | [ 18 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 19 | 73507, 20 | { 21 | "compressed": true, 22 | "immutable": true, 23 | "size": 73507, 24 | }, 25 | ], 26 | [ 27 | "23fc1d3ac606d117e05a140e0de79806.svg", 28 | 672, 29 | { 30 | "immutable": true, 31 | "size": 672, 32 | "sourceFilename": "icon.svg", 33 | }, 34 | ], 35 | [ 36 | "async.async.js?ver=8bb94238bc82ebe7567e", 37 | 194, 38 | { 39 | "fullhash": "8bb94238bc82ebe7567e", 40 | "immutable": true, 41 | "javascriptModule": false, 42 | "size": 194, 43 | }, 44 | ], 45 | [ 46 | "main.js?var=8bb94238bc82ebe7567e", 47 | 16413, 48 | { 49 | "fullhash": "8bb94238bc82ebe7567e", 50 | "immutable": true, 51 | "javascriptModule": false, 52 | "size": 16413, 53 | }, 54 | ], 55 | ] 56 | `; 57 | 58 | exports[`"test" option matches snapshot for a single \`test\` value ({RegExp}): errors 1`] = `[]`; 59 | 60 | exports[`"test" option matches snapshot for a single \`test\` value ({RegExp}): warnings 1`] = `[]`; 61 | 62 | exports[`"test" option matches snapshot for multiple \`test\` values ({Array}): assets 1`] = ` 63 | [ 64 | [ 65 | "09a1a1112c577c2794359715edfcb5ac.png", 66 | 78117, 67 | { 68 | "immutable": true, 69 | "related": { 70 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 71 | }, 72 | "size": 78117, 73 | "sourceFilename": "icon.png", 74 | }, 75 | ], 76 | [ 77 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 78 | 73507, 79 | { 80 | "compressed": true, 81 | "immutable": true, 82 | "size": 73507, 83 | }, 84 | ], 85 | [ 86 | "23fc1d3ac606d117e05a140e0de79806.svg", 87 | 672, 88 | { 89 | "immutable": true, 90 | "related": { 91 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 92 | }, 93 | "size": 672, 94 | "sourceFilename": "icon.svg", 95 | }, 96 | ], 97 | [ 98 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 99 | 390, 100 | { 101 | "compressed": true, 102 | "immutable": true, 103 | "size": 390, 104 | }, 105 | ], 106 | [ 107 | "async.async.js?ver=8bb94238bc82ebe7567e", 108 | 194, 109 | { 110 | "fullhash": "8bb94238bc82ebe7567e", 111 | "immutable": true, 112 | "javascriptModule": false, 113 | "size": 194, 114 | }, 115 | ], 116 | [ 117 | "main.js?var=8bb94238bc82ebe7567e", 118 | 16413, 119 | { 120 | "fullhash": "8bb94238bc82ebe7567e", 121 | "immutable": true, 122 | "javascriptModule": false, 123 | "size": 16413, 124 | }, 125 | ], 126 | ] 127 | `; 128 | 129 | exports[`"test" option matches snapshot for multiple \`test\` values ({Array}): errors 1`] = `[]`; 130 | 131 | exports[`"test" option matches snapshot for multiple \`test\` values ({Array}): warnings 1`] = `[]`; 132 | 133 | exports[`"test" option matches snapshot with empty \`test\` value: assets 1`] = ` 134 | [ 135 | [ 136 | "09a1a1112c577c2794359715edfcb5ac.png", 137 | 78117, 138 | { 139 | "immutable": true, 140 | "related": { 141 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 142 | }, 143 | "size": 78117, 144 | "sourceFilename": "icon.png", 145 | }, 146 | ], 147 | [ 148 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 149 | 73507, 150 | { 151 | "compressed": true, 152 | "immutable": true, 153 | "size": 73507, 154 | }, 155 | ], 156 | [ 157 | "23fc1d3ac606d117e05a140e0de79806.svg", 158 | 672, 159 | { 160 | "immutable": true, 161 | "related": { 162 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 163 | }, 164 | "size": 672, 165 | "sourceFilename": "icon.svg", 166 | }, 167 | ], 168 | [ 169 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 170 | 390, 171 | { 172 | "compressed": true, 173 | "immutable": true, 174 | "size": 390, 175 | }, 176 | ], 177 | [ 178 | "async.async.js.gz", 179 | 136, 180 | { 181 | "compressed": true, 182 | "immutable": true, 183 | "size": 136, 184 | }, 185 | ], 186 | [ 187 | "async.async.js?ver=8bb94238bc82ebe7567e", 188 | 194, 189 | { 190 | "fullhash": "8bb94238bc82ebe7567e", 191 | "immutable": true, 192 | "javascriptModule": false, 193 | "related": { 194 | "gzipped": "async.async.js.gz", 195 | }, 196 | "size": 194, 197 | }, 198 | ], 199 | [ 200 | "main.js.gz", 201 | 4124, 202 | { 203 | "compressed": true, 204 | "immutable": true, 205 | "size": 4124, 206 | }, 207 | ], 208 | [ 209 | "main.js?var=8bb94238bc82ebe7567e", 210 | 16413, 211 | { 212 | "fullhash": "8bb94238bc82ebe7567e", 213 | "immutable": true, 214 | "javascriptModule": false, 215 | "related": { 216 | "gzipped": "main.js.gz", 217 | }, 218 | "size": 16413, 219 | }, 220 | ], 221 | ] 222 | `; 223 | 224 | exports[`"test" option matches snapshot with empty \`test\` value: errors 1`] = `[]`; 225 | 226 | exports[`"test" option matches snapshot with empty \`test\` value: warnings 1`] = `[]`; 227 | 228 | exports[`"test" option should work when no asset to compress: assets 1`] = ` 229 | [ 230 | [ 231 | "09a1a1112c577c2794359715edfcb5ac.png", 232 | 78117, 233 | { 234 | "immutable": true, 235 | "size": 78117, 236 | "sourceFilename": "icon.png", 237 | }, 238 | ], 239 | [ 240 | "23fc1d3ac606d117e05a140e0de79806.svg", 241 | 672, 242 | { 243 | "immutable": true, 244 | "size": 672, 245 | "sourceFilename": "icon.svg", 246 | }, 247 | ], 248 | [ 249 | "async.async.js?ver=8bb94238bc82ebe7567e", 250 | 194, 251 | { 252 | "fullhash": "8bb94238bc82ebe7567e", 253 | "immutable": true, 254 | "javascriptModule": false, 255 | "size": 194, 256 | }, 257 | ], 258 | [ 259 | "main.js?var=8bb94238bc82ebe7567e", 260 | 16413, 261 | { 262 | "fullhash": "8bb94238bc82ebe7567e", 263 | "immutable": true, 264 | "javascriptModule": false, 265 | "size": 16413, 266 | }, 267 | ], 268 | ] 269 | `; 270 | 271 | exports[`"test" option should work when no asset to compress: errors 1`] = `[]`; 272 | 273 | exports[`"test" option should work when no asset to compress: warnings 1`] = `[]`; 274 | -------------------------------------------------------------------------------- /test/__snapshots__/algorithm.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"algorithm" option matches snapshot for \`gzip\` value ({String}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "related": { 11 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 12 | }, 13 | "size": 78117, 14 | "sourceFilename": "icon.png", 15 | }, 16 | ], 17 | [ 18 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 19 | 73507, 20 | { 21 | "compressed": true, 22 | "immutable": true, 23 | "size": 73507, 24 | }, 25 | ], 26 | [ 27 | "23fc1d3ac606d117e05a140e0de79806.svg", 28 | 672, 29 | { 30 | "immutable": true, 31 | "related": { 32 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 33 | }, 34 | "size": 672, 35 | "sourceFilename": "icon.svg", 36 | }, 37 | ], 38 | [ 39 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 40 | 390, 41 | { 42 | "compressed": true, 43 | "immutable": true, 44 | "size": 390, 45 | }, 46 | ], 47 | [ 48 | "async.async.55e6e9a872bcc7d4b226.js", 49 | 194, 50 | { 51 | "chunkhash": "55e6e9a872bcc7d4b226", 52 | "immutable": true, 53 | "javascriptModule": false, 54 | "related": { 55 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 56 | }, 57 | "size": 194, 58 | }, 59 | ], 60 | [ 61 | "async.async.55e6e9a872bcc7d4b226.js.gz", 62 | 136, 63 | { 64 | "compressed": true, 65 | "immutable": true, 66 | "size": 136, 67 | }, 68 | ], 69 | [ 70 | "main.4d75ca3dcb0ac1fb976b.js", 71 | 16252, 72 | { 73 | "chunkhash": "4d75ca3dcb0ac1fb976b", 74 | "immutable": true, 75 | "javascriptModule": false, 76 | "related": { 77 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 78 | }, 79 | "size": 16252, 80 | }, 81 | ], 82 | [ 83 | "main.4d75ca3dcb0ac1fb976b.js.gz", 84 | 4097, 85 | { 86 | "compressed": true, 87 | "immutable": true, 88 | "size": 4097, 89 | }, 90 | ], 91 | ] 92 | `; 93 | 94 | exports[`"algorithm" option matches snapshot for \`gzip\` value ({String}): errors 1`] = `[]`; 95 | 96 | exports[`"algorithm" option matches snapshot for \`gzip\` value ({String}): warnings 1`] = `[]`; 97 | 98 | exports[`"algorithm" option matches snapshot for \`unknown\` value ({String}) 1`] = `"Algorithm "unknown" is not found in "zlib""`; 99 | 100 | exports[`"algorithm" option matches snapshot for custom function ({Function}): assets 1`] = ` 101 | [ 102 | [ 103 | "09a1a1112c577c2794359715edfcb5ac.png", 104 | 78117, 105 | { 106 | "immutable": true, 107 | "related": { 108 | "gzed": "09a1a1112c577c2794359715edfcb5ac.png.gz", 109 | }, 110 | "size": 78117, 111 | "sourceFilename": "icon.png", 112 | }, 113 | ], 114 | [ 115 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 116 | 78117, 117 | { 118 | "compressed": true, 119 | "immutable": true, 120 | "size": 78117, 121 | }, 122 | ], 123 | [ 124 | "23fc1d3ac606d117e05a140e0de79806.svg", 125 | 672, 126 | { 127 | "immutable": true, 128 | "related": { 129 | "gzed": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 130 | }, 131 | "size": 672, 132 | "sourceFilename": "icon.svg", 133 | }, 134 | ], 135 | [ 136 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 137 | 672, 138 | { 139 | "compressed": true, 140 | "immutable": true, 141 | "size": 672, 142 | }, 143 | ], 144 | [ 145 | "async.async.55e6e9a872bcc7d4b226.js", 146 | 194, 147 | { 148 | "chunkhash": "55e6e9a872bcc7d4b226", 149 | "immutable": true, 150 | "javascriptModule": false, 151 | "related": { 152 | "gzed": "async.async.55e6e9a872bcc7d4b226.js.gz", 153 | }, 154 | "size": 194, 155 | }, 156 | ], 157 | [ 158 | "async.async.55e6e9a872bcc7d4b226.js.gz", 159 | 194, 160 | { 161 | "compressed": true, 162 | "immutable": true, 163 | "size": 194, 164 | }, 165 | ], 166 | [ 167 | "main.4d75ca3dcb0ac1fb976b.js", 168 | 16252, 169 | { 170 | "chunkhash": "4d75ca3dcb0ac1fb976b", 171 | "immutable": true, 172 | "javascriptModule": false, 173 | "related": { 174 | "gzed": "main.4d75ca3dcb0ac1fb976b.js.gz", 175 | }, 176 | "size": 16252, 177 | }, 178 | ], 179 | [ 180 | "main.4d75ca3dcb0ac1fb976b.js.gz", 181 | 16252, 182 | { 183 | "compressed": true, 184 | "immutable": true, 185 | "size": 16252, 186 | }, 187 | ], 188 | ] 189 | `; 190 | 191 | exports[`"algorithm" option matches snapshot for custom function ({Function}): compressionOptions 1`] = `{}`; 192 | 193 | exports[`"algorithm" option matches snapshot for custom function ({Function}): compressionOptions 2`] = `{}`; 194 | 195 | exports[`"algorithm" option matches snapshot for custom function ({Function}): compressionOptions 3`] = `{}`; 196 | 197 | exports[`"algorithm" option matches snapshot for custom function ({Function}): compressionOptions 4`] = `{}`; 198 | 199 | exports[`"algorithm" option matches snapshot for custom function ({Function}): errors 1`] = `[]`; 200 | 201 | exports[`"algorithm" option matches snapshot for custom function ({Function}): warnings 1`] = `[]`; 202 | 203 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): assets 1`] = ` 204 | [ 205 | [ 206 | "09a1a1112c577c2794359715edfcb5ac.png", 207 | 78117, 208 | { 209 | "immutable": true, 210 | "size": 78117, 211 | "sourceFilename": "icon.png", 212 | }, 213 | ], 214 | [ 215 | "23fc1d3ac606d117e05a140e0de79806.svg", 216 | 672, 217 | { 218 | "immutable": true, 219 | "size": 672, 220 | "sourceFilename": "icon.svg", 221 | }, 222 | ], 223 | [ 224 | "async.async.55e6e9a872bcc7d4b226.js", 225 | 194, 226 | { 227 | "chunkhash": "55e6e9a872bcc7d4b226", 228 | "immutable": true, 229 | "javascriptModule": false, 230 | "size": 194, 231 | }, 232 | ], 233 | [ 234 | "main.4d75ca3dcb0ac1fb976b.js", 235 | 16252, 236 | { 237 | "chunkhash": "4d75ca3dcb0ac1fb976b", 238 | "immutable": true, 239 | "javascriptModule": false, 240 | "size": 16252, 241 | }, 242 | ], 243 | ] 244 | `; 245 | 246 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): compressionOptions 1`] = `{}`; 247 | 248 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): compressionOptions 2`] = `{}`; 249 | 250 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): compressionOptions 3`] = `{}`; 251 | 252 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): compressionOptions 4`] = `{}`; 253 | 254 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): errors 1`] = ` 255 | [ 256 | "Error", 257 | "Error", 258 | "Error", 259 | "Error", 260 | ] 261 | `; 262 | 263 | exports[`"algorithm" option matches snapshot for custom function with error ({Function}): warnings 1`] = `[]`; 264 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export = CompressionPlugin; 2 | /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ 3 | /** @typedef {import("webpack").AssetInfo} AssetInfo */ 4 | /** @typedef {import("webpack").Compiler} Compiler */ 5 | /** @typedef {import("webpack").PathData} PathData */ 6 | /** @typedef {import("webpack").WebpackPluginInstance} WebpackPluginInstance */ 7 | /** @typedef {import("webpack").Compilation} Compilation */ 8 | /** @typedef {import("webpack").sources.Source} Source */ 9 | /** @typedef {import("webpack").Asset} Asset */ 10 | /** @typedef {import("webpack").WebpackError} WebpackError */ 11 | /** 12 | * @template T 13 | * @typedef {T | { valueOf(): T }} WithImplicitCoercion 14 | */ 15 | /** @typedef {RegExp | string} Rule */ 16 | /** @typedef {Rule[] | Rule} Rules */ 17 | /** @typedef {any} EXPECTED_ANY */ 18 | /** 19 | * @typedef {{ [key: string]: EXPECTED_ANY }} CustomOptions 20 | */ 21 | /** 22 | * @template T 23 | * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType 24 | */ 25 | /** 26 | * @template T 27 | * @typedef {InferDefaultType} CompressionOptions 28 | */ 29 | /** 30 | * @template T 31 | * @callback AlgorithmFunction 32 | * @param {Buffer} input 33 | * @param {CompressionOptions} options 34 | * @param {(error: Error | null | undefined, result: WithImplicitCoercion | Uint8Array | ReadonlyArray | WithImplicitCoercion | string> | WithImplicitCoercion | { [Symbol.toPrimitive](hint: 'string'): string }) => void} callback 35 | */ 36 | /** 37 | * @typedef {string | ((fileData: PathData) => string)} Filename 38 | */ 39 | /** 40 | * @typedef {boolean | "keep-source-map" | ((name: string) => boolean)} DeleteOriginalAssets 41 | */ 42 | /** 43 | * @template T 44 | * @typedef {object} BasePluginOptions 45 | * @property {Rules=} test include all assets that pass test assertion 46 | * @property {Rules=} include include all assets matching any of these conditions 47 | * @property {Rules=} exclude exclude all assets matching any of these conditions 48 | * @property {number=} threshold only assets bigger than this size are processed, in bytes 49 | * @property {number=} minRatio only assets that compress better than this ratio are processed (`minRatio = Compressed Size / Original Size`) 50 | * @property {DeleteOriginalAssets=} deleteOriginalAssets whether to delete the original assets or not 51 | * @property {Filename=} filename the target asset filename 52 | */ 53 | /** 54 | * @typedef {import("zlib").ZlibOptions} ZlibOptions 55 | */ 56 | /** 57 | * @template T 58 | * @typedef {T extends ZlibOptions ? { algorithm?: string | AlgorithmFunction | undefined, compressionOptions?: CompressionOptions | undefined } : { algorithm: string | AlgorithmFunction, compressionOptions?: CompressionOptions | undefined }} DefinedDefaultAlgorithmAndOptions 59 | */ 60 | /** 61 | * @template T 62 | * @typedef {BasePluginOptions & { algorithm: string | AlgorithmFunction, compressionOptions: CompressionOptions, threshold: number, minRatio: number, deleteOriginalAssets: DeleteOriginalAssets, filename: Filename }} InternalPluginOptions 63 | */ 64 | /** 65 | * @template [T=ZlibOptions] 66 | * @implements WebpackPluginInstance 67 | */ 68 | declare class CompressionPlugin 69 | implements WebpackPluginInstance 70 | { 71 | /** 72 | * @param {(BasePluginOptions & DefinedDefaultAlgorithmAndOptions)=} options options 73 | */ 74 | constructor( 75 | options?: 76 | | (BasePluginOptions & DefinedDefaultAlgorithmAndOptions) 77 | | undefined, 78 | ); 79 | /** 80 | * @private 81 | * @type {InternalPluginOptions} 82 | */ 83 | private options; 84 | /** 85 | * @private 86 | * @type {AlgorithmFunction} 87 | */ 88 | private algorithm; 89 | /** 90 | * @private 91 | * @param {Buffer} input input 92 | * @returns {Promise} compressed buffer 93 | */ 94 | private runCompressionAlgorithm; 95 | /** 96 | * @private 97 | * @param {Compiler} compiler compiler 98 | * @param {Compilation} compilation compilation 99 | * @param {Record} assets assets 100 | * @returns {Promise} 101 | */ 102 | private compress; 103 | /** 104 | * @param {Compiler} compiler compiler 105 | * @returns {void} 106 | */ 107 | apply(compiler: Compiler): void; 108 | } 109 | declare namespace CompressionPlugin { 110 | export { 111 | Schema, 112 | AssetInfo, 113 | Compiler, 114 | PathData, 115 | WebpackPluginInstance, 116 | Compilation, 117 | Source, 118 | Asset, 119 | WebpackError, 120 | WithImplicitCoercion, 121 | Rule, 122 | Rules, 123 | EXPECTED_ANY, 124 | CustomOptions, 125 | InferDefaultType, 126 | CompressionOptions, 127 | AlgorithmFunction, 128 | Filename, 129 | DeleteOriginalAssets, 130 | BasePluginOptions, 131 | ZlibOptions, 132 | DefinedDefaultAlgorithmAndOptions, 133 | InternalPluginOptions, 134 | }; 135 | } 136 | type Schema = import("schema-utils/declarations/validate").Schema; 137 | type AssetInfo = import("webpack").AssetInfo; 138 | type Compiler = import("webpack").Compiler; 139 | type PathData = import("webpack").PathData; 140 | type WebpackPluginInstance = import("webpack").WebpackPluginInstance; 141 | type Compilation = import("webpack").Compilation; 142 | type Source = import("webpack").sources.Source; 143 | type Asset = import("webpack").Asset; 144 | type WebpackError = import("webpack").WebpackError; 145 | type WithImplicitCoercion = 146 | | T 147 | | { 148 | valueOf(): T; 149 | }; 150 | type Rule = RegExp | string; 151 | type Rules = Rule[] | Rule; 152 | type EXPECTED_ANY = any; 153 | type CustomOptions = { 154 | [key: string]: EXPECTED_ANY; 155 | }; 156 | type InferDefaultType = T extends infer U ? U : CustomOptions; 157 | type CompressionOptions = InferDefaultType; 158 | type AlgorithmFunction = ( 159 | input: Buffer, 160 | options: CompressionOptions, 161 | callback: ( 162 | error: Error | null | undefined, 163 | result: 164 | | WithImplicitCoercion 165 | | Uint8Array 166 | | ReadonlyArray 167 | | WithImplicitCoercion | string> 168 | | WithImplicitCoercion 169 | | { 170 | [Symbol.toPrimitive](hint: "string"): string; 171 | }, 172 | ) => void, 173 | ) => any; 174 | type Filename = string | ((fileData: PathData) => string); 175 | type DeleteOriginalAssets = 176 | | boolean 177 | | "keep-source-map" 178 | | ((name: string) => boolean); 179 | type BasePluginOptions = { 180 | /** 181 | * include all assets that pass test assertion 182 | */ 183 | test?: Rules | undefined; 184 | /** 185 | * include all assets matching any of these conditions 186 | */ 187 | include?: Rules | undefined; 188 | /** 189 | * exclude all assets matching any of these conditions 190 | */ 191 | exclude?: Rules | undefined; 192 | /** 193 | * only assets bigger than this size are processed, in bytes 194 | */ 195 | threshold?: number | undefined; 196 | /** 197 | * only assets that compress better than this ratio are processed (`minRatio = Compressed Size / Original Size`) 198 | */ 199 | minRatio?: number | undefined; 200 | /** 201 | * whether to delete the original assets or not 202 | */ 203 | deleteOriginalAssets?: DeleteOriginalAssets | undefined; 204 | /** 205 | * the target asset filename 206 | */ 207 | filename?: Filename | undefined; 208 | }; 209 | type ZlibOptions = import("zlib").ZlibOptions; 210 | type DefinedDefaultAlgorithmAndOptions = T extends ZlibOptions 211 | ? { 212 | algorithm?: string | AlgorithmFunction | undefined; 213 | compressionOptions?: CompressionOptions | undefined; 214 | } 215 | : { 216 | algorithm: string | AlgorithmFunction; 217 | compressionOptions?: CompressionOptions | undefined; 218 | }; 219 | type InternalPluginOptions = BasePluginOptions & { 220 | algorithm: string | AlgorithmFunction; 221 | compressionOptions: CompressionOptions; 222 | threshold: number; 223 | minRatio: number; 224 | deleteOriginalAssets: DeleteOriginalAssets; 225 | filename: Filename; 226 | }; 227 | -------------------------------------------------------------------------------- /test/__snapshots__/deleteOriginalAssets.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"deleteOriginalAssets" option should delete original assets and keep source maps with option "keep-source-map": assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "size": 78117, 11 | "sourceFilename": "icon.png", 12 | }, 13 | ], 14 | [ 15 | "23fc1d3ac606d117e05a140e0de79806.svg", 16 | 390, 17 | { 18 | "compressed": true, 19 | "immutable": true, 20 | "size": 390, 21 | }, 22 | ], 23 | [ 24 | "async.async.5ea58b5a4311bcf28d3b.js", 25 | 136, 26 | { 27 | "compressed": true, 28 | "immutable": true, 29 | "size": 136, 30 | }, 31 | ], 32 | [ 33 | "main.0a8b31f817fe5c5f8985.js", 34 | 4144, 35 | { 36 | "compressed": true, 37 | "immutable": true, 38 | "size": 4144, 39 | }, 40 | ], 41 | [ 42 | "main.0a8b31f817fe5c5f8985.js.map", 43 | 13280, 44 | { 45 | "development": true, 46 | "size": 13280, 47 | }, 48 | ], 49 | ] 50 | `; 51 | 52 | exports[`"deleteOriginalAssets" option should delete original assets and keep source maps with option "keep-source-map": errors 1`] = `[]`; 53 | 54 | exports[`"deleteOriginalAssets" option should delete original assets and keep source maps with option "keep-source-map": warnings 1`] = `[]`; 55 | 56 | exports[`"deleteOriginalAssets" option should work and delete original assets when function used: assets 1`] = ` 57 | [ 58 | [ 59 | "09a1a1112c577c2794359715edfcb5ac.png", 60 | 78117, 61 | { 62 | "immutable": true, 63 | "size": 78117, 64 | "sourceFilename": "icon.png", 65 | }, 66 | ], 67 | [ 68 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 69 | 73507, 70 | { 71 | "compressed": true, 72 | "immutable": true, 73 | "size": 73507, 74 | }, 75 | ], 76 | [ 77 | "23fc1d3ac606d117e05a140e0de79806.svg", 78 | 672, 79 | { 80 | "immutable": true, 81 | "size": 672, 82 | "sourceFilename": "icon.svg", 83 | }, 84 | ], 85 | [ 86 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 87 | 390, 88 | { 89 | "compressed": true, 90 | "immutable": true, 91 | "size": 390, 92 | }, 93 | ], 94 | [ 95 | "async.async.55e6e9a872bcc7d4b226.js.gz", 96 | 136, 97 | { 98 | "compressed": true, 99 | "immutable": true, 100 | "size": 136, 101 | }, 102 | ], 103 | [ 104 | "main.4d75ca3dcb0ac1fb976b.js.gz", 105 | 4097, 106 | { 107 | "compressed": true, 108 | "immutable": true, 109 | "size": 4097, 110 | }, 111 | ], 112 | ] 113 | `; 114 | 115 | exports[`"deleteOriginalAssets" option should work and delete original assets when function used: errors 1`] = `[]`; 116 | 117 | exports[`"deleteOriginalAssets" option should work and delete original assets when function used: warnings 1`] = `[]`; 118 | 119 | exports[`"deleteOriginalAssets" option should work and delete original assets: assets 1`] = ` 120 | [ 121 | [ 122 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 123 | 73507, 124 | { 125 | "compressed": true, 126 | "immutable": true, 127 | "size": 73507, 128 | }, 129 | ], 130 | [ 131 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 132 | 390, 133 | { 134 | "compressed": true, 135 | "immutable": true, 136 | "size": 390, 137 | }, 138 | ], 139 | [ 140 | "async.async.55e6e9a872bcc7d4b226.js.gz", 141 | 136, 142 | { 143 | "compressed": true, 144 | "immutable": true, 145 | "size": 136, 146 | }, 147 | ], 148 | [ 149 | "main.4d75ca3dcb0ac1fb976b.js.gz", 150 | 4097, 151 | { 152 | "compressed": true, 153 | "immutable": true, 154 | "size": 4097, 155 | }, 156 | ], 157 | ] 158 | `; 159 | 160 | exports[`"deleteOriginalAssets" option should work and delete original assets: errors 1`] = `[]`; 161 | 162 | exports[`"deleteOriginalAssets" option should work and delete original assets: warnings 1`] = `[]`; 163 | 164 | exports[`"deleteOriginalAssets" option should work and do not report errors on duplicate assets when original assets were removed: assets 1`] = ` 165 | [ 166 | [ 167 | "09a1a1112c577c2794359715edfcb5ac.png", 168 | 78117, 169 | { 170 | "immutable": true, 171 | "size": 78117, 172 | "sourceFilename": "icon.png", 173 | }, 174 | ], 175 | [ 176 | "23fc1d3ac606d117e05a140e0de79806.svg", 177 | 390, 178 | { 179 | "compressed": true, 180 | "immutable": true, 181 | "size": 390, 182 | }, 183 | ], 184 | [ 185 | "async.async.55e6e9a872bcc7d4b226.js", 186 | 136, 187 | { 188 | "compressed": true, 189 | "immutable": true, 190 | "size": 136, 191 | }, 192 | ], 193 | [ 194 | "main.4d75ca3dcb0ac1fb976b.js", 195 | 4097, 196 | { 197 | "compressed": true, 198 | "immutable": true, 199 | "size": 4097, 200 | }, 201 | ], 202 | ] 203 | `; 204 | 205 | exports[`"deleteOriginalAssets" option should work and do not report errors on duplicate assets when original assets were removed: errors 1`] = `[]`; 206 | 207 | exports[`"deleteOriginalAssets" option should work and do not report errors on duplicate assets when original assets were removed: warnings 1`] = `[]`; 208 | 209 | exports[`"deleteOriginalAssets" option should work and keep original assets by default: assets 1`] = ` 210 | [ 211 | [ 212 | "09a1a1112c577c2794359715edfcb5ac.png", 213 | 78117, 214 | { 215 | "immutable": true, 216 | "size": 78117, 217 | "sourceFilename": "icon.png", 218 | }, 219 | ], 220 | [ 221 | "23fc1d3ac606d117e05a140e0de79806.svg", 222 | 672, 223 | { 224 | "immutable": true, 225 | "related": { 226 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 227 | }, 228 | "size": 672, 229 | "sourceFilename": "icon.svg", 230 | }, 231 | ], 232 | [ 233 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 234 | 390, 235 | { 236 | "compressed": true, 237 | "immutable": true, 238 | "size": 390, 239 | }, 240 | ], 241 | [ 242 | "async.async.55e6e9a872bcc7d4b226.js", 243 | 194, 244 | { 245 | "chunkhash": "55e6e9a872bcc7d4b226", 246 | "immutable": true, 247 | "javascriptModule": false, 248 | "related": { 249 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 250 | }, 251 | "size": 194, 252 | }, 253 | ], 254 | [ 255 | "async.async.55e6e9a872bcc7d4b226.js.gz", 256 | 136, 257 | { 258 | "compressed": true, 259 | "immutable": true, 260 | "size": 136, 261 | }, 262 | ], 263 | [ 264 | "main.4d75ca3dcb0ac1fb976b.js", 265 | 16252, 266 | { 267 | "chunkhash": "4d75ca3dcb0ac1fb976b", 268 | "immutable": true, 269 | "javascriptModule": false, 270 | "related": { 271 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 272 | }, 273 | "size": 16252, 274 | }, 275 | ], 276 | [ 277 | "main.4d75ca3dcb0ac1fb976b.js.gz", 278 | 4097, 279 | { 280 | "compressed": true, 281 | "immutable": true, 282 | "size": 4097, 283 | }, 284 | ], 285 | ] 286 | `; 287 | 288 | exports[`"deleteOriginalAssets" option should work and keep original assets by default: errors 1`] = `[]`; 289 | 290 | exports[`"deleteOriginalAssets" option should work and keep original assets by default: warnings 1`] = `[]`; 291 | 292 | exports[`"deleteOriginalAssets" option should work and keep original assets: assets 1`] = ` 293 | [ 294 | [ 295 | "09a1a1112c577c2794359715edfcb5ac.png", 296 | 78117, 297 | { 298 | "immutable": true, 299 | "related": { 300 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 301 | }, 302 | "size": 78117, 303 | "sourceFilename": "icon.png", 304 | }, 305 | ], 306 | [ 307 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 308 | 73507, 309 | { 310 | "compressed": true, 311 | "immutable": true, 312 | "size": 73507, 313 | }, 314 | ], 315 | [ 316 | "23fc1d3ac606d117e05a140e0de79806.svg", 317 | 672, 318 | { 319 | "immutable": true, 320 | "related": { 321 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 322 | }, 323 | "size": 672, 324 | "sourceFilename": "icon.svg", 325 | }, 326 | ], 327 | [ 328 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 329 | 390, 330 | { 331 | "compressed": true, 332 | "immutable": true, 333 | "size": 390, 334 | }, 335 | ], 336 | [ 337 | "async.async.55e6e9a872bcc7d4b226.js", 338 | 194, 339 | { 340 | "chunkhash": "55e6e9a872bcc7d4b226", 341 | "immutable": true, 342 | "javascriptModule": false, 343 | "related": { 344 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 345 | }, 346 | "size": 194, 347 | }, 348 | ], 349 | [ 350 | "async.async.55e6e9a872bcc7d4b226.js.gz", 351 | 136, 352 | { 353 | "compressed": true, 354 | "immutable": true, 355 | "size": 136, 356 | }, 357 | ], 358 | [ 359 | "main.4d75ca3dcb0ac1fb976b.js", 360 | 16252, 361 | { 362 | "chunkhash": "4d75ca3dcb0ac1fb976b", 363 | "immutable": true, 364 | "javascriptModule": false, 365 | "related": { 366 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 367 | }, 368 | "size": 16252, 369 | }, 370 | ], 371 | [ 372 | "main.4d75ca3dcb0ac1fb976b.js.gz", 373 | 4097, 374 | { 375 | "compressed": true, 376 | "immutable": true, 377 | "size": 4097, 378 | }, 379 | ], 380 | ] 381 | `; 382 | 383 | exports[`"deleteOriginalAssets" option should work and keep original assets: errors 1`] = `[]`; 384 | 385 | exports[`"deleteOriginalAssets" option should work and keep original assets: warnings 1`] = `[]`; 386 | 387 | exports[`"deleteOriginalAssets" option should work and report errors on duplicate assets: assets 1`] = ` 388 | [ 389 | [ 390 | "09a1a1112c577c2794359715edfcb5ac.png", 391 | 78117, 392 | { 393 | "immutable": true, 394 | "size": 78117, 395 | "sourceFilename": "icon.png", 396 | }, 397 | ], 398 | [ 399 | "23fc1d3ac606d117e05a140e0de79806.svg", 400 | 390, 401 | { 402 | "compressed": true, 403 | "immutable": true, 404 | "size": 390, 405 | }, 406 | ], 407 | [ 408 | "async.async.55e6e9a872bcc7d4b226.js", 409 | 136, 410 | { 411 | "compressed": true, 412 | "immutable": true, 413 | "size": 136, 414 | }, 415 | ], 416 | [ 417 | "main.4d75ca3dcb0ac1fb976b.js", 418 | 4097, 419 | { 420 | "compressed": true, 421 | "immutable": true, 422 | "size": 4097, 423 | }, 424 | ], 425 | ] 426 | `; 427 | 428 | exports[`"deleteOriginalAssets" option should work and report errors on duplicate assets: errors 1`] = ` 429 | [ 430 | "Error: Conflict: Multiple assets emit different content to the same filename 23fc1d3ac606d117e05a140e0de79806.svg", 431 | "Error: Conflict: Multiple assets emit different content to the same filename async.async.55e6e9a872bcc7d4b226.js", 432 | "Error: Conflict: Multiple assets emit different content to the same filename main.4d75ca3dcb0ac1fb976b.js", 433 | ] 434 | `; 435 | 436 | exports[`"deleteOriginalAssets" option should work and report errors on duplicate assets: warnings 1`] = `[]`; 437 | -------------------------------------------------------------------------------- /test/__snapshots__/filename-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"filename" option matches snapshot for \`[name][ext].super-compressed.gz[query]\` value ({String}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "related": { 11 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.super-compressed.png.gz", 12 | }, 13 | "size": 78117, 14 | "sourceFilename": "icon.png", 15 | }, 16 | ], 17 | [ 18 | "09a1a1112c577c2794359715edfcb5ac.super-compressed.png.gz", 19 | 73507, 20 | { 21 | "compressed": true, 22 | "immutable": true, 23 | "size": 73507, 24 | }, 25 | ], 26 | [ 27 | "23fc1d3ac606d117e05a140e0de79806.super-compressed.svg.gz", 28 | 390, 29 | { 30 | "compressed": true, 31 | "immutable": true, 32 | "size": 390, 33 | }, 34 | ], 35 | [ 36 | "23fc1d3ac606d117e05a140e0de79806.svg", 37 | 672, 38 | { 39 | "immutable": true, 40 | "related": { 41 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.super-compressed.svg.gz", 42 | }, 43 | "size": 672, 44 | "sourceFilename": "icon.svg", 45 | }, 46 | ], 47 | [ 48 | "async.async.js?ver=8bb94238bc82ebe7567e", 49 | 194, 50 | { 51 | "fullhash": "8bb94238bc82ebe7567e", 52 | "immutable": true, 53 | "javascriptModule": false, 54 | "related": { 55 | "gzipped": "async.async.super-compressed.js.gz?ver=8bb94238bc82ebe7567e", 56 | }, 57 | "size": 194, 58 | }, 59 | ], 60 | [ 61 | "async.async.super-compressed.js.gz?ver=8bb94238bc82ebe7567e", 62 | 136, 63 | { 64 | "compressed": true, 65 | "immutable": true, 66 | "size": 136, 67 | }, 68 | ], 69 | [ 70 | "main.js?var=8bb94238bc82ebe7567e", 71 | 16413, 72 | { 73 | "fullhash": "8bb94238bc82ebe7567e", 74 | "immutable": true, 75 | "javascriptModule": false, 76 | "related": { 77 | "gzipped": "main.super-compressed.js.gz?var=8bb94238bc82ebe7567e", 78 | }, 79 | "size": 16413, 80 | }, 81 | ], 82 | [ 83 | "main.super-compressed.js.gz?var=8bb94238bc82ebe7567e", 84 | 4124, 85 | { 86 | "compressed": true, 87 | "immutable": true, 88 | "size": 4124, 89 | }, 90 | ], 91 | ] 92 | `; 93 | 94 | exports[`"filename" option matches snapshot for \`[name][ext].super-compressed.gz[query]\` value ({String}): errors 1`] = `[]`; 95 | 96 | exports[`"filename" option matches snapshot for \`[name][ext].super-compressed.gz[query]\` value ({String}): warnings 1`] = `[]`; 97 | 98 | exports[`"filename" option matches snapshot for \`[path][base].super-compressed.gz[query][fragment]\` value ({String}): assets 1`] = ` 99 | [ 100 | [ 101 | "09a1a1112c577c2794359715edfcb5ac.png", 102 | 78117, 103 | { 104 | "immutable": true, 105 | "related": { 106 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.super-compressed.gz", 107 | }, 108 | "size": 78117, 109 | "sourceFilename": "icon.png", 110 | }, 111 | ], 112 | [ 113 | "09a1a1112c577c2794359715edfcb5ac.png.super-compressed.gz", 114 | 73507, 115 | { 116 | "compressed": true, 117 | "immutable": true, 118 | "size": 73507, 119 | }, 120 | ], 121 | [ 122 | "23fc1d3ac606d117e05a140e0de79806.svg", 123 | 672, 124 | { 125 | "immutable": true, 126 | "related": { 127 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.super-compressed.gz", 128 | }, 129 | "size": 672, 130 | "sourceFilename": "icon.svg", 131 | }, 132 | ], 133 | [ 134 | "23fc1d3ac606d117e05a140e0de79806.svg.super-compressed.gz", 135 | 390, 136 | { 137 | "compressed": true, 138 | "immutable": true, 139 | "size": 390, 140 | }, 141 | ], 142 | [ 143 | "assets/js/async.async.js.super-compressed.gz?ver=806dd6f81564a908091b#hash", 144 | 136, 145 | { 146 | "compressed": true, 147 | "immutable": true, 148 | "size": 136, 149 | }, 150 | ], 151 | [ 152 | "assets/js/async.async.js?ver=806dd6f81564a908091b#hash", 153 | 194, 154 | { 155 | "fullhash": "806dd6f81564a908091b", 156 | "immutable": true, 157 | "javascriptModule": false, 158 | "related": { 159 | "gzipped": "assets/js/async.async.js.super-compressed.gz?ver=806dd6f81564a908091b#hash", 160 | }, 161 | "size": 194, 162 | }, 163 | ], 164 | [ 165 | "assets/js/main.js.super-compressed.gz?var=806dd6f81564a908091b#hash", 166 | 4143, 167 | { 168 | "compressed": true, 169 | "immutable": true, 170 | "size": 4143, 171 | }, 172 | ], 173 | [ 174 | "assets/js/main.js?var=806dd6f81564a908091b#hash", 175 | 16439, 176 | { 177 | "fullhash": "806dd6f81564a908091b", 178 | "immutable": true, 179 | "javascriptModule": false, 180 | "related": { 181 | "gzipped": "assets/js/main.js.super-compressed.gz?var=806dd6f81564a908091b#hash", 182 | }, 183 | "size": 16439, 184 | }, 185 | ], 186 | ] 187 | `; 188 | 189 | exports[`"filename" option matches snapshot for \`[path][base].super-compressed.gz[query][fragment]\` value ({String}): errors 1`] = `[]`; 190 | 191 | exports[`"filename" option matches snapshot for \`[path][base].super-compressed.gz[query][fragment]\` value ({String}): warnings 1`] = `[]`; 192 | 193 | exports[`"filename" option matches snapshot for custom function ({Function}) and custom algorithm ({Function}): assets 1`] = ` 194 | [ 195 | [ 196 | "09a1a1112c577c2794359715edfcb5ac.png", 197 | 78117, 198 | { 199 | "immutable": true, 200 | "related": { 201 | "compression-function-5f27f917e58a6cfd45a9fb0dbfb129f9": "09a1a1112c577c2794359715edfcb5ac.png.gz", 202 | }, 203 | "size": 78117, 204 | "sourceFilename": "icon.png", 205 | }, 206 | ], 207 | [ 208 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 209 | 78117, 210 | { 211 | "compressed": true, 212 | "size": 78117, 213 | }, 214 | ], 215 | [ 216 | "23fc1d3ac606d117e05a140e0de79806.svg", 217 | 672, 218 | { 219 | "immutable": true, 220 | "related": { 221 | "compression-function-5f27f917e58a6cfd45a9fb0dbfb129f9": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 222 | }, 223 | "size": 672, 224 | "sourceFilename": "icon.svg", 225 | }, 226 | ], 227 | [ 228 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 229 | 672, 230 | { 231 | "compressed": true, 232 | "size": 672, 233 | }, 234 | ], 235 | [ 236 | "async.async.js.gz?ver=3bf6e5d811bf791b08d4", 237 | 194, 238 | { 239 | "compressed": true, 240 | "size": 194, 241 | }, 242 | ], 243 | [ 244 | "async.async.js?ver=3bf6e5d811bf791b08d4#hash", 245 | 194, 246 | { 247 | "fullhash": "3bf6e5d811bf791b08d4", 248 | "immutable": true, 249 | "javascriptModule": false, 250 | "related": { 251 | "compression-function-5f27f917e58a6cfd45a9fb0dbfb129f9": "async.async.js.gz?ver=3bf6e5d811bf791b08d4", 252 | }, 253 | "size": 194, 254 | }, 255 | ], 256 | [ 257 | "main.js.gz?var=3bf6e5d811bf791b08d4", 258 | 16418, 259 | { 260 | "compressed": true, 261 | "size": 16418, 262 | }, 263 | ], 264 | [ 265 | "main.js?var=3bf6e5d811bf791b08d4#hash", 266 | 16418, 267 | { 268 | "fullhash": "3bf6e5d811bf791b08d4", 269 | "immutable": true, 270 | "javascriptModule": false, 271 | "related": { 272 | "compression-function-5f27f917e58a6cfd45a9fb0dbfb129f9": "main.js.gz?var=3bf6e5d811bf791b08d4", 273 | }, 274 | "size": 16418, 275 | }, 276 | ], 277 | ] 278 | `; 279 | 280 | exports[`"filename" option matches snapshot for custom function ({Function}) and custom algorithm ({Function}): errors 1`] = `[]`; 281 | 282 | exports[`"filename" option matches snapshot for custom function ({Function}) and custom algorithm ({Function}): warnings 1`] = `[]`; 283 | 284 | exports[`"filename" option matches snapshot for custom function ({Function}): assets 1`] = ` 285 | [ 286 | [ 287 | "09a1a1112c577c2794359715edfcb5ac.png", 288 | 78117, 289 | { 290 | "immutable": true, 291 | "related": { 292 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 293 | }, 294 | "size": 78117, 295 | "sourceFilename": "icon.png", 296 | }, 297 | ], 298 | [ 299 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 300 | 73507, 301 | { 302 | "compressed": true, 303 | "size": 73507, 304 | }, 305 | ], 306 | [ 307 | "23fc1d3ac606d117e05a140e0de79806.svg", 308 | 672, 309 | { 310 | "immutable": true, 311 | "related": { 312 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 313 | }, 314 | "size": 672, 315 | "sourceFilename": "icon.svg", 316 | }, 317 | ], 318 | [ 319 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 320 | 390, 321 | { 322 | "compressed": true, 323 | "size": 390, 324 | }, 325 | ], 326 | [ 327 | "async.async.js.gz?ver=3bf6e5d811bf791b08d4", 328 | 136, 329 | { 330 | "compressed": true, 331 | "size": 136, 332 | }, 333 | ], 334 | [ 335 | "async.async.js?ver=3bf6e5d811bf791b08d4#hash", 336 | 194, 337 | { 338 | "fullhash": "3bf6e5d811bf791b08d4", 339 | "immutable": true, 340 | "javascriptModule": false, 341 | "related": { 342 | "gzipped": "async.async.js.gz?ver=3bf6e5d811bf791b08d4", 343 | }, 344 | "size": 194, 345 | }, 346 | ], 347 | [ 348 | "main.js.gz?var=3bf6e5d811bf791b08d4", 349 | 4129, 350 | { 351 | "compressed": true, 352 | "size": 4129, 353 | }, 354 | ], 355 | [ 356 | "main.js?var=3bf6e5d811bf791b08d4#hash", 357 | 16418, 358 | { 359 | "fullhash": "3bf6e5d811bf791b08d4", 360 | "immutable": true, 361 | "javascriptModule": false, 362 | "related": { 363 | "gzipped": "main.js.gz?var=3bf6e5d811bf791b08d4", 364 | }, 365 | "size": 16418, 366 | }, 367 | ], 368 | ] 369 | `; 370 | 371 | exports[`"filename" option matches snapshot for custom function ({Function}): errors 1`] = `[]`; 372 | 373 | exports[`"filename" option matches snapshot for custom function ({Function}): warnings 1`] = `[]`; 374 | 375 | exports[`"filename" option show work: assets 1`] = ` 376 | [ 377 | [ 378 | "09a1a1112c577c2794359715edfcb5ac.png", 379 | 78117, 380 | { 381 | "immutable": true, 382 | "related": { 383 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 384 | }, 385 | "size": 78117, 386 | "sourceFilename": "icon.png", 387 | }, 388 | ], 389 | [ 390 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 391 | 73507, 392 | { 393 | "compressed": true, 394 | "immutable": true, 395 | "size": 73507, 396 | }, 397 | ], 398 | [ 399 | "23fc1d3ac606d117e05a140e0de79806.svg", 400 | 672, 401 | { 402 | "immutable": true, 403 | "related": { 404 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 405 | }, 406 | "size": 672, 407 | "sourceFilename": "icon.svg", 408 | }, 409 | ], 410 | [ 411 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 412 | 390, 413 | { 414 | "compressed": true, 415 | "immutable": true, 416 | "size": 390, 417 | }, 418 | ], 419 | [ 420 | "assets/scripts/async.async.js.gz", 421 | 136, 422 | { 423 | "compressed": true, 424 | "immutable": true, 425 | "size": 136, 426 | }, 427 | ], 428 | [ 429 | "assets/scripts/async.async.js?ver=45dcd2971e836b8dec35#hash", 430 | 194, 431 | { 432 | "fullhash": "45dcd2971e836b8dec35", 433 | "immutable": true, 434 | "javascriptModule": false, 435 | "related": { 436 | "gzipped": "assets/scripts/async.async.js.gz", 437 | }, 438 | "size": 194, 439 | }, 440 | ], 441 | [ 442 | "assets/scripts/main.js.gz", 443 | 4143, 444 | { 445 | "compressed": true, 446 | "immutable": true, 447 | "size": 4143, 448 | }, 449 | ], 450 | [ 451 | "assets/scripts/main.js?var=45dcd2971e836b8dec35#hash", 452 | 16444, 453 | { 454 | "fullhash": "45dcd2971e836b8dec35", 455 | "immutable": true, 456 | "javascriptModule": false, 457 | "related": { 458 | "gzipped": "assets/scripts/main.js.gz", 459 | }, 460 | "size": 16444, 461 | }, 462 | ], 463 | ] 464 | `; 465 | 466 | exports[`"filename" option show work: errors 1`] = `[]`; 467 | 468 | exports[`"filename" option show work: warnings 1`] = `[]`; 469 | -------------------------------------------------------------------------------- /test/__snapshots__/compressionOptions-option.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"compressionOptions" option matches snapshot for custom options ({Object}): assets 1`] = ` 4 | [ 5 | [ 6 | "09a1a1112c577c2794359715edfcb5ac.png", 7 | 78117, 8 | { 9 | "immutable": true, 10 | "related": { 11 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 12 | }, 13 | "size": 78117, 14 | "sourceFilename": "icon.png", 15 | }, 16 | ], 17 | [ 18 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 19 | 73507, 20 | { 21 | "compressed": true, 22 | "immutable": true, 23 | "size": 73507, 24 | }, 25 | ], 26 | [ 27 | "23fc1d3ac606d117e05a140e0de79806.svg", 28 | 672, 29 | { 30 | "immutable": true, 31 | "related": { 32 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 33 | }, 34 | "size": 672, 35 | "sourceFilename": "icon.svg", 36 | }, 37 | ], 38 | [ 39 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 40 | 390, 41 | { 42 | "compressed": true, 43 | "immutable": true, 44 | "size": 390, 45 | }, 46 | ], 47 | [ 48 | "async.async.55e6e9a872bcc7d4b226.js", 49 | 194, 50 | { 51 | "chunkhash": "55e6e9a872bcc7d4b226", 52 | "immutable": true, 53 | "javascriptModule": false, 54 | "related": { 55 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 56 | }, 57 | "size": 194, 58 | }, 59 | ], 60 | [ 61 | "async.async.55e6e9a872bcc7d4b226.js.gz", 62 | 136, 63 | { 64 | "compressed": true, 65 | "immutable": true, 66 | "size": 136, 67 | }, 68 | ], 69 | [ 70 | "main.4d75ca3dcb0ac1fb976b.js", 71 | 16252, 72 | { 73 | "chunkhash": "4d75ca3dcb0ac1fb976b", 74 | "immutable": true, 75 | "javascriptModule": false, 76 | "related": { 77 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 78 | }, 79 | "size": 16252, 80 | }, 81 | ], 82 | [ 83 | "main.4d75ca3dcb0ac1fb976b.js.gz", 84 | 4097, 85 | { 86 | "compressed": true, 87 | "immutable": true, 88 | "size": 4097, 89 | }, 90 | ], 91 | ] 92 | `; 93 | 94 | exports[`"compressionOptions" option matches snapshot for custom options ({Object}): errors 1`] = `[]`; 95 | 96 | exports[`"compressionOptions" option matches snapshot for custom options ({Object}): warnings 1`] = `[]`; 97 | 98 | exports[`"compressionOptions" option matches snapshot without values: assets 1`] = ` 99 | [ 100 | [ 101 | "09a1a1112c577c2794359715edfcb5ac.png", 102 | 78117, 103 | { 104 | "immutable": true, 105 | "related": { 106 | "gzipped": "09a1a1112c577c2794359715edfcb5ac.png.gz", 107 | }, 108 | "size": 78117, 109 | "sourceFilename": "icon.png", 110 | }, 111 | ], 112 | [ 113 | "09a1a1112c577c2794359715edfcb5ac.png.gz", 114 | 73507, 115 | { 116 | "compressed": true, 117 | "immutable": true, 118 | "size": 73507, 119 | }, 120 | ], 121 | [ 122 | "23fc1d3ac606d117e05a140e0de79806.svg", 123 | 672, 124 | { 125 | "immutable": true, 126 | "related": { 127 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 128 | }, 129 | "size": 672, 130 | "sourceFilename": "icon.svg", 131 | }, 132 | ], 133 | [ 134 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 135 | 390, 136 | { 137 | "compressed": true, 138 | "immutable": true, 139 | "size": 390, 140 | }, 141 | ], 142 | [ 143 | "async.async.55e6e9a872bcc7d4b226.js", 144 | 194, 145 | { 146 | "chunkhash": "55e6e9a872bcc7d4b226", 147 | "immutable": true, 148 | "javascriptModule": false, 149 | "related": { 150 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 151 | }, 152 | "size": 194, 153 | }, 154 | ], 155 | [ 156 | "async.async.55e6e9a872bcc7d4b226.js.gz", 157 | 136, 158 | { 159 | "compressed": true, 160 | "immutable": true, 161 | "size": 136, 162 | }, 163 | ], 164 | [ 165 | "main.4d75ca3dcb0ac1fb976b.js", 166 | 16252, 167 | { 168 | "chunkhash": "4d75ca3dcb0ac1fb976b", 169 | "immutable": true, 170 | "javascriptModule": false, 171 | "related": { 172 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 173 | }, 174 | "size": 16252, 175 | }, 176 | ], 177 | [ 178 | "main.4d75ca3dcb0ac1fb976b.js.gz", 179 | 4097, 180 | { 181 | "compressed": true, 182 | "immutable": true, 183 | "size": 4097, 184 | }, 185 | ], 186 | ] 187 | `; 188 | 189 | exports[`"compressionOptions" option matches snapshot without values: errors 1`] = `[]`; 190 | 191 | exports[`"compressionOptions" option matches snapshot without values: warnings 1`] = `[]`; 192 | 193 | exports[`"compressionOptions" option set default compression level to maximum for brotli: assets 1`] = ` 194 | [ 195 | [ 196 | "09a1a1112c577c2794359715edfcb5ac.png", 197 | 78117, 198 | { 199 | "immutable": true, 200 | "size": 78117, 201 | "sourceFilename": "icon.png", 202 | }, 203 | ], 204 | [ 205 | "23fc1d3ac606d117e05a140e0de79806.svg", 206 | 672, 207 | { 208 | "immutable": true, 209 | "related": { 210 | "brotliCompressed": "23fc1d3ac606d117e05a140e0de79806.svg.br", 211 | }, 212 | "size": 672, 213 | "sourceFilename": "icon.svg", 214 | }, 215 | ], 216 | [ 217 | "23fc1d3ac606d117e05a140e0de79806.svg.br", 218 | 363, 219 | { 220 | "compressed": true, 221 | "immutable": true, 222 | "size": 363, 223 | }, 224 | ], 225 | [ 226 | "async.async.55e6e9a872bcc7d4b226.js", 227 | 194, 228 | { 229 | "chunkhash": "55e6e9a872bcc7d4b226", 230 | "immutable": true, 231 | "javascriptModule": false, 232 | "related": { 233 | "brotliCompressed": "async.async.55e6e9a872bcc7d4b226.js.br", 234 | }, 235 | "size": 194, 236 | }, 237 | ], 238 | [ 239 | "async.async.55e6e9a872bcc7d4b226.js.br", 240 | 123, 241 | { 242 | "compressed": true, 243 | "immutable": true, 244 | "size": 123, 245 | }, 246 | ], 247 | [ 248 | "main.4d75ca3dcb0ac1fb976b.js", 249 | 16252, 250 | { 251 | "chunkhash": "4d75ca3dcb0ac1fb976b", 252 | "immutable": true, 253 | "javascriptModule": false, 254 | "related": { 255 | "brotliCompressed": "main.4d75ca3dcb0ac1fb976b.js.br", 256 | }, 257 | "size": 16252, 258 | }, 259 | ], 260 | [ 261 | "main.4d75ca3dcb0ac1fb976b.js.br", 262 | 3578, 263 | { 264 | "compressed": true, 265 | "immutable": true, 266 | "size": 3578, 267 | }, 268 | ], 269 | ] 270 | `; 271 | 272 | exports[`"compressionOptions" option set default compression level to maximum for brotli: errors 1`] = `[]`; 273 | 274 | exports[`"compressionOptions" option set default compression level to maximum for brotli: warnings 1`] = `[]`; 275 | 276 | exports[`"compressionOptions" option set default compression level to maximum for deflate: assets 1`] = ` 277 | [ 278 | [ 279 | "09a1a1112c577c2794359715edfcb5ac.png", 280 | 78117, 281 | { 282 | "immutable": true, 283 | "size": 78117, 284 | "sourceFilename": "icon.png", 285 | }, 286 | ], 287 | [ 288 | "23fc1d3ac606d117e05a140e0de79806.svg", 289 | 672, 290 | { 291 | "immutable": true, 292 | "related": { 293 | "deflateed": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 294 | }, 295 | "size": 672, 296 | "sourceFilename": "icon.svg", 297 | }, 298 | ], 299 | [ 300 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 301 | 378, 302 | { 303 | "compressed": true, 304 | "immutable": true, 305 | "size": 378, 306 | }, 307 | ], 308 | [ 309 | "async.async.55e6e9a872bcc7d4b226.js", 310 | 194, 311 | { 312 | "chunkhash": "55e6e9a872bcc7d4b226", 313 | "immutable": true, 314 | "javascriptModule": false, 315 | "related": { 316 | "deflateed": "async.async.55e6e9a872bcc7d4b226.js.gz", 317 | }, 318 | "size": 194, 319 | }, 320 | ], 321 | [ 322 | "async.async.55e6e9a872bcc7d4b226.js.gz", 323 | 124, 324 | { 325 | "compressed": true, 326 | "immutable": true, 327 | "size": 124, 328 | }, 329 | ], 330 | [ 331 | "main.4d75ca3dcb0ac1fb976b.js", 332 | 16252, 333 | { 334 | "chunkhash": "4d75ca3dcb0ac1fb976b", 335 | "immutable": true, 336 | "javascriptModule": false, 337 | "related": { 338 | "deflateed": "main.4d75ca3dcb0ac1fb976b.js.gz", 339 | }, 340 | "size": 16252, 341 | }, 342 | ], 343 | [ 344 | "main.4d75ca3dcb0ac1fb976b.js.gz", 345 | 4085, 346 | { 347 | "compressed": true, 348 | "immutable": true, 349 | "size": 4085, 350 | }, 351 | ], 352 | ] 353 | `; 354 | 355 | exports[`"compressionOptions" option set default compression level to maximum for deflate: errors 1`] = `[]`; 356 | 357 | exports[`"compressionOptions" option set default compression level to maximum for deflate: warnings 1`] = `[]`; 358 | 359 | exports[`"compressionOptions" option set default compression level to maximum for deflateRaw: assets 1`] = ` 360 | [ 361 | [ 362 | "09a1a1112c577c2794359715edfcb5ac.png", 363 | 78117, 364 | { 365 | "immutable": true, 366 | "size": 78117, 367 | "sourceFilename": "icon.png", 368 | }, 369 | ], 370 | [ 371 | "23fc1d3ac606d117e05a140e0de79806.svg", 372 | 672, 373 | { 374 | "immutable": true, 375 | "related": { 376 | "deflateRawed": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 377 | }, 378 | "size": 672, 379 | "sourceFilename": "icon.svg", 380 | }, 381 | ], 382 | [ 383 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 384 | 372, 385 | { 386 | "compressed": true, 387 | "immutable": true, 388 | "size": 372, 389 | }, 390 | ], 391 | [ 392 | "async.async.55e6e9a872bcc7d4b226.js", 393 | 194, 394 | { 395 | "chunkhash": "55e6e9a872bcc7d4b226", 396 | "immutable": true, 397 | "javascriptModule": false, 398 | "related": { 399 | "deflateRawed": "async.async.55e6e9a872bcc7d4b226.js.gz", 400 | }, 401 | "size": 194, 402 | }, 403 | ], 404 | [ 405 | "async.async.55e6e9a872bcc7d4b226.js.gz", 406 | 118, 407 | { 408 | "compressed": true, 409 | "immutable": true, 410 | "size": 118, 411 | }, 412 | ], 413 | [ 414 | "main.4d75ca3dcb0ac1fb976b.js", 415 | 16252, 416 | { 417 | "chunkhash": "4d75ca3dcb0ac1fb976b", 418 | "immutable": true, 419 | "javascriptModule": false, 420 | "related": { 421 | "deflateRawed": "main.4d75ca3dcb0ac1fb976b.js.gz", 422 | }, 423 | "size": 16252, 424 | }, 425 | ], 426 | [ 427 | "main.4d75ca3dcb0ac1fb976b.js.gz", 428 | 4079, 429 | { 430 | "compressed": true, 431 | "immutable": true, 432 | "size": 4079, 433 | }, 434 | ], 435 | ] 436 | `; 437 | 438 | exports[`"compressionOptions" option set default compression level to maximum for deflateRaw: errors 1`] = `[]`; 439 | 440 | exports[`"compressionOptions" option set default compression level to maximum for deflateRaw: warnings 1`] = `[]`; 441 | 442 | exports[`"compressionOptions" option set default compression level to maximum for gzip: assets 1`] = ` 443 | [ 444 | [ 445 | "09a1a1112c577c2794359715edfcb5ac.png", 446 | 78117, 447 | { 448 | "immutable": true, 449 | "size": 78117, 450 | "sourceFilename": "icon.png", 451 | }, 452 | ], 453 | [ 454 | "23fc1d3ac606d117e05a140e0de79806.svg", 455 | 672, 456 | { 457 | "immutable": true, 458 | "related": { 459 | "gzipped": "23fc1d3ac606d117e05a140e0de79806.svg.gz", 460 | }, 461 | "size": 672, 462 | "sourceFilename": "icon.svg", 463 | }, 464 | ], 465 | [ 466 | "23fc1d3ac606d117e05a140e0de79806.svg.gz", 467 | 390, 468 | { 469 | "compressed": true, 470 | "immutable": true, 471 | "size": 390, 472 | }, 473 | ], 474 | [ 475 | "async.async.55e6e9a872bcc7d4b226.js", 476 | 194, 477 | { 478 | "chunkhash": "55e6e9a872bcc7d4b226", 479 | "immutable": true, 480 | "javascriptModule": false, 481 | "related": { 482 | "gzipped": "async.async.55e6e9a872bcc7d4b226.js.gz", 483 | }, 484 | "size": 194, 485 | }, 486 | ], 487 | [ 488 | "async.async.55e6e9a872bcc7d4b226.js.gz", 489 | 136, 490 | { 491 | "compressed": true, 492 | "immutable": true, 493 | "size": 136, 494 | }, 495 | ], 496 | [ 497 | "main.4d75ca3dcb0ac1fb976b.js", 498 | 16252, 499 | { 500 | "chunkhash": "4d75ca3dcb0ac1fb976b", 501 | "immutable": true, 502 | "javascriptModule": false, 503 | "related": { 504 | "gzipped": "main.4d75ca3dcb0ac1fb976b.js.gz", 505 | }, 506 | "size": 16252, 507 | }, 508 | ], 509 | [ 510 | "main.4d75ca3dcb0ac1fb976b.js.gz", 511 | 4097, 512 | { 513 | "compressed": true, 514 | "immutable": true, 515 | "size": 4097, 516 | }, 517 | ], 518 | ] 519 | `; 520 | 521 | exports[`"compressionOptions" option set default compression level to maximum for gzip: errors 1`] = `[]`; 522 | 523 | exports[`"compressionOptions" option set default compression level to maximum for gzip: warnings 1`] = `[]`; 524 | -------------------------------------------------------------------------------- /test/__snapshots__/validate-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`validate options should throw an error on the "algorithm" option with "true" value 1`] = ` 4 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 5 | - options.algorithm should be one of these: 6 | string | function 7 | -> The compression algorithm/function. 8 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#algorithm 9 | Details: 10 | * options.algorithm should be a string. 11 | * options.algorithm should be an instance of function." 12 | `; 13 | 14 | exports[`validate options should throw an error on the "compressionOptions" option with "1024" value 1`] = ` 15 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 16 | - options.compressionOptions should be an object: 17 | object { … } 18 | -> Compression options for \`algorithm\`. 19 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#compressionoptions" 20 | `; 21 | 22 | exports[`validate options should throw an error on the "deleteOriginalAssets" option with "true" value 1`] = ` 23 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 24 | - options.deleteOriginalAssets should be one of these: 25 | boolean | function | "keep-source-map" 26 | -> Whether to delete the original assets or not. 27 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#deleteoriginalassets 28 | Details: 29 | * options.deleteOriginalAssets should be a boolean. 30 | * options.deleteOriginalAssets should be an instance of function. 31 | * options.deleteOriginalAssets should be "keep-source-map"." 32 | `; 33 | 34 | exports[`validate options should throw an error on the "deleteOriginalAssets" option with "unknown" value 1`] = ` 35 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 36 | - options.deleteOriginalAssets should be one of these: 37 | boolean | function | "keep-source-map" 38 | -> Whether to delete the original assets or not. 39 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#deleteoriginalassets 40 | Details: 41 | * options.deleteOriginalAssets should be a boolean. 42 | * options.deleteOriginalAssets should be an instance of function. 43 | * options.deleteOriginalAssets should be "keep-source-map"." 44 | `; 45 | 46 | exports[`validate options should throw an error on the "exclude" option with "[{},"foo",true]" value 1`] = ` 47 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 48 | - options.exclude should be one of these: 49 | [RegExp | non-empty string, ...] | RegExp | non-empty string 50 | -> Filtering rules. 51 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#exclude 52 | Details: 53 | * options.exclude[2] should be one of these: 54 | RegExp | non-empty string 55 | -> Filtering rule as regex or string. 56 | Details: 57 | * options.exclude[2] should be an instance of RegExp. 58 | * options.exclude[2] should be a non-empty string." 59 | `; 60 | 61 | exports[`validate options should throw an error on the "exclude" option with "[true]" value 1`] = ` 62 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 63 | - options.exclude should be one of these: 64 | [RegExp | non-empty string, ...] | RegExp | non-empty string 65 | -> Filtering rules. 66 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#exclude 67 | Details: 68 | * options.exclude[0] should be one of these: 69 | RegExp | non-empty string 70 | -> Filtering rule as regex or string. 71 | Details: 72 | * options.exclude[0] should be an instance of RegExp. 73 | * options.exclude[0] should be a non-empty string." 74 | `; 75 | 76 | exports[`validate options should throw an error on the "exclude" option with "true" value 1`] = ` 77 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 78 | - options.exclude should be one of these: 79 | [RegExp | non-empty string, ...] | RegExp | non-empty string 80 | -> Filtering rules. 81 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#exclude 82 | Details: 83 | * options.exclude should be an array: 84 | [RegExp | non-empty string, ...] 85 | * options.exclude should be one of these: 86 | RegExp | non-empty string 87 | -> Filtering rule as regex or string. 88 | Details: 89 | * options.exclude should be an instance of RegExp. 90 | * options.exclude should be a non-empty string." 91 | `; 92 | 93 | exports[`validate options should throw an error on the "filename" option with "true" value 1`] = ` 94 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 95 | - options.filename should be one of these: 96 | string | function 97 | -> The target asset filename. 98 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#filename 99 | Details: 100 | * options.filename should be a string. 101 | * options.filename should be an instance of function." 102 | `; 103 | 104 | exports[`validate options should throw an error on the "include" option with "[{},"foo",true]" value 1`] = ` 105 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 106 | - options.include should be one of these: 107 | [RegExp | non-empty string, ...] | RegExp | non-empty string 108 | -> Filtering rules. 109 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#include 110 | Details: 111 | * options.include[2] should be one of these: 112 | RegExp | non-empty string 113 | -> Filtering rule as regex or string. 114 | Details: 115 | * options.include[2] should be an instance of RegExp. 116 | * options.include[2] should be a non-empty string." 117 | `; 118 | 119 | exports[`validate options should throw an error on the "include" option with "[true]" value 1`] = ` 120 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 121 | - options.include should be one of these: 122 | [RegExp | non-empty string, ...] | RegExp | non-empty string 123 | -> Filtering rules. 124 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#include 125 | Details: 126 | * options.include[0] should be one of these: 127 | RegExp | non-empty string 128 | -> Filtering rule as regex or string. 129 | Details: 130 | * options.include[0] should be an instance of RegExp. 131 | * options.include[0] should be a non-empty string." 132 | `; 133 | 134 | exports[`validate options should throw an error on the "include" option with "true" value 1`] = ` 135 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 136 | - options.include should be one of these: 137 | [RegExp | non-empty string, ...] | RegExp | non-empty string 138 | -> Filtering rules. 139 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#include 140 | Details: 141 | * options.include should be an array: 142 | [RegExp | non-empty string, ...] 143 | * options.include should be one of these: 144 | RegExp | non-empty string 145 | -> Filtering rule as regex or string. 146 | Details: 147 | * options.include should be an instance of RegExp. 148 | * options.include should be a non-empty string." 149 | `; 150 | 151 | exports[`validate options should throw an error on the "minRatio" option with "0.8" value 1`] = ` 152 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 153 | - options.minRatio should be a number. 154 | -> Only assets that compress better than this ratio are processed (\`minRatio = Compressed Size / Original Size\`). 155 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#minratio" 156 | `; 157 | 158 | exports[`validate options should throw an error on the "test" option with "[{},"foo",true]" value 1`] = ` 159 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 160 | - options.test should be one of these: 161 | [RegExp | non-empty string, ...] | RegExp | non-empty string 162 | -> Filtering rules. 163 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#test 164 | Details: 165 | * options.test[2] should be one of these: 166 | RegExp | non-empty string 167 | -> Filtering rule as regex or string. 168 | Details: 169 | * options.test[2] should be an instance of RegExp. 170 | * options.test[2] should be a non-empty string." 171 | `; 172 | 173 | exports[`validate options should throw an error on the "test" option with "[true]" value 1`] = ` 174 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 175 | - options.test should be one of these: 176 | [RegExp | non-empty string, ...] | RegExp | non-empty string 177 | -> Filtering rules. 178 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#test 179 | Details: 180 | * options.test[0] should be one of these: 181 | RegExp | non-empty string 182 | -> Filtering rule as regex or string. 183 | Details: 184 | * options.test[0] should be an instance of RegExp. 185 | * options.test[0] should be a non-empty string." 186 | `; 187 | 188 | exports[`validate options should throw an error on the "test" option with "true" value 1`] = ` 189 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 190 | - options.test should be one of these: 191 | [RegExp | non-empty string, ...] | RegExp | non-empty string 192 | -> Filtering rules. 193 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#test 194 | Details: 195 | * options.test should be an array: 196 | [RegExp | non-empty string, ...] 197 | * options.test should be one of these: 198 | RegExp | non-empty string 199 | -> Filtering rule as regex or string. 200 | Details: 201 | * options.test should be an instance of RegExp. 202 | * options.test should be a non-empty string." 203 | `; 204 | 205 | exports[`validate options should throw an error on the "threshold" option with "1024" value 1`] = ` 206 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 207 | - options.threshold should be a number. 208 | -> Only assets bigger than this size are processed. In bytes. 209 | -> Read more at https://github.com/webpack/compression-webpack-plugin/#threshold" 210 | `; 211 | 212 | exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` 213 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 214 | - options has an unknown property 'unknown'. These properties are valid: 215 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 216 | `; 217 | 218 | exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` 219 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 220 | - options has an unknown property 'unknown'. These properties are valid: 221 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 222 | `; 223 | 224 | exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` 225 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 226 | - options has an unknown property 'unknown'. These properties are valid: 227 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 228 | `; 229 | 230 | exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` 231 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 232 | - options has an unknown property 'unknown'. These properties are valid: 233 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 234 | `; 235 | 236 | exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` 237 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 238 | - options has an unknown property 'unknown'. These properties are valid: 239 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 240 | `; 241 | 242 | exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` 243 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 244 | - options has an unknown property 'unknown'. These properties are valid: 245 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 246 | `; 247 | 248 | exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` 249 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 250 | - options has an unknown property 'unknown'. These properties are valid: 251 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 252 | `; 253 | 254 | exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` 255 | "Invalid options object. Compression Plugin has been initialized using an options object that does not match the API schema. 256 | - options has an unknown property 'unknown'. These properties are valid: 257 | object { test?, include?, exclude?, algorithm?, compressionOptions?, threshold?, minRatio?, deleteOriginalAssets?, filename? }" 258 | `; 259 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | const crypto = require("node:crypto"); 7 | const path = require("node:path"); 8 | 9 | const { validate } = require("schema-utils"); 10 | const serialize = require("serialize-javascript"); 11 | 12 | const schema = require("./options.json"); 13 | 14 | /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ 15 | /** @typedef {import("webpack").AssetInfo} AssetInfo */ 16 | /** @typedef {import("webpack").Compiler} Compiler */ 17 | /** @typedef {import("webpack").PathData} PathData */ 18 | /** @typedef {import("webpack").WebpackPluginInstance} WebpackPluginInstance */ 19 | /** @typedef {import("webpack").Compilation} Compilation */ 20 | /** @typedef {import("webpack").sources.Source} Source */ 21 | /** @typedef {import("webpack").Asset} Asset */ 22 | /** @typedef {import("webpack").WebpackError} WebpackError */ 23 | 24 | /** 25 | * @template T 26 | * @typedef {T | { valueOf(): T }} WithImplicitCoercion 27 | */ 28 | 29 | /** @typedef {RegExp | string} Rule */ 30 | /** @typedef {Rule[] | Rule} Rules */ 31 | 32 | // eslint-disable-next-line jsdoc/no-restricted-syntax 33 | /** @typedef {any} EXPECTED_ANY */ 34 | 35 | /** 36 | * @typedef {{ [key: string]: EXPECTED_ANY }} CustomOptions 37 | */ 38 | 39 | /** 40 | * @template T 41 | * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType 42 | */ 43 | 44 | /** 45 | * @template T 46 | * @typedef {InferDefaultType} CompressionOptions 47 | */ 48 | 49 | /** 50 | * @template T 51 | * @callback AlgorithmFunction 52 | * @param {Buffer} input 53 | * @param {CompressionOptions} options 54 | * @param {(error: Error | null | undefined, result: WithImplicitCoercion | Uint8Array | ReadonlyArray | WithImplicitCoercion | string> | WithImplicitCoercion | { [Symbol.toPrimitive](hint: 'string'): string }) => void} callback 55 | */ 56 | 57 | /** 58 | * @typedef {string | ((fileData: PathData) => string)} Filename 59 | */ 60 | 61 | /** 62 | * @typedef {boolean | "keep-source-map" | ((name: string) => boolean)} DeleteOriginalAssets 63 | */ 64 | 65 | /** 66 | * @template T 67 | * @typedef {object} BasePluginOptions 68 | * @property {Rules=} test include all assets that pass test assertion 69 | * @property {Rules=} include include all assets matching any of these conditions 70 | * @property {Rules=} exclude exclude all assets matching any of these conditions 71 | * @property {number=} threshold only assets bigger than this size are processed, in bytes 72 | * @property {number=} minRatio only assets that compress better than this ratio are processed (`minRatio = Compressed Size / Original Size`) 73 | * @property {DeleteOriginalAssets=} deleteOriginalAssets whether to delete the original assets or not 74 | * @property {Filename=} filename the target asset filename 75 | */ 76 | 77 | /** 78 | * @typedef {import("zlib").ZlibOptions} ZlibOptions 79 | */ 80 | 81 | /** 82 | * @template T 83 | * @typedef {T extends ZlibOptions ? { algorithm?: string | AlgorithmFunction | undefined, compressionOptions?: CompressionOptions | undefined } : { algorithm: string | AlgorithmFunction, compressionOptions?: CompressionOptions | undefined }} DefinedDefaultAlgorithmAndOptions 84 | */ 85 | 86 | /** 87 | * @template T 88 | * @typedef {BasePluginOptions & { algorithm: string | AlgorithmFunction, compressionOptions: CompressionOptions, threshold: number, minRatio: number, deleteOriginalAssets: DeleteOriginalAssets, filename: Filename }} InternalPluginOptions 89 | */ 90 | 91 | /** 92 | * @template [T=ZlibOptions] 93 | * @implements WebpackPluginInstance 94 | */ 95 | class CompressionPlugin { 96 | /** 97 | * @param {(BasePluginOptions & DefinedDefaultAlgorithmAndOptions)=} options options 98 | */ 99 | constructor(options) { 100 | validate(/** @type {Schema} */ (schema), options || {}, { 101 | name: "Compression Plugin", 102 | baseDataPath: "options", 103 | }); 104 | 105 | const { 106 | test, 107 | include, 108 | exclude, 109 | algorithm = "gzip", 110 | compressionOptions = /** @type {CompressionOptions} */ ({}), 111 | filename = (options || {}).algorithm === "brotliCompress" 112 | ? "[path][base].br" 113 | : "[path][base].gz", 114 | threshold = 0, 115 | minRatio = 0.8, 116 | deleteOriginalAssets = false, 117 | } = options || {}; 118 | 119 | /** 120 | * @private 121 | * @type {InternalPluginOptions} 122 | */ 123 | this.options = { 124 | test, 125 | include, 126 | exclude, 127 | algorithm, 128 | compressionOptions, 129 | filename, 130 | threshold, 131 | minRatio, 132 | deleteOriginalAssets, 133 | }; 134 | 135 | /** 136 | * @private 137 | * @type {AlgorithmFunction} 138 | */ 139 | this.algorithm = 140 | /** @type {AlgorithmFunction} */ 141 | (this.options.algorithm); 142 | 143 | if (typeof this.algorithm === "string") { 144 | /** 145 | * @type {typeof import("zlib")} 146 | */ 147 | 148 | const zlib = require("node:zlib"); 149 | 150 | /** 151 | * @private 152 | * @type {AlgorithmFunction} 153 | */ 154 | this.algorithm = zlib[this.algorithm]; 155 | 156 | if (!this.algorithm) { 157 | throw new Error( 158 | `Algorithm "${this.options.algorithm}" is not found in "zlib"`, 159 | ); 160 | } 161 | 162 | const defaultCompressionOptions = 163 | { 164 | gzip: { 165 | level: zlib.constants.Z_BEST_COMPRESSION, 166 | }, 167 | deflate: { 168 | level: zlib.constants.Z_BEST_COMPRESSION, 169 | }, 170 | deflateRaw: { 171 | level: zlib.constants.Z_BEST_COMPRESSION, 172 | }, 173 | brotliCompress: { 174 | params: { 175 | [zlib.constants.BROTLI_PARAM_QUALITY]: 176 | zlib.constants.BROTLI_MAX_QUALITY, 177 | }, 178 | }, 179 | }[/** @type {string} */ (algorithm)] || {}; 180 | 181 | this.options.compressionOptions = 182 | /** 183 | * @type {CompressionOptions} 184 | */ 185 | ({ 186 | ...defaultCompressionOptions, 187 | .../** @type {CustomOptions} */ (this.options.compressionOptions), 188 | }); 189 | } 190 | } 191 | 192 | /** 193 | * @private 194 | * @param {Buffer} input input 195 | * @returns {Promise} compressed buffer 196 | */ 197 | runCompressionAlgorithm(input) { 198 | return new Promise((resolve, reject) => { 199 | this.algorithm( 200 | input, 201 | this.options.compressionOptions, 202 | (error, result) => { 203 | if (error) { 204 | reject(error); 205 | 206 | return; 207 | } 208 | 209 | if (!Buffer.isBuffer(result)) { 210 | resolve(Buffer.from(/** @type {string} */ (result))); 211 | } else { 212 | resolve(result); 213 | } 214 | }, 215 | ); 216 | }); 217 | } 218 | 219 | /** 220 | * @private 221 | * @param {Compiler} compiler compiler 222 | * @param {Compilation} compilation compilation 223 | * @param {Record} assets assets 224 | * @returns {Promise} 225 | */ 226 | async compress(compiler, compilation, assets) { 227 | const cache = compilation.getCache("CompressionWebpackPlugin"); 228 | 229 | /** 230 | * @typedef {object} AssetForCompression 231 | * @property {string} name name 232 | * @property {Source} source source 233 | * @property {{ source: Source, compressed: Buffer }} output output 234 | * @property {AssetInfo} info asset info 235 | * @property {Buffer} buffer buffer 236 | * @property {ReturnType["getItemCache"]>} cacheItem cache item 237 | * @property {string} relatedName related name 238 | */ 239 | 240 | const assetsForCompression = ( 241 | await Promise.all( 242 | Object.keys(assets).map(async (name) => { 243 | const { info, source } = 244 | /** @type {Asset} */ 245 | (compilation.getAsset(name)); 246 | 247 | if (info.compressed) { 248 | return false; 249 | } 250 | 251 | if ( 252 | !compiler.webpack.ModuleFilenameHelpers.matchObject.bind( 253 | undefined, 254 | this.options, 255 | )(name) 256 | ) { 257 | return false; 258 | } 259 | 260 | /** 261 | * @type {string | undefined} 262 | */ 263 | let relatedName; 264 | 265 | if (typeof this.options.algorithm === "function") { 266 | if (typeof this.options.filename === "function") { 267 | relatedName = `compression-function-${crypto 268 | .createHash("md5") 269 | .update(serialize(this.options.filename)) 270 | .digest("hex")}`; 271 | } else { 272 | /** 273 | * @type {string} 274 | */ 275 | let filenameForRelatedName = this.options.filename; 276 | 277 | const index = filenameForRelatedName.indexOf("?"); 278 | 279 | if (index >= 0) { 280 | filenameForRelatedName = filenameForRelatedName.slice(0, index); 281 | } 282 | 283 | relatedName = `${path 284 | .extname(filenameForRelatedName) 285 | .slice(1)}ed`; 286 | } 287 | } else if (this.options.algorithm === "gzip") { 288 | relatedName = "gzipped"; 289 | } else { 290 | relatedName = `${this.options.algorithm}ed`; 291 | } 292 | 293 | if (info.related && info.related[relatedName]) { 294 | return false; 295 | } 296 | 297 | const cacheItem = cache.getItemCache( 298 | serialize({ 299 | name, 300 | algorithm: this.options.algorithm, 301 | compressionOptions: this.options.compressionOptions, 302 | }), 303 | cache.getLazyHashedEtag(source), 304 | ); 305 | const output = (await cacheItem.getPromise()) || {}; 306 | 307 | let buffer; 308 | 309 | // No need original buffer for cached files 310 | if (!output.source) { 311 | if (typeof source.buffer === "function") { 312 | buffer = source.buffer(); 313 | } 314 | // Compatibility with webpack plugins which don't use `webpack-sources` 315 | // See https://github.com/webpack/compression-webpack-plugin/issues/236 316 | else { 317 | buffer = source.source(); 318 | 319 | if (!Buffer.isBuffer(buffer)) { 320 | buffer = Buffer.from(buffer); 321 | } 322 | } 323 | 324 | if (buffer.length < this.options.threshold) { 325 | return false; 326 | } 327 | } 328 | 329 | return { name, source, info, buffer, output, cacheItem, relatedName }; 330 | }), 331 | ) 332 | ).filter(Boolean); 333 | 334 | const { RawSource } = compiler.webpack.sources; 335 | const scheduledTasks = []; 336 | 337 | for (const asset of assetsForCompression) { 338 | scheduledTasks.push( 339 | (async () => { 340 | const { name, source, buffer, output, cacheItem, info, relatedName } = 341 | /** @type {AssetForCompression} */ 342 | (asset); 343 | 344 | if (!output.source) { 345 | if (!output.compressed) { 346 | try { 347 | output.compressed = await this.runCompressionAlgorithm(buffer); 348 | } catch (error) { 349 | compilation.errors.push(/** @type {WebpackError} */ (error)); 350 | 351 | return; 352 | } 353 | } 354 | 355 | if ( 356 | output.compressed.length / buffer.length > 357 | this.options.minRatio 358 | ) { 359 | await cacheItem.storePromise({ compressed: output.compressed }); 360 | 361 | return; 362 | } 363 | 364 | output.source = new RawSource(output.compressed); 365 | 366 | await cacheItem.storePromise(output); 367 | } 368 | 369 | const newFilename = compilation.getPath(this.options.filename, { 370 | filename: name, 371 | }); 372 | /** @type {AssetInfo} */ 373 | const newInfo = { compressed: true }; 374 | 375 | // TODO: possible problem when developer uses custom function, ideally we need to get parts of filename (i.e. name/base/ext/etc) in info 376 | // otherwise we can't detect an asset as immutable 377 | if ( 378 | info.immutable && 379 | typeof this.options.filename === "string" && 380 | /(\[name]|\[base]|\[file])/.test(this.options.filename) 381 | ) { 382 | newInfo.immutable = true; 383 | } 384 | 385 | if (this.options.deleteOriginalAssets) { 386 | if (this.options.deleteOriginalAssets === "keep-source-map") { 387 | compilation.updateAsset(name, source, { 388 | related: { sourceMap: null }, 389 | }); 390 | 391 | compilation.deleteAsset(name); 392 | } else if ( 393 | typeof this.options.deleteOriginalAssets === "function" 394 | ) { 395 | if (this.options.deleteOriginalAssets(name)) { 396 | compilation.deleteAsset(name); 397 | } 398 | } else { 399 | compilation.deleteAsset(name); 400 | } 401 | } else { 402 | compilation.updateAsset(name, source, { 403 | related: { [relatedName]: newFilename }, 404 | }); 405 | } 406 | 407 | compilation.emitAsset(newFilename, output.source, newInfo); 408 | })(), 409 | ); 410 | } 411 | 412 | await Promise.all(scheduledTasks); 413 | } 414 | 415 | /** 416 | * @param {Compiler} compiler compiler 417 | * @returns {void} 418 | */ 419 | apply(compiler) { 420 | const pluginName = this.constructor.name; 421 | 422 | compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { 423 | compilation.hooks.processAssets.tapPromise( 424 | { 425 | name: pluginName, 426 | stage: 427 | compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER, 428 | additionalAssets: true, 429 | }, 430 | (assets) => this.compress(compiler, compilation, assets), 431 | ); 432 | 433 | compilation.hooks.statsPrinter.tap(pluginName, (stats) => { 434 | stats.hooks.print 435 | .for("asset.info.compressed") 436 | .tap( 437 | "compression-webpack-plugin", 438 | (compressed, { green, formatFlag }) => 439 | compressed 440 | ? /** @type {((value: string | number) => string)} */ 441 | (green)( 442 | /** @type {(prefix: string) => string} */ 443 | (formatFlag)("compressed"), 444 | ) 445 | : "", 446 | ); 447 | }); 448 | }); 449 | } 450 | } 451 | 452 | module.exports = CompressionPlugin; 453 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | [![npm][npm]][npm-url] 8 | [![node][node]][node-url] 9 | [![tests][tests]][tests-url] 10 | [![cover][cover]][cover-url] 11 | [![discussion][discussion]][discussion-url] 12 | [![size][size]][size-url] 13 | 14 | # compression-webpack-plugin 15 | 16 | Prepare compressed versions of assets to serve them with Content-Encoding. 17 | 18 | ## Getting Started 19 | 20 | To begin, you'll need to install `compression-webpack-plugin`: 21 | 22 | ```console 23 | npm install compression-webpack-plugin --save-dev 24 | ``` 25 | 26 | or 27 | 28 | ```console 29 | yarn add -D compression-webpack-plugin 30 | ``` 31 | 32 | or 33 | 34 | ```console 35 | pnpm add -D compression-webpack-plugin 36 | ``` 37 | 38 | Then add the plugin to your `webpack` config. For example: 39 | 40 | **webpack.config.js** 41 | 42 | ```js 43 | const CompressionPlugin = require("compression-webpack-plugin"); 44 | 45 | module.exports = { 46 | plugins: [new CompressionPlugin()], 47 | }; 48 | ``` 49 | 50 | Finally, run `webpack` using the method you normally use (e.g., via CLI or an npm script). 51 | 52 | ## Options 53 | 54 | - **[`test`](#test)** 55 | - **[`include`](#include)** 56 | - **[`exclude`](#exclude)** 57 | - **[`algorithm`](#algorithm)** 58 | - **[`compressionOptions`](#compressionoptions)** 59 | - **[`threshold`](#threshold)** 60 | - **[`minRatio`](#minratio)** 61 | - **[`filename`](#filename)** 62 | - **[`deleteOriginalAssets`](#deleteoriginalassets)** 63 | 64 | ### `test` 65 | 66 | Type: 67 | 68 | ```ts 69 | type test = string | RegExp | (string | RegExp)[]; 70 | ``` 71 | 72 | Default: `undefined` 73 | 74 | Include all assets that pass test assertion. 75 | 76 | **webpack.config.js** 77 | 78 | ```js 79 | module.exports = { 80 | plugins: [ 81 | new CompressionPlugin({ 82 | test: /\.js(\?.*)?$/i, 83 | }), 84 | ], 85 | }; 86 | ``` 87 | 88 | ### `include` 89 | 90 | Type: 91 | 92 | ```ts 93 | type include = string | RegExp | (string | RegExp)[]; 94 | ``` 95 | 96 | Default: `undefined` 97 | 98 | Include all assets matching any of these conditions. 99 | 100 | **webpack.config.js** 101 | 102 | ```js 103 | module.exports = { 104 | plugins: [ 105 | new CompressionPlugin({ 106 | include: /\/includes/, 107 | }), 108 | ], 109 | }; 110 | ``` 111 | 112 | ### `exclude` 113 | 114 | Type: 115 | 116 | ```ts 117 | type exclude = string | RegExp | (string | RegExp)[]; 118 | ``` 119 | 120 | Default: `undefined` 121 | 122 | Exclude all assets matching any of these conditions. 123 | 124 | **webpack.config.js** 125 | 126 | ```js 127 | module.exports = { 128 | plugins: [ 129 | new CompressionPlugin({ 130 | exclude: /\/excludes/, 131 | }), 132 | ], 133 | }; 134 | ``` 135 | 136 | ### `algorithm` 137 | 138 | Type: 139 | 140 | ```ts 141 | type algorithm = 142 | | string 143 | | (( 144 | input: Buffer, 145 | options: CompressionOptions, 146 | callback: ( 147 | error: Error | null | undefined, 148 | result: 149 | | string 150 | | ArrayBuffer 151 | | SharedArrayBuffer 152 | | Uint8Array 153 | | readonly number[] 154 | | { 155 | valueOf(): ArrayBuffer | SharedArrayBuffer; 156 | } 157 | | { 158 | valueOf(): string | Uint8Array | readonly number[]; 159 | } 160 | | { 161 | valueOf(): string; 162 | } 163 | | { 164 | [Symbol.toPrimitive](hint: "string"): string; 165 | }, 166 | ) => void, 167 | ) => void); 168 | ``` 169 | 170 | Defines the compression algorithm or function to use. Defaults to `gzip`. 171 | 172 | > [!NOTE] 173 | > 174 | > If you use a custom function for the `algorithm` option, the default value of `compressionOptions` will be an empty object `{}`. 175 | 176 | #### `string` 177 | 178 | The algorithm is based on the Node.js [zlib](https://nodejs.org/api/zlib.html) module. 179 | 180 | **webpack.config.js** 181 | 182 | ```js 183 | module.exports = { 184 | plugins: [ 185 | new CompressionPlugin({ 186 | algorithm: "gzip", 187 | }), 188 | ], 189 | }; 190 | ``` 191 | 192 | #### `function` 193 | 194 | Allow you to specify a custom compression function. 195 | 196 | **webpack.config.js** 197 | 198 | ```js 199 | module.exports = { 200 | plugins: [ 201 | new CompressionPlugin({ 202 | algorithm(input, compressionOptions, callback) { 203 | return compressionFunction(input, compressionOptions, callback); 204 | }, 205 | }), 206 | ], 207 | }; 208 | ``` 209 | 210 | ### `compressionOptions` 211 | 212 | Type: 213 | 214 | ```ts 215 | interface compressionOptions { 216 | flush?: number; 217 | finishFlush?: number; 218 | chunkSize?: number; 219 | windowBits?: number; 220 | level?: number; 221 | memLevel?: number; 222 | strategy?: number; 223 | dictionary?: Buffer | TypedArray | DataView | ArrayBuffer; 224 | info?: boolean; 225 | maxOutputLength?: number; 226 | } 227 | ``` 228 | 229 | Default: `{ level: 9 }` 230 | 231 | Compression options for `algorithm`. 232 | 233 | You can find all available options in the [zlib](https://nodejs.org/api/zlib.html#zlib_class_options) documentation. 234 | 235 | > [!NOTE] 236 | > 237 | > If you use a custom function for the `algorithm` option, the default value of `compressionOptions` will be an empty object `{}`. 238 | 239 | **webpack.config.js** 240 | 241 | ```js 242 | module.exports = { 243 | plugins: [ 244 | new CompressionPlugin({ 245 | compressionOptions: { level: 1 }, 246 | }), 247 | ], 248 | }; 249 | ``` 250 | 251 | ### `threshold` 252 | 253 | Type: 254 | 255 | ```ts 256 | type threshold = number; 257 | ``` 258 | 259 | Default: `0` 260 | 261 | Only assets larger than this size (in bytes) are processed. 262 | 263 | **webpack.config.js** 264 | 265 | ```js 266 | module.exports = { 267 | plugins: [ 268 | new CompressionPlugin({ 269 | threshold: 8192, 270 | }), 271 | ], 272 | }; 273 | ``` 274 | 275 | ### `minRatio` 276 | 277 | Type: 278 | 279 | ```ts 280 | type minRatio = number; 281 | ``` 282 | 283 | Default: `0.8` 284 | 285 | Only assets that compress better than this ratio are processed (`minRatio = Compressed Size / Original Size`). 286 | For example, if you have a `image.png` file with a size of 1024 bytes, and its compressed version is of 768 bytes, the `minRatio` is `0.75`. 287 | In other words, assets will be processed only when the ratio of `Compressed Size / Original Size` is less than the specified `minRatio`. 288 | 289 | You can use a value of `1` to process assets that are smaller than or equal to the original size. 290 | 291 | Use a value of `Infinity` to process all assets, even if they are larger than the original size or their original size is `0` bytes (useful when you are pre-zipping all assets for AWS). 292 | 293 | Use a value of `Number.MAX_SAFE_INTEGER` to process all assets even if they are larger than the original size, excluding assets with their original size is `0` bytes. 294 | 295 | **webpack.config.js** 296 | 297 | ```js 298 | module.exports = { 299 | plugins: [ 300 | new CompressionPlugin({ 301 | // Compress all assets, including files with `0` bytes size 302 | // minRatio: Infinity 303 | 304 | // Compress all assets, excluding files with `0` bytes size 305 | // minRatio: Number.MAX_SAFE_INTEGER 306 | 307 | minRatio: 0.8, 308 | }), 309 | ], 310 | }; 311 | ``` 312 | 313 | ### `filename` 314 | 315 | Type: 316 | 317 | ```ts 318 | type filename = string | ((pathdata: PathData) => string); 319 | ``` 320 | 321 | Default: `"[path][base].gz"` 322 | 323 | The target asset filename. 324 | 325 | #### `string` 326 | 327 | For example, given an asset path: `assets/images/image.png?foo=bar#hash`: 328 | 329 | `[path]` is replaced with the directories of the original asset, including the trailing `/` (`assets/images/`). 330 | 331 | `[file]` is replaced with the path of the original asset (`assets/images/image.png`). 332 | 333 | `[base]` is replaced with the base name (`[name]` + `[ext]`) of the original asset (`image.png`). 334 | 335 | `[name]` is replaced with the name of the original asset (`image`). 336 | 337 | `[ext]` is replaced with the extension of the original asset, including the `.` (`.png`). 338 | 339 | `[query]` is replaced with the query of the original asset, including the `?` (`?foo=bar`). 340 | 341 | `[fragment]` is replaced with the fragment (in the concept of URL it is called `hash`) of the original asset (`#hash`). 342 | 343 | **webpack.config.js** 344 | 345 | ```js 346 | module.exports = { 347 | plugins: [ 348 | new CompressionPlugin({ 349 | filename: "[path][base].gz", 350 | }), 351 | ], 352 | }; 353 | ``` 354 | 355 | #### `function` 356 | 357 | **webpack.config.js** 358 | 359 | ```js 360 | module.exports = { 361 | plugins: [ 362 | new CompressionPlugin({ 363 | filename(pathData) { 364 | // The `pathData` argument contains all placeholders - `path`/`name`/`ext`/etc 365 | // Available properties described above, for the `String` notation 366 | if (/\.svg$/.test(pathData.filename)) { 367 | return "assets/svg/[path][base].gz"; 368 | } 369 | 370 | return "assets/js/[path][base].gz"; 371 | }, 372 | }), 373 | ], 374 | }; 375 | ``` 376 | 377 | ### `deleteOriginalAssets` 378 | 379 | Type: 380 | 381 | ```ts 382 | type deleteOriginalAssets = 383 | | boolean 384 | | "keep-source-map" 385 | | ((name: string) => boolean); 386 | ``` 387 | 388 | Default: `false` 389 | 390 | Determines whether the original (uncompressed) assets should be deleted after compression. 391 | 392 | - If set to `true` , all original assets will be deleted. 393 | 394 | - If set to `"keep-source-map"`, all original assets except source maps (`.map` files) will be deleted. 395 | 396 | - If a function is provided, it will be called with each asset’s name and should return `true` to delete the asset or `false` to keep it. 397 | 398 | Example: 399 | 400 | ```js 401 | module.exports = { 402 | plugins: [ 403 | new CompressionPlugin({ 404 | deleteOriginalAssets: (assetName) => 405 | // Delete all assets except images 406 | !assetName.endsWith(".png") && !assetName.endsWith(".jpg"), 407 | }), 408 | ], 409 | }; 410 | ``` 411 | 412 | **webpack.config.js** 413 | 414 | ```js 415 | module.exports = { 416 | plugins: [ 417 | new CompressionPlugin({ 418 | deleteOriginalAssets: true, 419 | }), 420 | ], 421 | }; 422 | ``` 423 | 424 | To exclude sourcemaps from compression: 425 | 426 | ```js 427 | module.exports = { 428 | plugins: [ 429 | new CompressionPlugin({ 430 | exclude: /.map$/, 431 | deleteOriginalAssets: "keep-source-map", 432 | }), 433 | ], 434 | }; 435 | ``` 436 | 437 | Using a custom function: 438 | 439 | ```js 440 | module.exports = { 441 | plugins: [ 442 | new CompressionPlugin({ 443 | exclude: /.map$/, 444 | deleteOriginalAssets: (name) => { 445 | if (/\.js$/.test(name)) { 446 | return false; 447 | } 448 | 449 | return true; 450 | }, 451 | }), 452 | ], 453 | }; 454 | ``` 455 | 456 | ## Examples 457 | 458 | ### Using Zopfli 459 | 460 | Prepare compressed versions of assets using the `zopfli` library. 461 | 462 | > [!NOTE] 463 | > 464 | > `@gfx/zopfli` requires at least `Node.js` version `8`. 465 | 466 | To begin, you'll need to install `@gfx/zopfli`: 467 | 468 | ```console 469 | $ npm install @gfx/zopfli --save-dev 470 | ``` 471 | 472 | **webpack.config.js** 473 | 474 | ```js 475 | const zopfli = require("@gfx/zopfli"); 476 | 477 | module.exports = { 478 | plugins: [ 479 | new CompressionPlugin({ 480 | compressionOptions: { 481 | numiterations: 15, 482 | }, 483 | algorithm(input, compressionOptions, callback) { 484 | return zopfli.gzip(input, compressionOptions, callback); 485 | }, 486 | }), 487 | ], 488 | }; 489 | ``` 490 | 491 | ### Using Brotli 492 | 493 | [Brotli](https://en.wikipedia.org/wiki/Brotli) is a compression algorithm originally developed by Google, and offers compression superior to gzip. 494 | 495 | Node.js v10.16.0 and later includes [native support](https://nodejs.org/api/zlib.html#zlib_zlib_createbrotlicompress_options) for Brotli compression in its `zlib` module. 496 | 497 | You can take advantage of this built-in support for Brotli in Node 10.16.0 and later by just passing in the appropriate `algorithm` to the CompressionPlugin: 498 | 499 | **webpack.config.js** 500 | 501 | ```js 502 | const zlib = require("node:zlib"); 503 | 504 | module.exports = { 505 | plugins: [ 506 | new CompressionPlugin({ 507 | filename: "[path][base].br", 508 | algorithm: "brotliCompress", 509 | test: /\.(js|css|html|svg)$/, 510 | compressionOptions: { 511 | params: { 512 | [zlib.constants.BROTLI_PARAM_QUALITY]: 11, 513 | }, 514 | }, 515 | threshold: 10240, 516 | minRatio: 0.8, 517 | deleteOriginalAssets: false, 518 | }), 519 | ], 520 | }; 521 | ``` 522 | 523 | [!NOTE] Brotli’s `BROTLI_PARAM_QUALITY` option is functionally equivalent to zlib’s `level` option. 524 | You can find all Brotli’s options in [the relevant part of the zlib module documentation](https://nodejs.org/api/zlib.html#zlib_class_brotlioptions). 525 | 526 | ### Using Zstandard 527 | 528 | [Zstandard](https://facebook.github.io/zstd/) (zstd) is a fast lossless compression algorithm, targeting real-time compression scenarios at zlib-level and better compression ratios. 529 | 530 | Node.js 22.15.0 and later includes [native support](https://nodejs.org/api/zlib.html#zlibcreatezstdcompressoptions) for Zstandard compression in its `zlib` module. 531 | 532 | You can take advantage of this built-in support for zstd in Node 22.15.0 and later by just passing in the appropriate `algorithm` to the CompressionPlugin: 533 | 534 | **webpack.config.js** 535 | 536 | ```js 537 | const zlib = require("node:zlib"); 538 | 539 | module.exports = { 540 | plugins: [ 541 | new CompressionPlugin({ 542 | filename: "[path][base].zst", 543 | algorithm: "zstdCompress", 544 | test: /\.(js|css|html|svg)$/, 545 | compressionOptions: { 546 | params: { 547 | [zlib.constants.ZSTD_c_compressionLevel]: 10, 548 | }, 549 | }, 550 | threshold: 10240, 551 | minRatio: 0.8, 552 | deleteOriginalAssets: false, 553 | }), 554 | ], 555 | }; 556 | ``` 557 | 558 | You can find all Zstandard's options in [the relevant part of the zlib module documentation](https://nodejs.org/api/zlib.html#class-zstdoptions). 559 | 560 | ### Multiple compressed versions of assets for different algorithm 561 | 562 | **webpack.config.js** 563 | 564 | ```js 565 | const zlib = require("node:zlib"); 566 | 567 | module.exports = { 568 | plugins: [ 569 | new CompressionPlugin({ 570 | filename: "[path][base].gz", 571 | algorithm: "gzip", 572 | test: /\.js$|\.css$|\.html$/, 573 | threshold: 10240, 574 | minRatio: 0.8, 575 | }), 576 | new CompressionPlugin({ 577 | filename: "[path][base].br", 578 | algorithm: "brotliCompress", 579 | test: /\.(js|css|html|svg)$/, 580 | compressionOptions: { 581 | params: { 582 | [zlib.constants.BROTLI_PARAM_QUALITY]: 11, 583 | }, 584 | }, 585 | threshold: 10240, 586 | minRatio: 0.8, 587 | }), 588 | ], 589 | }; 590 | ``` 591 | 592 | ## Contributing 593 | 594 | We welcome contributions! 595 | 596 | Please take a moment to read our contributing guidelines if you haven't yet done so. 597 | 598 | [CONTRIBUTING](https://github.com/webpack/compression-webpack-plugin?tab=contributing-ov-file#contributing) 599 | 600 | ## License 601 | 602 | [MIT](./LICENSE) 603 | 604 | [npm]: https://img.shields.io/npm/v/compression-webpack-plugin.svg 605 | [npm-url]: https://npmjs.com/package/compression-webpack-plugin 606 | [node]: https://img.shields.io/node/v/compression-webpack-plugin.svg 607 | [node-url]: https://nodejs.org 608 | [tests]: https://github.com/webpack/compression-webpack-plugin/workflows/compression-webpack-plugin/badge.svg 609 | [tests-url]: https://github.com/webpack/compression-webpack-plugin/actions 610 | [cover]: https://codecov.io/gh/webpack/compression-webpack-plugin/branch/main/graph/badge.svg 611 | [cover-url]: https://codecov.io/gh/webpack/compression-webpack-plugin 612 | [discussion]: https://img.shields.io/github/discussions/webpack/webpack 613 | [discussion-url]: https://github.com/webpack/webpack/discussions 614 | [size]: https://packagephobia.now.sh/badge?p=compression-webpack-plugin 615 | [size-url]: https://packagephobia.now.sh/result?p=compression-webpack-plugin 616 | -------------------------------------------------------------------------------- /test/CompressionPlugin.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import zlib from "node:zlib"; 3 | 4 | import { StatsWriterPlugin } from "webpack-stats-plugin"; 5 | import { GenerateSW, InjectManifest } from "workbox-webpack-plugin"; 6 | 7 | import CompressionPlugin from "../src/index"; 8 | 9 | import { 10 | CopyPluginWithAssetInfo, 11 | EmitNewAsset, 12 | ModifyExistingAsset, 13 | compile, 14 | getAssetsNameAndSize, 15 | getCompiler, 16 | getErrors, 17 | getWarnings, 18 | } from "./helpers/index"; 19 | 20 | describe("CompressionPlugin", () => { 21 | it("should work", async () => { 22 | const compiler = getCompiler( 23 | "./entry.js", 24 | {}, 25 | { 26 | output: { 27 | path: path.join(__dirname, "./dist"), 28 | filename: "[name].js?var=[contenthash]", 29 | chunkFilename: "[id].[name].js?ver=[contenthash]", 30 | }, 31 | }, 32 | ); 33 | 34 | new CompressionPlugin().apply(compiler); 35 | 36 | const stats = await compile(compiler); 37 | 38 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 39 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 40 | expect(getErrors(stats)).toMatchSnapshot("errors"); 41 | }); 42 | 43 | it("should work with assets info", async () => { 44 | const compiler = getCompiler( 45 | "./entry.js", 46 | {}, 47 | { 48 | devtool: "source-map", 49 | output: { 50 | path: path.join(__dirname, "./dist"), 51 | filename: "[name].js?var=[contenthash]", 52 | chunkFilename: "[id].[name].js?ver=[contenthash]", 53 | }, 54 | }, 55 | ); 56 | 57 | new CompressionPlugin().apply(compiler); 58 | new CopyPluginWithAssetInfo().apply(compiler); 59 | 60 | const stats = await compile(compiler); 61 | 62 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 63 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 64 | expect(getErrors(stats)).toMatchSnapshot("errors"); 65 | }); 66 | 67 | it("should work child compilations", async () => { 68 | const gzipSpy = jest.spyOn(zlib, "gzip"); 69 | const compiler = getCompiler( 70 | "./entry.js", 71 | {}, 72 | { 73 | output: { 74 | path: path.join(__dirname, "./dist"), 75 | filename: "[name].js?var=[contenthash]", 76 | chunkFilename: "[id].[name].js?ver=[contenthash]", 77 | }, 78 | module: { 79 | rules: [ 80 | { 81 | test: /number\.js$/i, 82 | rules: [ 83 | { 84 | loader: require.resolve( 85 | "./helpers/loader-with-child-compilation.js", 86 | ), 87 | }, 88 | ], 89 | }, 90 | { 91 | test: /\.(png|jpg|gif|svg)$/i, 92 | rules: [ 93 | { 94 | loader: "file-loader", 95 | }, 96 | ], 97 | }, 98 | ], 99 | }, 100 | }, 101 | ); 102 | 103 | new CompressionPlugin().apply(compiler); 104 | 105 | const stats = await compile(compiler); 106 | 107 | expect(gzipSpy).toHaveBeenCalledTimes(5); 108 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 109 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 110 | expect(getErrors(stats)).toMatchSnapshot("errors"); 111 | 112 | gzipSpy.mockRestore(); 113 | }); 114 | 115 | it("should work with multiple plugins", async () => { 116 | const compiler = getCompiler( 117 | "./entry.js", 118 | {}, 119 | { 120 | output: { 121 | path: path.join(__dirname, "./dist"), 122 | filename: "[name].js?var=[contenthash]", 123 | chunkFilename: "[id].[name].js?ver=[contenthash]", 124 | }, 125 | }, 126 | ); 127 | 128 | new CompressionPlugin({ 129 | algorithm: "gzip", 130 | filename: "[path][base].gz", 131 | }).apply(compiler); 132 | new CompressionPlugin({ 133 | algorithm: "brotliCompress", 134 | filename: "[path][base].br", 135 | }).apply(compiler); 136 | new CompressionPlugin({ 137 | minRatio: Infinity, 138 | algorithm: (input, options, callback) => callback(null, input), 139 | filename: "[path][base].compress", 140 | }).apply(compiler); 141 | new CompressionPlugin({ 142 | minRatio: Infinity, 143 | algorithm: (input, options, callback) => callback(null, input), 144 | filename: "[path][base].custom?foo=bar#hash", 145 | }).apply(compiler); 146 | 147 | const stats = await compile(compiler); 148 | 149 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 150 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 151 | expect(getErrors(stats)).toMatchSnapshot("errors"); 152 | }); 153 | 154 | it("should work and show compress assets in stats", async () => { 155 | const compiler = getCompiler( 156 | "./entry.js", 157 | {}, 158 | { 159 | stats: "verbose", 160 | output: { 161 | path: path.join(__dirname, "./dist"), 162 | filename: "[name].js", 163 | chunkFilename: "[id].[name].js", 164 | }, 165 | }, 166 | ); 167 | 168 | new CompressionPlugin().apply(compiler); 169 | 170 | const stats = await compile(compiler); 171 | const stringStats = stats.toString({ relatedAssets: true }); 172 | const printedCompressed = stringStats.match(/\[compressed]/g); 173 | 174 | expect(printedCompressed ? printedCompressed.length : 0).toBe(3); 175 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 176 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 177 | expect(getErrors(stats)).toMatchSnapshot("errors"); 178 | }); 179 | 180 | it("should work and keep assets info", async () => { 181 | const compiler = getCompiler( 182 | "./entry.js", 183 | {}, 184 | { 185 | stats: "verbose", 186 | output: { 187 | path: path.join(__dirname, "./dist"), 188 | filename: "[name].[contenthash].js", 189 | chunkFilename: "[id].[name].[contenthash].js", 190 | }, 191 | }, 192 | ); 193 | 194 | new CompressionPlugin().apply(compiler); 195 | 196 | const stats = await compile(compiler); 197 | 198 | for (const [, info] of stats.compilation.assetsInfo.entries()) { 199 | expect(info.immutable).toBe(true); 200 | } 201 | 202 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 203 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 204 | expect(getErrors(stats)).toMatchSnapshot("errors"); 205 | }); 206 | 207 | it('should work and use memory cache without options in the "development" mode', async () => { 208 | const compiler = getCompiler("./entry.js", {}, { mode: "development" }); 209 | 210 | new CompressionPlugin().apply(compiler); 211 | 212 | const stats = await compile(compiler); 213 | 214 | expect(stats.compilation.emittedAssets.size).toBe(7); 215 | 216 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 217 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 218 | expect(getErrors(stats)).toMatchSnapshot("errors"); 219 | 220 | const newStats = await compile(compiler); 221 | 222 | expect(newStats.compilation.emittedAssets.size).toBe(0); 223 | 224 | expect(getAssetsNameAndSize(newStats, compiler)).toMatchSnapshot("assets"); 225 | expect(getWarnings(newStats)).toMatchSnapshot("errors"); 226 | expect(getErrors(newStats)).toMatchSnapshot("warnings"); 227 | }); 228 | 229 | it('should work and use memory cache when the "cache" option is "true"', async () => { 230 | const compiler = getCompiler( 231 | "./entry.js", 232 | {}, 233 | { 234 | cache: true, 235 | output: { 236 | path: path.resolve(__dirname, "./outputs"), 237 | filename: "[name].js", 238 | chunkFilename: "[id].js", 239 | }, 240 | }, 241 | ); 242 | 243 | new CompressionPlugin().apply(compiler); 244 | 245 | const stats = await compile(compiler); 246 | 247 | expect(stats.compilation.emittedAssets.size).toBe(7); 248 | 249 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 250 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 251 | expect(getErrors(stats)).toMatchSnapshot("errors"); 252 | 253 | const newStats = await compile(compiler); 254 | 255 | expect(newStats.compilation.emittedAssets.size).toBe(0); 256 | 257 | expect(getAssetsNameAndSize(newStats, compiler)).toMatchSnapshot("assets"); 258 | expect(getWarnings(newStats)).toMatchSnapshot("errors"); 259 | expect(getErrors(newStats)).toMatchSnapshot("warnings"); 260 | }); 261 | 262 | it('should work and use memory cache when the "cache" option is "true" and the asset has been changed', async () => { 263 | const compiler = getCompiler( 264 | "./entry.js", 265 | {}, 266 | { 267 | cache: true, 268 | output: { 269 | path: path.resolve(__dirname, "./outputs"), 270 | filename: "[name].js", 271 | chunkFilename: "[id].js", 272 | }, 273 | }, 274 | ); 275 | 276 | new CompressionPlugin().apply(compiler); 277 | 278 | const stats = await compile(compiler); 279 | 280 | expect(stats.compilation.emittedAssets.size).toBe(7); 281 | 282 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 283 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 284 | expect(getErrors(stats)).toMatchSnapshot("errors"); 285 | 286 | new ModifyExistingAsset({ 287 | name: "main.js", 288 | content: "function changed() { /*! CHANGED */ }", 289 | }).apply(compiler); 290 | 291 | const newStats = await compile(compiler); 292 | 293 | expect(newStats.compilation.emittedAssets.size).toBe(2); 294 | 295 | expect(getAssetsNameAndSize(newStats, compiler)).toMatchSnapshot("assets"); 296 | expect(getWarnings(newStats)).toMatchSnapshot("errors"); 297 | expect(getErrors(newStats)).toMatchSnapshot("warnings"); 298 | }); 299 | 300 | it('should work and use memory cache when the "cache" option is "true" and the asset has been changed which filtered by the "minRation" option', async () => { 301 | const compiler = getCompiler( 302 | "./entry.js", 303 | { 304 | name: "[name].[ext]", 305 | }, 306 | { 307 | cache: true, 308 | output: { 309 | path: path.resolve(__dirname, "./outputs"), 310 | filename: "[name].js", 311 | chunkFilename: "[id].js", 312 | }, 313 | }, 314 | ); 315 | 316 | new CompressionPlugin().apply(compiler); 317 | 318 | const stats = await compile(compiler); 319 | 320 | expect(stats.compilation.emittedAssets.size).toBe(7); 321 | 322 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 323 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 324 | expect(getErrors(stats)).toMatchSnapshot("errors"); 325 | 326 | new ModifyExistingAsset({ 327 | name: "icon.png", 328 | content: "1q!Q2w@W3e#e4r$r".repeat(1000), 329 | }).apply(compiler); 330 | 331 | const newStats = await compile(compiler); 332 | 333 | expect(newStats.compilation.emittedAssets.size).toBe(2); 334 | 335 | expect(getAssetsNameAndSize(newStats, compiler)).toMatchSnapshot("assets"); 336 | expect(getWarnings(newStats)).toMatchSnapshot("errors"); 337 | expect(getErrors(newStats)).toMatchSnapshot("warnings"); 338 | }); 339 | 340 | it('should work and use memory cache when the "cache" option is "true" with multiple plugins', async () => { 341 | const compiler = getCompiler( 342 | "./entry.js", 343 | {}, 344 | { 345 | cache: true, 346 | output: { 347 | path: path.resolve(__dirname, "./outputs"), 348 | filename: "[name].js", 349 | chunkFilename: "[id].js", 350 | }, 351 | }, 352 | ); 353 | 354 | new CompressionPlugin({ 355 | filename: "[path][base].gz", 356 | algorithm: "gzip", 357 | }).apply(compiler); 358 | new CompressionPlugin({ 359 | filename: "[path][base].br", 360 | algorithm: "brotliCompress", 361 | }).apply(compiler); 362 | new CompressionPlugin({ 363 | minRatio: Infinity, 364 | algorithm: (input, options, callback) => callback(null, input), 365 | filename: "[path][base].custom", 366 | }).apply(compiler); 367 | 368 | const stats = await compile(compiler); 369 | 370 | expect(stats.compilation.emittedAssets.size).toBe(14); 371 | 372 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 373 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 374 | expect(getErrors(stats)).toMatchSnapshot("errors"); 375 | 376 | const newStats = await compile(compiler); 377 | 378 | expect(newStats.compilation.emittedAssets.size).toBe(0); 379 | 380 | expect(getAssetsNameAndSize(newStats, compiler)).toMatchSnapshot("assets"); 381 | expect(getWarnings(newStats)).toMatchSnapshot("errors"); 382 | expect(getErrors(newStats)).toMatchSnapshot("warnings"); 383 | }); 384 | 385 | it('should work and do not use memory cache when the "cache" option is "false"', async () => { 386 | const compiler = getCompiler( 387 | "./entry.js", 388 | { 389 | name: "[name].[ext]", 390 | }, 391 | { 392 | cache: false, 393 | output: { 394 | path: path.resolve(__dirname, "./outputs"), 395 | filename: "[name].js", 396 | chunkFilename: "[id].[name].js", 397 | }, 398 | }, 399 | ); 400 | 401 | new CompressionPlugin().apply(compiler); 402 | 403 | const stats = await compile(compiler); 404 | 405 | expect(stats.compilation.emittedAssets.size).toBe(7); 406 | 407 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 408 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 409 | expect(getErrors(stats)).toMatchSnapshot("errors"); 410 | 411 | const newStats = await compile(compiler); 412 | 413 | expect(newStats.compilation.emittedAssets.size).toBe(7); 414 | 415 | expect(getAssetsNameAndSize(newStats, compiler)).toMatchSnapshot("assets"); 416 | expect(getWarnings(newStats)).toMatchSnapshot("errors"); 417 | expect(getErrors(newStats)).toMatchSnapshot("warnings"); 418 | }); 419 | 420 | it("should run plugin against assets added later by plugins", async () => { 421 | const compiler = getCompiler( 422 | "./number.js", 423 | {}, 424 | { 425 | output: { 426 | path: path.resolve(__dirname, "./outputs"), 427 | filename: "[name].js", 428 | chunkFilename: "[id].js", 429 | }, 430 | }, 431 | ); 432 | 433 | new CompressionPlugin({ minRatio: 10 }).apply(compiler); 434 | new EmitNewAsset({ name: "newFile.js" }).apply(compiler); 435 | 436 | const stats = await compile(compiler); 437 | 438 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 439 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 440 | expect(getErrors(stats)).toMatchSnapshot("errors"); 441 | }); 442 | 443 | // TODO broken on windows https://github.com/GoogleChrome/workbox/issues/2667 444 | // eslint-disable-next-line jest/no-disabled-tests 445 | it.skip("should work with 'workbox-webpack-plugin' (GenerateSW)", async () => { 446 | const compiler = getCompiler( 447 | "./entry.js", 448 | {}, 449 | { 450 | output: { 451 | path: path.join(__dirname, "./dist"), 452 | filename: "[name].js?var=[contenthash]", 453 | chunkFilename: "[id].[name].js?ver=[contenthash]", 454 | }, 455 | }, 456 | ); 457 | 458 | new CompressionPlugin().apply(compiler); 459 | new GenerateSW().apply(compiler); 460 | 461 | const stats = await compile(compiler); 462 | 463 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 464 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 465 | expect(getErrors(stats)).toMatchSnapshot("errors"); 466 | }); 467 | 468 | // TODO broken on windows https://github.com/GoogleChrome/workbox/issues/2667 469 | // eslint-disable-next-line jest/no-disabled-tests 470 | it.skip("should work with 'workbox-webpack-plugin' (InjectManifest)", async () => { 471 | const compiler = getCompiler( 472 | "./entry.js", 473 | {}, 474 | { 475 | output: { 476 | path: path.join(__dirname, "./dist"), 477 | filename: "[name].js?var=[contenthash]", 478 | chunkFilename: "[id].[name].js?ver=[contenthash]", 479 | }, 480 | }, 481 | ); 482 | 483 | new CompressionPlugin().apply(compiler); 484 | new InjectManifest({ 485 | swSrc: path.resolve(__dirname, "./fixtures/sw.js"), 486 | }).apply(compiler); 487 | 488 | const stats = await compile(compiler); 489 | 490 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 491 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 492 | expect(getErrors(stats)).toMatchSnapshot("errors"); 493 | }); 494 | 495 | it("should work with 'webpack-stats-plugin'", async () => { 496 | const compiler = getCompiler("./entry.js"); 497 | 498 | new CompressionPlugin().apply(compiler); 499 | new StatsWriterPlugin().apply(compiler); 500 | 501 | const stats = await compile(compiler); 502 | 503 | expect(getAssetsNameAndSize(stats, compiler)).toMatchSnapshot("assets"); 504 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 505 | expect(getErrors(stats)).toMatchSnapshot("errors"); 506 | }); 507 | }); 508 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [11.1.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v11.0.0...v11.1.0) (2024-02-27) 6 | 7 | 8 | ### Features 9 | 10 | * support the `deleteOriginalAssets` option as a function ([#380](https://github.com/webpack-contrib/compression-webpack-plugin/issues/380)) ([1be8955](https://github.com/webpack-contrib/compression-webpack-plugin/commit/1be895599c15c7da796ef5131de4f705de3f2635)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * `test`/`include`/`exclude` types ([#379](https://github.com/webpack-contrib/compression-webpack-plugin/issues/379)) ([4c9f6f4](https://github.com/webpack-contrib/compression-webpack-plugin/commit/4c9f6f4f4b3a1e61962e190ea2d651d23175aa18)) 16 | 17 | ## [11.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v10.0.0...v11.0.0) (2024-01-15) 18 | 19 | 20 | ### ⚠ BREAKING CHANGES 21 | 22 | * minimum supported Node.js version is `18.12.0` ([2ef7b37](https://github.com/webpack-contrib/compression-webpack-plugin/commit/2ef7b3724a841285a0dcadc2bd68c307d299ef48)) 23 | 24 | ## [10.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v9.2.0...v10.0.0) (2022-05-17) 25 | 26 | 27 | ### ⚠ BREAKING CHANGES 28 | 29 | * default filename is `[path][base].br` for brotli (#286) 30 | * minimum supported `Node.js` version is `14.15.0` (#301) 31 | 32 | ### Bug Fixes 33 | 34 | * default filename is `[path][base].br` for brotli ([#286](https://github.com/webpack-contrib/compression-webpack-plugin/issues/286)) ([db04e14](https://github.com/webpack-contrib/compression-webpack-plugin/commit/db04e144f2beb4ff0993a007c11d51ade70cc741)) 35 | 36 | 37 | * minimum supported `Node.js` version is `14.15.0` ([#301](https://github.com/webpack-contrib/compression-webpack-plugin/issues/301)) ([5db3255](https://github.com/webpack-contrib/compression-webpack-plugin/commit/5db32551830f58bb52c990c8ea94e0e4bd8efee6)) 38 | 39 | ## [9.2.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v9.1.2...v9.2.0) (2021-12-16) 40 | 41 | 42 | ### Features 43 | 44 | * removed cjs wrapper and generated types in commonjs format (`export =` and `namespaces` used in types), now you can directly use exported types ([#277](https://github.com/webpack-contrib/compression-webpack-plugin/issues/277)) ([8664d36](https://github.com/webpack-contrib/compression-webpack-plugin/commit/8664d36f2ea36afc52cc01998f543c055dc1da15)) 45 | 46 | ### [9.1.2](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v9.1.0...v9.1.2) (2021-12-13) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * types ([76009b7](https://github.com/webpack-contrib/compression-webpack-plugin/commit/76009b7ecd7495e56f9a59b60abfab4c20dca4b9)) 52 | * types ([#276](https://github.com/webpack-contrib/compression-webpack-plugin/issues/276)) ([c5c3dba](https://github.com/webpack-contrib/compression-webpack-plugin/commit/c5c3dba72b91bb020c3c142c43f264869ccde3d1)) 53 | 54 | ### [9.1.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v9.1.0...v9.1.1) (2021-12-09) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * added `types` to `package.json` ([0acff90](https://github.com/webpack-contrib/compression-webpack-plugin/commit/0acff903ded3fae933e0cc2c9727e3637310f5af)) 60 | 61 | ## [9.1.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v9.0.1...v9.1.0) (2021-12-09) 62 | 63 | 64 | ### Features 65 | 66 | * added types ([#269](https://github.com/webpack-contrib/compression-webpack-plugin/issues/269)) ([02d9b2a](https://github.com/webpack-contrib/compression-webpack-plugin/commit/02d9b2a672985c7862bced28f5927e779eccbba6)) 67 | 68 | ### [9.0.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v9.0.0...v9.0.1) (2021-11-17) 69 | 70 | 71 | ### Chore 72 | 73 | * update `schema-utils` package to `4.0.0` version 74 | 75 | ## [9.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v8.0.1...v9.0.0) (2021-09-11) 76 | 77 | 78 | ### ⚠ BREAKING CHANGES 79 | 80 | * `pathData` as first argument of a custom function for the `filename` option was changed, now we pass only `pathData.filename`, see [example](https://github.com/webpack-contrib/compression-webpack-plugin#function-1) 81 | 82 | ### Features 83 | 84 | * migrate on webpack API for getting filename ([59fe68c](https://github.com/webpack-contrib/compression-webpack-plugin/commit/59fe68c2c6047c9a6120856b3aa4fc6ab1fc811d)) 85 | * output documentation links on errors ([#255](https://github.com/webpack-contrib/compression-webpack-plugin/issues/255)) ([8624e44](https://github.com/webpack-contrib/compression-webpack-plugin/commit/8624e44b9e76dacb8d67d762c647777c6a8f9573)) 86 | 87 | ### [8.0.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v8.0.0...v8.0.1) (2021-06-25) 88 | 89 | ### Chore 90 | 91 | * update `serialize-javascript` 92 | 93 | ## [8.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v7.1.2...v8.0.0) (2021-05-21) 94 | 95 | 96 | ### ⚠ BREAKING CHANGES 97 | 98 | * minimum supported `Node.js` version is `12.13.0` 99 | 100 | ### [7.1.2](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v7.1.1...v7.1.2) (2021-01-11) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * crash when `filename` and `algorithm` options are functions ([#241](https://github.com/webpack-contrib/compression-webpack-plugin/issues/241)) ([f33424a](https://github.com/webpack-contrib/compression-webpack-plugin/commit/f33424a441323eaeb9415886f8c7f4bb95f01f6b)) 106 | 107 | ### [7.1.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v7.1.0...v7.1.1) (2020-12-25) 108 | 109 | 110 | ### Bug Fixes 111 | 112 | * compatibility with old plugins ([#237](https://github.com/webpack-contrib/compression-webpack-plugin/issues/237)) ([863c340](https://github.com/webpack-contrib/compression-webpack-plugin/commit/863c34032c5a025c0ea64c2fca24e35dae5f2bfb)) 113 | 114 | ## [7.1.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v7.0.0...v7.1.0) (2020-12-18) 115 | 116 | 117 | ### Features 118 | 119 | * compress assets added later by plugins ([5b8b356](https://github.com/webpack-contrib/compression-webpack-plugin/commit/5b8b356255fe970d5cb9ffdbc4be44d41e201dc6)) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * compatibility with `workbox-webpack-plugin` ([#234](https://github.com/webpack-contrib/compression-webpack-plugin/issues/234)) ([5d54128](https://github.com/webpack-contrib/compression-webpack-plugin/commit/5d54128682b7e43432e95e6b05b6d9fa3d69fc4c)) 125 | * perf 126 | 127 | ## [7.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.1.1...v7.0.0) (2020-12-02) 128 | 129 | 130 | ### ⚠ BREAKING CHANGES 131 | 132 | * minimum supported webpack version is `^5.1.0` 133 | * the `cache` option was removed, the plugin respects caching from configurations, please [read](https://webpack.js.org/configuration/other-options/#cache) 134 | 135 | ### [6.1.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.1.0...v6.1.1) (2020-11-12) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * compatibility with child compilations ([5e3bb95](https://github.com/webpack-contrib/compression-webpack-plugin/commit/5e3bb9516499438e43dba69db6257b37da4fc5ef)) 141 | 142 | ## [6.1.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.0.5...v6.1.0) (2020-11-09) 143 | 144 | 145 | ### Features 146 | 147 | * added the `keep-source-maps` value to the `deleteOriginalAssets` option ([#216](https://github.com/webpack-contrib/compression-webpack-plugin/issues/216)) ([bd60650](https://github.com/webpack-contrib/compression-webpack-plugin/commit/bd6065030c387ac371a2685c445b27d96be09868)) 148 | 149 | ### [6.0.5](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.0.4...v6.0.5) (2020-11-02) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * allowed compressed assets to overwrite original assets using the `deleteOriginalAssets` option ([62d3d0a](https://github.com/webpack-contrib/compression-webpack-plugin/commit/62d3d0a39186b160b5f1e175e883bba55ba984ff)) 155 | 156 | ### [6.0.4](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.0.3...v6.0.4) (2020-10-26) 157 | 158 | 159 | ### Bug Fixes 160 | 161 | * always set compression level to maximum for the custom `algorithm` ([483f328](https://github.com/webpack-contrib/compression-webpack-plugin/commit/483f328d571b1953db36fbc759570b5fa90d474e)) 162 | 163 | ### [6.0.3](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.0.2...v6.0.3) (2020-10-09) 164 | 165 | ### Chore 166 | 167 | * update `schema-utils` 168 | 169 | ### [6.0.2](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.0.1...v6.0.2) (2020-09-19) 170 | 171 | 172 | ### Bug Fixes 173 | 174 | * cache invalidation ([2284b0c](https://github.com/webpack-contrib/compression-webpack-plugin/commit/2284b0c8eb44040bed648b78804af2f627188172)) 175 | 176 | ### [6.0.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v6.0.0...v6.0.1) (2020-09-16) 177 | 178 | 179 | ### Bug Fixes 180 | 181 | * respect directories in output ([#200](https://github.com/webpack-contrib/compression-webpack-plugin/issues/200)) ([91382cf](https://github.com/webpack-contrib/compression-webpack-plugin/commit/91382cfe4c01cedcfcbce546cb992112ffa4374a)) 182 | 183 | ## [6.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v5.0.2...v6.0.0) (2020-09-14) 184 | 185 | 186 | ### ⚠ BREAKING CHANGES 187 | 188 | * default value of the `filename` option was changed to `"[path][base].gz"` 189 | * removed the `[dir]` placeholder, please use the `[path]` placeholder 190 | * the `Function` type of the `filename` option should return value with placeholders, please see an [example](https://github.com/webpack-contrib/compression-webpack-plugin#function-1) 191 | 192 | ### Features 193 | 194 | * added `[fragment]`, `[base]` and `[path]` placeholders for the `filename` option 195 | 196 | ### Bug Fixes 197 | 198 | * caching ([#194](https://github.com/webpack-contrib/compression-webpack-plugin/issues/194)) ([9de2a88](https://github.com/webpack-contrib/compression-webpack-plugin/commit/9de2a88ccb7b16a84a4e1e08ae0889bc589a0089)) 199 | * respect `immutable` flag for assets 200 | 201 | ### [5.0.2](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v5.0.1...v5.0.2) (2020-09-02) 202 | 203 | 204 | ### Bug Fixes 205 | 206 | * do not crash when the `algorithm` option return non `Buffer` ([#190](https://github.com/webpack-contrib/compression-webpack-plugin/issues/190)) ([81bf601](https://github.com/webpack-contrib/compression-webpack-plugin/commit/81bf60166caeeea9f5d5a18e1471ed0f7a28c312)) 207 | 208 | ### [5.0.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v5.0.0...v5.0.1) (2020-08-22) 209 | 210 | 211 | ### Bug Fixes 212 | 213 | * compatibility with webpack@5 ([84e367b](https://github.com/webpack-contrib/compression-webpack-plugin/commit/84e367b03c9440afd9cab410c680837829543cbe)) 214 | 215 | ## [5.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v4.0.1...v5.0.0) (2020-08-17) 216 | 217 | 218 | ### ⚠ BREAKING CHANGES 219 | 220 | * default value of the `filename` option is `'[path].gz'` 221 | * use `processAssets` hook for `webpack@5` compatibility, it can create incompatibility with plugins that do not support `webpack@5`, please open an issue in their repositories 222 | 223 | ### Features 224 | 225 | * improve compatibility with `webpack@5` ([1f9674e](https://github.com/webpack-contrib/compression-webpack-plugin/commit/1f9674e7573a2b30822c08d4c719fc4849cce793)) 226 | * improve stats output for `webpack@5` 227 | 228 | ### Bug Fixes 229 | 230 | * fix persistent cache between builds 231 | 232 | ### [4.0.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v4.0.0...v4.0.1) (2020-08-12) 233 | 234 | 235 | ### Bug Fixes 236 | 237 | * cache invalidation ([025b778](https://github.com/webpack-contrib/compression-webpack-plugin/commit/025b778fdcd43681dac3b9a693b375d6abf79852)) 238 | * memory leak ([#174](https://github.com/webpack-contrib/compression-webpack-plugin/issues/174)) ([2a1e884](https://github.com/webpack-contrib/compression-webpack-plugin/commit/2a1e884a7f6781e13e08d82c10d244eb1868cd55)) 239 | 240 | ## [4.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v3.1.0...v4.0.0) (2020-05-12) 241 | 242 | 243 | ### ⚠ BREAKING CHANGES 244 | 245 | * the `cache` is `true` by default for webpack@4 246 | * the `cache` option is ignored in webpack 5. Please use https://webpack.js.org/configuration/other-options/#cache. 247 | * minimum supported Node.js version is `10.13` 248 | 249 | ### Features 250 | 251 | * enable cache by default for webpack@4 ([#164](https://github.com/webpack-contrib/compression-webpack-plugin/issues/164)) ([ea33463](https://github.com/webpack-contrib/compression-webpack-plugin/commit/ea33463b2c9986fa5dc744bd2860620af7404a7f)) 252 | 253 | 254 | ### Bug Fixes 255 | 256 | * behaviour in watch mode ([e222c4e](https://github.com/webpack-contrib/compression-webpack-plugin/commit/e222c4ee7554c0d3ce3a80749b6087b40ffd5ed1)) 257 | 258 | ## [3.1.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v3.0.0...v3.1.0) (2020-01-09) 259 | 260 | 261 | ### Features 262 | 263 | * add `dir`, `name` and `ext` placeholders in filename option ([#144](https://github.com/webpack-contrib/compression-webpack-plugin/issues/144)) ([bf22024](https://github.com/webpack-contrib/compression-webpack-plugin/commit/bf220243bdf74595dfe97b055a8424f7365fc866)) 264 | 265 | 266 | ### Bug Fixes 267 | 268 | * support webpack@5 ([#146](https://github.com/webpack-contrib/compression-webpack-plugin/issues/146)) ([17c29a5](https://github.com/webpack-contrib/compression-webpack-plugin/commit/17c29a557743a82d0a4ac10a98c8dc90a1aef64b)) 269 | 270 | ## [3.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v2.0.0...v3.0.0) (2019-06-05) 271 | 272 | 273 | ### chore 274 | 275 | * **deps:** update ([#124](https://github.com/webpack-contrib/compression-webpack-plugin/issues/124)) ([897bd95](https://github.com/webpack-contrib/compression-webpack-plugin/commit/897bd95)) 276 | 277 | 278 | ### Tests 279 | 280 | * validation ([#111](https://github.com/webpack-contrib/compression-webpack-plugin/issues/111)) ([833e825](https://github.com/webpack-contrib/compression-webpack-plugin/commit/833e825)) 281 | 282 | 283 | ### BREAKING CHANGES 284 | 285 | * **deps:** minimum required nodejs version is `8.9.0` 286 | 287 | 288 | 289 | 290 | ## [2.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.12...v2.0.0) (2018-09-04) 291 | 292 | ### Features 293 | 294 | * `filename` option now accept `{String}` value 295 | * validation schema for plugin options 296 | 297 | ### BREAKING CHANGES 298 | 299 | * enforces `peerDependencies` of `"webpack": "^4.3.0"` 300 | * enforces `engines` of `"node": ">= 6.9.0 <7.0.0 || >= 8.9.0` 301 | * compressed options (`options.level`, `options.flush`, `options.dictionary` and etc) grouped into `compressionOptions` option 302 | * `asset` option was removed (use `filename` option instead) 303 | * default value of `filename` option is now `[path].gz[query]` 304 | 305 | 306 | 307 | 308 | ## [1.1.12](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.11...v1.1.12) (2018-08-29) 309 | 310 | ### Bug Fixes 311 | 312 | * correct handling filename with `query` ([#105](https://github.com/webpack-contrib/compression-webpack-plugin/issues/105)) ([c8d7757](https://github.com/webpack-contrib/compression-webpack-plugin/commit/c8d7757)) 313 | 314 | 315 | 316 | 317 | ## [1.1.11](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.10...v1.1.11) (2018-03-09) 318 | 319 | 320 | ### Performance Improvements 321 | 322 | * **index:** switch to `md4` for content hashing ([#103](https://github.com/webpack-contrib/compression-webpack-plugin/issues/103)) ([0eebc46](https://github.com/webpack-contrib/compression-webpack-plugin/commit/0eebc46)) 323 | 324 | 325 | 326 | 327 | ## [1.1.10](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.9...v1.1.10) (2018-02-27) 328 | 329 | 330 | ### Performance Improvements 331 | 332 | * use `neo-async` instead `async` ([#98](https://github.com/webpack-contrib/compression-webpack-plugin/issues/98)) ([1cd3095](https://github.com/webpack-contrib/compression-webpack-plugin/commit/1cd3095)) 333 | 334 | 335 | 336 | 337 | ## [1.1.9](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.8...v1.1.9) (2018-02-26) 338 | 339 | 340 | ### Bug Fixes 341 | 342 | * **package:** add `webpack >= 4` (`peerDependencies`) ([#101](https://github.com/webpack-contrib/compression-webpack-plugin/issues/101)) ([d3c29e7](https://github.com/webpack-contrib/compression-webpack-plugin/commit/d3c29e7)) 343 | 344 | 345 | 346 | 347 | ## [1.1.8](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.7...v1.1.8) (2018-02-23) 348 | 349 | 350 | ### Bug Fixes 351 | 352 | * **index:** `tapable` deprecation warnings (`webpack >= v4.0.0`) ([#100](https://github.com/webpack-contrib/compression-webpack-plugin/issues/100)) ([d6ccdc4](https://github.com/webpack-contrib/compression-webpack-plugin/commit/d6ccdc4)) 353 | 354 | 355 | 356 | 357 | ## [1.1.7](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.6...v1.1.7) (2018-02-16) 358 | 359 | 360 | ### Bug Fixes 361 | 362 | * **index:** reduce memory usage (`cacheKey.hash`) ([#97](https://github.com/webpack-contrib/compression-webpack-plugin/issues/97)) ([3c77d43](https://github.com/webpack-contrib/compression-webpack-plugin/commit/3c77d43)) 363 | 364 | 365 | 366 | 367 | ## [1.1.6](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.5...v1.1.6) (2018-01-29) 368 | 369 | 370 | 371 | 372 | ## [1.1.5](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.4...v1.1.5) (2018-01-29) 373 | 374 | 375 | ### Bug Fixes 376 | 377 | * **package:** use `prepare` instead of `prepublish` for release ([0b90a71](https://github.com/webpack-contrib/compression-webpack-plugin/commit/0b90a71)) 378 | 379 | 380 | 381 | 382 | ## [1.1.4](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.3...v1.1.4) (2018-01-29) 383 | 384 | 385 | ### Bug Fixes 386 | 387 | * missing options `include` and `exclude` ([#95](https://github.com/webpack-contrib/compression-webpack-plugin/issues/95)) ([cc44bcb](https://github.com/webpack-contrib/compression-webpack-plugin/commit/cc44bcb)) 388 | 389 | 390 | 391 | 392 | ## [1.1.3](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.2...v1.1.3) (2017-12-22) 393 | 394 | 395 | ### Bug Fixes 396 | 397 | * `cache` behaviour ([#91](https://github.com/webpack-contrib/compression-webpack-plugin/issues/91)) ([9791044](https://github.com/webpack-contrib/compression-webpack-plugin/commit/9791044)) 398 | 399 | 400 | 401 | 402 | ## [1.1.2](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.1...v1.1.2) (2017-12-14) 403 | 404 | 405 | ### Bug Fixes 406 | 407 | * `text/include/exclude` option behaviour ([#88](https://github.com/webpack-contrib/compression-webpack-plugin/issues/88)) ([1d0a840](https://github.com/webpack-contrib/compression-webpack-plugin/commit/1d0a840)) 408 | 409 | 410 | 411 | 412 | ## [1.1.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.1.0...v1.1.1) (2017-12-14) 413 | 414 | 415 | ### Bug Fixes 416 | 417 | * **index:** don't use `JSON.stringify()` to serialize the `cache` data (`options.cache`) ([#87](https://github.com/webpack-contrib/compression-webpack-plugin/issues/87)) ([0d22741](https://github.com/webpack-contrib/compression-webpack-plugin/commit/0d22741)) 418 | 419 | 420 | 421 | 422 | # [1.1.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.0.1...v1.1.0) (2017-12-14) 423 | 424 | 425 | ### Features 426 | 427 | * add `cache` option (`options.cache`) ([#86](https://github.com/webpack-contrib/compression-webpack-plugin/issues/86)) ([49a8a77](https://github.com/webpack-contrib/compression-webpack-plugin/commit/49a8a77)) 428 | * add `include` and `exclude` options (`options.include|options.exclude`) ([#82](https://github.com/webpack-contrib/compression-webpack-plugin/issues/82)) ([1ce3024](https://github.com/webpack-contrib/compression-webpack-plugin/commit/1ce3024)) 429 | 430 | 431 | 432 | 433 | ## [1.0.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.0.0...v1.0.1) (2017-09-29) 434 | 435 | 436 | ### Code Refactoring 437 | 438 | * Use emit event instead of this-compilation ([#71](https://github.com/webpack-contrib/compression-webpack-plugin/pull/71)) ([9ebc852](https://github.com/webpack-contrib/compression-webpack-plugin/commit/9ebc852)) 439 | 440 | 441 | 442 | # [1.0.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.0.0-beta.1...v1.0.0) (2017-07-15) 443 | 444 | ### Code Refactoring 445 | 446 | * apply webpack-defaults ([#54](https://github.com/webpack-contrib/compression-webpack-plugin/issues/54)) ([f6f8c6c](https://github.com/webpack-contrib/compression-webpack-plugin/commit/f6f8c6c)) 447 | 448 | 449 | ### BREAKING CHANGES 450 | 451 | * Enforces `peerDependencies` of `"webpack": ">= 3.0.0-rc.0 || ^3.0.0"`. 452 | * Enforces `engines` of `"node": ">=4.3.0 < 5.0.0 || >= 5.10` 453 | * Remove loose dependency on Node Zopfli, which has been extracted to it's own plugin https://github.com/webpack-contrib/zopfli-webpack-plugin 454 | 455 | Migration: 456 | 457 | - `npm i -D zopfli-webpack-plugin` 458 | - The Zopfli API has remained the same, those who were using the Zopfli option in this plugin should just need to switch plugins. 459 | 460 | 461 | 462 | # [1.0.0-beta.1](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v1.0.0-beta.0...v1.0.0-beta.1) (2017-07-03) 463 | 464 | 465 | ### Code Refactoring 466 | 467 | * Drops optional Zopfli dependency ([#65](https://github.com/webpack-contrib/compression-webpack-plugin/issues/65)) ([328048a](https://github.com/webpack-contrib/compression-webpack-plugin/commit/328048a)) 468 | 469 | 470 | ### BREAKING CHANGES 471 | 472 | * The optional dependency for Zopfli was causing issues in consumers CI / CD chains, this option has now been removed. 473 | 474 | MIGRATION: Zopfli is now in it's own plugin the options have remained the same. For those using the Zopfli option in `compression-webpack-plugin` swap it out for `https://github.com/webpack-contrib/zopfli-webpack-plugin` 475 | 476 | 477 | 478 | 479 | # [1.0.0-beta.0](https://github.com/webpack-contrib/compression-webpack-plugin/compare/v0.4.0...v1.0.0-beta.0) (2017-06-24) 480 | 481 | 482 | ### Code Refactoring 483 | 484 | * apply webpack-defaults ([#54](https://github.com/webpack-contrib/compression-webpack-plugin/issues/54)) ([f6f8c6c](https://github.com/webpack-contrib/compression-webpack-plugin/commit/f6f8c6c)) 485 | 486 | 487 | ### BREAKING CHANGES 488 | 489 | * Enforces `peerDependencies` of `"webpack": ">= 3.0.0-rc.0 || ^3.0.0"`. 490 | * Enforces `engines` of `"node": ">=4.3.0 < 5.0.0 || >= 5.10` 491 | * Remove loose dependency on Node Zopfli, which has been extracted to it's own plugin https://github.com/webpack-contrib/zopfli-webpack-plugin 492 | 493 | Migration: 494 | 495 | - `npm i -D zopfli-webpack-plugin` 496 | - The Zopfli API has remained the same, those who were using the Zopfli option in this plugin should just need to switch plugins. 497 | 498 | 499 | 500 | 501 | # [0.4.0](https://github.com/webpack/compression-webpack-plugin/compare/v0.3.2...v0.4.0) (2017-04-08) 502 | 503 | 504 | ### Features 505 | 506 | * add option to change the filename ([#51](https://github.com/webpack/compression-webpack-plugin/issues/51)) ([fb7bd81](https://github.com/webpack/compression-webpack-plugin/commit/fb7bd81)) 507 | * add option to delete original assets ([#44](https://github.com/webpack/compression-webpack-plugin/issues/44)) ([24f15f2](https://github.com/webpack/compression-webpack-plugin/commit/24f15f2)) 508 | 509 | 510 | 511 | 512 | ## 0.3.2 (2016-09-13) 513 | 514 | 515 | ### Chores 516 | 517 | * Update node-zopfli version ([2d3dd44](https://github.com/webpack-contrib/compression-webpack-plugin/commit/2d3dd44)) 518 | 519 | 520 | 521 | ## 0.3.1 (2016-03-26) 522 | 523 | 524 | ### Bug Fixes 525 | 526 | * TypeError Invalid non-strig/buffer chunk ([53ec8a9](https://github.com/webpack/compression-webpack-plugin/commit/53ec8a9)) 527 | 528 | 529 | ## 0.3.0 (2016-01-23) 530 | 531 | 532 | ### Bug Fixes 533 | 534 | * Correct zopfli options ([1f3b595](https://github.com/webpack/compression-webpack-plugin/commit/1f3b595)) 535 | * plugin options syntax ([437bdff](https://github.com/webpack/compression-webpack-plugin/commit/437bdff)) 536 | 537 | ### Features 538 | 539 | * Add compression level option ([9d05172](https://github.com/webpack/compression-webpack-plugin/commit/9d05172)) 540 | * Add node-zopfli option ([2c22b1c](https://github.com/webpack/compression-webpack-plugin/commit/2c22b1c)) 541 | * Permit {path} and {query} in asset name ([12d167c](https://github.com/webpack/compression-webpack-plugin/commit/12d167c)) 542 | 543 | 544 | ## 0.2.0 (2015-04-08) 545 | 546 | 547 | ### Features 548 | 549 | * use webpack RawSource ([3c85a2b](https://github.com/webpack/compression-webpack-plugin/commit/3c85a2b)) 550 | 551 | 552 | 553 | ## 0.1.2 (2015-04-08) 554 | 555 | 556 | ### Bug Fixes 557 | 558 | * Double compression on worker-loader bundles ([7ce2b32](https://github.com/webpack/compression-webpack-plugin/commit/7ce2b32)) 559 | * Remove unneeded module.exports ([6f4e60d](https://github.com/webpack/compression-webpack-plugin/commit/6f4e60d)) 560 | --------------------------------------------------------------------------------