├── 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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------