├── .husky
├── pre-commit
└── commit-msg
├── test
├── fixtures
│ ├── loader-test.unknown
│ ├── loader-test.txt
│ ├── empty-entry.js
│ ├── entry-with-css.js
│ ├── simple-emit.js
│ ├── simple.js
│ ├── child-compilation.js
│ ├── loader-test.css
│ ├── loader-test.json
│ ├── generator.js
│ ├── nested
│ │ ├── deep
│ │ │ ├── loader-test.json
│ │ │ ├── loader-test.svg
│ │ │ ├── loader-test.gif
│ │ │ ├── loader-test.jpg
│ │ │ ├── loader-test.png
│ │ │ ├── plugin-test.png
│ │ │ └── loader.js
│ │ └── multiple-loader-test-4.svg
│ ├── plugin-test.svg
│ ├── single-image-loader.js
│ ├── generator-and-minimizer-4.js
│ ├── generator-and-minimizer-5.js
│ ├── generator-and-minimizer-7.js
│ ├── loader-single.js
│ ├── generator-and-minimizer-3.js
│ ├── svgo-prefix-id.js
│ ├── loader-corrupted.js
│ ├── generator-asset-modules.js
│ ├── minimizer-only-corrupted.js
│ ├── multiple-entry.js
│ ├── url.png
│ ├── multiple-entry-2.js
│ ├── newImg.png
│ ├── generator-and-minimizer-animation.js
│ ├── loader-test.gif
│ ├── loader-test.jpg
│ ├── loader-test.png
│ ├── plugin-test.gif
│ ├── plugin-test.jpg
│ ├── plugin-test.png
│ ├── animation-test.gif
│ ├── test-corrupted.jpg
│ ├── asset-inline.js
│ ├── child-compilation-image.png
│ ├── svg-and-jpg.js
│ ├── unknown-and-jpg.js
│ ├── loader-test.svg
│ ├── loader.js
│ ├── test-corrupted.svg
│ ├── minimizer-only.js
│ ├── validate-options.js
│ ├── large-after-optimization.svg
│ ├── loader-with-child.js
│ ├── svgo-id.svg
│ ├── generator-and-minimizer.js
│ ├── loader-other-imports.js
│ ├── loader-other-imports-1.js
│ ├── cache
│ │ └── absolute-url
│ │ │ ├── https_upload.wikimedia.org
│ │ │ ├── wikipedia_commons_7_70_Example_as_webp_186c80a4861cd02a331c.png
│ │ │ └── wikipedia_commons_4_47_PNG_transparency_demonstration_1_foo_bar_as_webp_1a8ac3df16c88ee1d867.png
│ │ │ ├── https_cdn.jsdelivr.net
│ │ │ └── gh_webpack_media_e7485eb2_logo_icon_bddc4456f7d21daf0df8.svg
│ │ │ └── lock.json
│ ├── multiple-loader-test-2.svg
│ ├── multiple-loader-test-4.svg
│ ├── multiple-plugin-test-2.svg
│ ├── multiple-plugin-test-4.svg
│ ├── emitAssetLoader.js
│ ├── style.css
│ ├── generator-and-minimizer-resize-query.js
│ ├── generator-and-minimizer-percent-resize-query.js
│ ├── svg-with-id.svg
│ ├── multiple-loader-test-1.svg
│ ├── multiple-loader-test-3.svg
│ ├── multiple-plugin-test-1.svg
│ ├── multiple-plugin-test-3.svg
│ ├── EmitWepbackPlugin.js
│ └── emit-asset-in-child-compilation-loader.js
├── .eslintrc.json
├── imagemin-base64.js
├── plugin-loader-option.test.js
├── __snapshots__
│ ├── ImageminPlugin.test.js.snap
│ ├── api.test.js.snap
│ ├── validate-plugin-options.test.js.snap
│ └── validate-loader-options.test.js.snap
├── api.test.js
├── utils.test.js
├── loader-severityError-option.test.js
├── validate-loader-options.test.js
├── plugin-deleteOriginalAssets-option.test.js
├── plugin-severityError-option.test.js
├── helpers.js
├── loader-minimizer-option.test.js
└── loader-generator-option.test.js
├── src
├── squoosh-lib.d.ts
├── loader-options.json
├── worker.js
├── plugin-options.json
└── loader.js
├── .gitattributes
├── .remarkrc.js
├── .prettierignore
├── jest.config.js
├── lint-staged.config.js
├── commitlint.config.js
├── .editorconfig
├── .gitignore
├── .github
└── workflows
│ ├── dependency-review.yml
│ └── nodejs.yml
├── babel.config.js
├── eslint.config.mjs
├── tsconfig.json
├── types
├── worker.d.ts
├── loader.d.ts
├── utils.d.ts
└── index.d.ts
├── LICENSE
├── .cspell.json
├── package.json
└── CHANGELOG.md
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | lint-staged
2 |
--------------------------------------------------------------------------------
/test/fixtures/loader-test.unknown:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/loader-test.txt:
--------------------------------------------------------------------------------
1 | TEXT
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | commitlint --edit $1
2 |
--------------------------------------------------------------------------------
/test/fixtures/empty-entry.js:
--------------------------------------------------------------------------------
1 | export default 1;
2 |
--------------------------------------------------------------------------------
/src/squoosh-lib.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@squoosh/lib";
2 |
--------------------------------------------------------------------------------
/test/fixtures/entry-with-css.js:
--------------------------------------------------------------------------------
1 | import "./style.css";
2 |
--------------------------------------------------------------------------------
/test/fixtures/simple-emit.js:
--------------------------------------------------------------------------------
1 | export default 'foo';
2 |
--------------------------------------------------------------------------------
/test/fixtures/simple.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.jpg");
2 |
--------------------------------------------------------------------------------
/test/fixtures/child-compilation.js:
--------------------------------------------------------------------------------
1 | export default "foobar";
2 |
--------------------------------------------------------------------------------
/test/fixtures/loader-test.css:
--------------------------------------------------------------------------------
1 | a {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/loader-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "foo": "bar"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/generator.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.png?as=webp");
2 |
--------------------------------------------------------------------------------
/test/fixtures/nested/deep/loader-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "foo": "bar"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/plugin-test.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/fixtures/single-image-loader.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.jpg?as=webp");
2 |
--------------------------------------------------------------------------------
/test/fixtures/nested/deep/loader-test.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-4.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.png?as=avif");
2 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-5.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.txt?as=avif");
2 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-7.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.svg?as=webp");
2 |
--------------------------------------------------------------------------------
/test/fixtures/loader-single.js:
--------------------------------------------------------------------------------
1 | require("./nested/deep/loader-test.jpg?as=webp");
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | bin/* eol=lf
3 | yarn.lock -diff
4 | package-lock.json -diff
--------------------------------------------------------------------------------
/.remarkrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["remark-preset-lint-itgalaxy"],
3 | };
4 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-3.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.png?as=webp-other");
2 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "sourceType": "module"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/svgo-prefix-id.js:
--------------------------------------------------------------------------------
1 | console.log(new URL("../fixtures/svgo-id.svg", import.meta.url));
2 |
--------------------------------------------------------------------------------
/test/fixtures/loader-corrupted.js:
--------------------------------------------------------------------------------
1 | require("./test-corrupted.jpg");
2 | require("./loader-test.png");
3 |
--------------------------------------------------------------------------------
/test/fixtures/generator-asset-modules.js:
--------------------------------------------------------------------------------
1 | console.log(new URL("../fixtures/loader-test.png?as=webp", import.meta.url));
--------------------------------------------------------------------------------
/test/fixtures/minimizer-only-corrupted.js:
--------------------------------------------------------------------------------
1 | require("./test-corrupted.jpg");
2 | require("./test-corrupted.svg");
3 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-entry.js:
--------------------------------------------------------------------------------
1 | import "./multiple-loader-test-1.svg";
2 | import "./multiple-loader-test-2.svg";
3 |
--------------------------------------------------------------------------------
/test/fixtures/url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/url.png
--------------------------------------------------------------------------------
/test/fixtures/multiple-entry-2.js:
--------------------------------------------------------------------------------
1 | import "./multiple-loader-test-3.svg";
2 | import "./multiple-loader-test-4.svg";
3 |
--------------------------------------------------------------------------------
/test/fixtures/newImg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/newImg.png
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /test/fixtures
5 | /test/outputs
6 | /test/bundled
7 | CHANGELOG.md
8 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: "node",
3 | collectCoverageFrom: ["src/**/*.{js,mjs,jsx}"],
4 | };
5 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-animation.js:
--------------------------------------------------------------------------------
1 | require("./animation-test.gif");
2 | require("./animation-test.gif?as=webp");
3 |
--------------------------------------------------------------------------------
/test/fixtures/loader-test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/loader-test.gif
--------------------------------------------------------------------------------
/test/fixtures/loader-test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/loader-test.jpg
--------------------------------------------------------------------------------
/test/fixtures/loader-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/loader-test.png
--------------------------------------------------------------------------------
/test/fixtures/plugin-test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/plugin-test.gif
--------------------------------------------------------------------------------
/test/fixtures/plugin-test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/plugin-test.jpg
--------------------------------------------------------------------------------
/test/fixtures/plugin-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/plugin-test.png
--------------------------------------------------------------------------------
/test/fixtures/animation-test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/animation-test.gif
--------------------------------------------------------------------------------
/test/fixtures/test-corrupted.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/test-corrupted.jpg
--------------------------------------------------------------------------------
/test/fixtures/asset-inline.js:
--------------------------------------------------------------------------------
1 | import imageSvg from "./loader-test.svg";
2 |
3 | console.log(imageSvg)
4 |
5 | export default imageSvg;
6 |
--------------------------------------------------------------------------------
/test/fixtures/child-compilation-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/child-compilation-image.png
--------------------------------------------------------------------------------
/test/fixtures/nested/deep/loader-test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/nested/deep/loader-test.gif
--------------------------------------------------------------------------------
/test/fixtures/nested/deep/loader-test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/nested/deep/loader-test.jpg
--------------------------------------------------------------------------------
/test/fixtures/nested/deep/loader-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/nested/deep/loader-test.png
--------------------------------------------------------------------------------
/test/fixtures/nested/deep/plugin-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/nested/deep/plugin-test.png
--------------------------------------------------------------------------------
/test/fixtures/svg-and-jpg.js:
--------------------------------------------------------------------------------
1 | console.log(new URL("../fixtures/loader-test.svg", import.meta.url));
2 | console.log(new URL("../fixtures/loader-test.jpg", import.meta.url));
3 |
--------------------------------------------------------------------------------
/test/fixtures/unknown-and-jpg.js:
--------------------------------------------------------------------------------
1 | console.log(new URL("../fixtures/loader-test.unknown", import.meta.url));
2 | console.log(new URL("../fixtures/loader-test.jpg", import.meta.url));
3 |
--------------------------------------------------------------------------------
/test/fixtures/loader-test.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/loader.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.gif");
2 | require("./loader-test.jpg");
3 | require("./loader-test.png");
4 | require("./loader-test.svg");
5 | require("./loader-test.json");
6 |
--------------------------------------------------------------------------------
/test/fixtures/test-corrupted.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/test/fixtures/loader-with-child.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.gif");
2 | require("./loader-test.jpg");
3 | require("./loader-test.png");
4 | require("./loader-test.svg");
5 | require("./loader-test.json");
6 | require("./child-compilation");
7 |
--------------------------------------------------------------------------------
/test/fixtures/svgo-id.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.gif");
2 | require("./loader-test.jpg");
3 | require("./loader-test.png");
4 | require("./loader-test.svg");
5 | require("./loader-test.json");
6 | require("./loader-test.png?as=webp");
7 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/test/imagemin-base64.js:
--------------------------------------------------------------------------------
1 | module.exports = () => (buffer) => {
2 | if (!Buffer.isBuffer(buffer)) {
3 | return Promise.reject(new TypeError("Expected a buffer"));
4 | }
5 |
6 | return Promise.resolve(Buffer.from(buffer.toString("base64")));
7 | };
8 |
--------------------------------------------------------------------------------
/test/fixtures/loader-other-imports.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.gif");
2 | require("./loader-test.jpg");
3 | require("./loader-test.png");
4 | require("./loader-test.svg");
5 | require("./loader-test.css");
6 | require("./loader-test.txt");
7 | require("./loader-test.json");
8 |
--------------------------------------------------------------------------------
/test/fixtures/loader-other-imports-1.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.gif");
2 | require("./loader-test.jpg");
3 | require("./loader-test.png");
4 | require("./loader-test.svg");
5 | require("./loader-test.css");
6 | require("./loader-test.txt");
7 | require("./loader-test.json");
8 | require("./loader-test.jpg?as=webp");
--------------------------------------------------------------------------------
/test/fixtures/cache/absolute-url/https_upload.wikimedia.org/wikipedia_commons_7_70_Example_as_webp_186c80a4861cd02a331c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/cache/absolute-url/https_upload.wikimedia.org/wikipedia_commons_7_70_Example_as_webp_186c80a4861cd02a331c.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .vscode
4 | logs
5 | *.log
6 | npm-debug.log*
7 | .cspellcache
8 | .eslintcache
9 | /coverage
10 | /dist
11 | /local
12 | /reports
13 | /node_modules
14 | /test/outputs
15 | /test/bundled
16 | .DS_Store
17 | Thumbs.db
18 | .idea
19 | *.iml
20 | *.sublime-project
21 | *.sublime-workspace
22 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-loader-test-2.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-loader-test-4.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-plugin-test-2.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-plugin-test-4.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/fixtures/nested/multiple-loader-test-4.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/fixtures/cache/absolute-url/https_upload.wikimedia.org/wikipedia_commons_4_47_PNG_transparency_demonstration_1_foo_bar_as_webp_1a8ac3df16c88ee1d867.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack/image-minimizer-webpack-plugin/HEAD/test/fixtures/cache/absolute-url/https_upload.wikimedia.org/wikipedia_commons_4_47_PNG_transparency_demonstration_1_foo_bar_as_webp_1a8ac3df16c88ee1d867.png
--------------------------------------------------------------------------------
/test/fixtures/emitAssetLoader.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 |
4 | export default function loader(content) {
5 | const filename = 'loader-test.jpg';
6 | const fileContent = fs.readFileSync(path.resolve(filename));
7 | this.emitFile(filename, fileContent);
8 |
9 | const callback = this.async();
10 |
11 | return callback(null, content);
12 | }
13 |
--------------------------------------------------------------------------------
/.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@v4
13 | - name: "Dependency Review"
14 | uses: actions/dependency-review-action@v4
15 |
--------------------------------------------------------------------------------
/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 | exclude: ["proposal-dynamic-import"],
13 | targets: {
14 | node: "18.12.0",
15 | },
16 | },
17 | ],
18 | ],
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "eslint/config";
2 | import configs from "eslint-config-webpack/configs.js";
3 | import jest from "eslint-plugin-jest";
4 |
5 | export default defineConfig([
6 | {
7 | extends: [configs["recommended-dirty"]],
8 | plugins: {
9 | jest,
10 | },
11 | rules: {
12 | "jest/no-standalone-expect": [
13 | "error",
14 | { additionalTestBlockFunctions: ["ifit"] },
15 | ],
16 | },
17 | },
18 | ]);
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "commonjs",
5 | "lib": ["es2023"],
6 | "allowJs": true,
7 | "checkJs": true,
8 | "strict": true,
9 | "types": ["node"],
10 | "resolveJsonModule": true,
11 |
12 | // `skipLibCheck` only for avoid bug of `svgo` v3.0.0
13 | // see: https://github.com/svg/svgo/issues/1700
14 | "skipLibCheck": true,
15 |
16 | "outDir": "./types"
17 | },
18 | "include": ["./src/**/*"]
19 | }
20 |
--------------------------------------------------------------------------------
/test/fixtures/style.css:
--------------------------------------------------------------------------------
1 | a {
2 | background: url("./url.png");
3 | background: url("./url.png?as=webp");
4 | }
5 |
6 | body {
7 | content: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27#000%27%2F%3E%3C%2Fsvg%3E');
8 | }
9 |
10 | body {
11 | content: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyMCAyMCc+PHBhdGggZD0nTTE0LjgzIDQuODlsMS4zNC45NC01LjgxIDguMzhIOS4wMkw1Ljc4IDkuNjdsMS4zNC0xLjI1IDIuNTcgMi40eicgZmlsbD0nIzAwMCcvPjwvc3ZnPg==');
12 | }
13 |
--------------------------------------------------------------------------------
/types/worker.d.ts:
--------------------------------------------------------------------------------
1 | export = worker;
2 | /**
3 | * @template T
4 | * @param {import("./index").InternalWorkerOptions} options worker options
5 | * @returns {Promise} worker result
6 | */
7 | declare function worker(
8 | options: import("./index").InternalWorkerOptions,
9 | ): Promise;
10 | declare namespace worker {
11 | export { isFilenameProcessed, WorkerResult, FilenameFn };
12 | }
13 | /** @typedef {import("./index").WorkerResult} WorkerResult */
14 | /** @typedef {import("./index").FilenameFn} FilenameFn */
15 | /** @type {unique symbol} */
16 | declare const isFilenameProcessed: unique symbol;
17 | type WorkerResult = import("./index").WorkerResult;
18 | type FilenameFn = import("./index").FilenameFn;
19 |
--------------------------------------------------------------------------------
/test/fixtures/cache/absolute-url/https_cdn.jsdelivr.net/gh_webpack_media_e7485eb2_logo_icon_bddc4456f7d21daf0df8.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-resize-query.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.png?width=100");
2 | require("./loader-test.png?w=150");
3 | require("./loader-test.png?height=200");
4 | require("./loader-test.png?h=250");
5 | require("./loader-test.png?width=300&height=auto");
6 | require("./loader-test.png?width=auto&height=320");
7 | require("./loader-test.png?width=350&height=350");
8 |
9 | require("./loader-test.png?as=webp&width=100");
10 | require("./loader-test.png?as=webp&w=150");
11 | require("./loader-test.png?as=webp&height=200");
12 | require("./loader-test.png?as=webp&h=250");
13 | require("./loader-test.png?as=webp&width=300&height=auto");
14 | require("./loader-test.png?as=webp&width=auto&height=320");
15 | require("./loader-test.png?as=webp&width=350&height=350");
16 |
--------------------------------------------------------------------------------
/test/fixtures/generator-and-minimizer-percent-resize-query.js:
--------------------------------------------------------------------------------
1 | require("./loader-test.png?width=100&unit=percent");
2 | require("./loader-test.png?w=150&u=percent");
3 | require("./loader-test.png?height=200&unit=percent");
4 | require("./loader-test.png?h=250&u=percent");
5 | require("./loader-test.png?width=300&height=auto&unit=percent");
6 | require("./loader-test.png?width=auto&height=320&unit=percent");
7 | require("./loader-test.png?width=350&height=350&unit=percent");
8 |
9 | require("./loader-test.png?as=webp&width=100&unit=percent");
10 | require("./loader-test.png?as=webp&w=150&u=percent");
11 | require("./loader-test.png?as=webp&height=200&unit=percent");
12 | require("./loader-test.png?as=webp&h=250&u=percent");
13 | require("./loader-test.png?as=webp&width=300&height=auto&unit=percent");
14 | require("./loader-test.png?as=webp&width=auto&height=320&unit=percent");
15 | require("./loader-test.png?as=webp&width=350&height=350&unit=percent");
16 |
--------------------------------------------------------------------------------
/test/fixtures/svg-with-id.svg:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-loader-test-1.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-loader-test-3.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-plugin-test-1.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-plugin-test-3.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2",
3 | "language": "en,en-gb",
4 | "words": [
5 | "Squoosh",
6 | "squoosh",
7 | "ifit",
8 | "Mozjpeg",
9 | "mozjpeg",
10 | "pify",
11 | "oxipng",
12 | "pngquant",
13 | "gifsicle",
14 | "minifi",
15 | "webpz",
16 | "multipass",
17 | "MCEP",
18 | "tempy",
19 | "multipass",
20 | "zdmc",
21 | "fullhash",
22 | "emmited",
23 | "Wepback",
24 | "pathinfo",
25 | "datauri",
26 | "preproc",
27 | "heix",
28 | "hevx",
29 | "ftyp",
30 | "flif",
31 | "FUJIFILMCCD",
32 | "apng",
33 | "IHDR",
34 | "IDAT",
35 | "webp",
36 | "jpegtran",
37 | "minimication",
38 | "extname",
39 | "srcset",
40 | "webp",
41 | "optipng",
42 | "jpegtran",
43 | "libvips",
44 | "hspace",
45 | "commitlint",
46 | "nodenext"
47 | ],
48 |
49 | "ignorePaths": [
50 | "CHANGELOG.md",
51 | "package.json",
52 | "dist/**",
53 | "**/__snapshots__/**",
54 | "package-lock.json",
55 | "types",
56 | "test/bundled",
57 | "/test/outputs",
58 | "*.webp",
59 | "*.css",
60 | "*.svg",
61 | "node_modules",
62 | "coverage",
63 | "*.log"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/test/fixtures/EmitWepbackPlugin.js:
--------------------------------------------------------------------------------
1 | import fs from "fs/promises";
2 | import path from "path";
3 |
4 | export default class EmitWepbackPlugin {
5 | constructor(options = {}) {
6 | this.options = Object.assign(
7 | {},
8 | {
9 | fileNames: ["plugin-test.jpg"],
10 | },
11 | options
12 | );
13 | }
14 |
15 | apply(compiler) {
16 | const plugin = { name: "EmitPlugin" };
17 | const mapCache = new Map();
18 |
19 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
20 | compilation.hooks.additionalAssets.tapPromise(plugin, () => {
21 | const { fileNames } = this.options;
22 | const { RawSource } = compiler.webpack.sources;
23 |
24 | return Promise.all(
25 | fileNames.map(async (fileName) => {
26 | const filePath = path.join(__dirname, fileName);
27 | const data = await fs.readFile(filePath);
28 |
29 | let source = mapCache.get(fileName);
30 |
31 | if (!source) {
32 | source = new RawSource(data);
33 | mapCache.set(fileName, source);
34 | }
35 |
36 | compilation.emitAsset(fileName, source);
37 | })
38 | );
39 | });
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/plugin-loader-option.test.js:
--------------------------------------------------------------------------------
1 | import ImageMinimizerPlugin from "../src/index.js";
2 | import { isOptimized, plugins, runWebpack } from "./helpers";
3 |
4 | describe("plugin loader option", () => {
5 | it("should optimizes all images (plugin standalone)", async () => {
6 | const stats = await runWebpack({
7 | emitPlugin: true,
8 | imageminPluginOptions: {
9 | minimizer: {
10 | implementation: ImageMinimizerPlugin.imageminMinify,
11 | options: { plugins },
12 | },
13 | loader: false,
14 | },
15 | });
16 | const { compilation } = stats;
17 | const { warnings, errors } = compilation;
18 |
19 | expect(warnings).toHaveLength(0);
20 | expect(errors).toHaveLength(0);
21 |
22 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
23 | true,
24 | );
25 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
26 | true,
27 | );
28 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
29 | true,
30 | );
31 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
32 | true,
33 | );
34 | await expect(isOptimized("plugin-test.jpg", compilation)).resolves.toBe(
35 | true,
36 | );
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/fixtures/cache/absolute-url/lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "https://cdn.rawgit.com/webpack/media/e7485eb2/logo/icon.svg": { "resolved": "https://cdn.jsdelivr.net/gh/webpack/media@e7485eb2/logo/icon.svg", "integrity": "sha512-DyC5VS91HRdJSZ1G8BOJCzXEZfQCmBAu0W2v8tcJNq3bvmXrL8vdFqDpFNrI/SL7qLBSV7DiiR0rLZcb1FGebA==", "contentType": "image/svg+xml" },
3 | "https://cdn.rawgit.com/webpack/media/e7485eb2/logo/icon.svg?as=webp": { "resolved": "https://cdn.jsdelivr.net/gh/webpack/media@e7485eb2/logo/icon.svg", "integrity": "sha512-DyC5VS91HRdJSZ1G8BOJCzXEZfQCmBAu0W2v8tcJNq3bvmXrL8vdFqDpFNrI/SL7qLBSV7DiiR0rLZcb1FGebA==", "contentType": "image/svg+xml" },
4 | "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?foo=bar&as=webp": { "integrity": "sha512-oko0UnN7/aOH4loqI1F79zMZozo+Y5fbTYEvcfpE75439sR6INOke7En72cUAvbhcuKmmYUgZMxcGew9iBRTzQ==", "contentType": "image/png" },
5 | "https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png?as=webp": { "integrity": "sha512-5rIlhMQXmv1/+wxg+Xl12smI+bkT84f70stvfyIdKth6Nh75eM+Cyfl3mIUoiFwIaQeIXCGgqUPOceyBgZmvmg==", "contentType": "image/png" },
6 | "https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png?foo=bar&as=webp": { "integrity": "sha512-5rIlhMQXmv1/+wxg+Xl12smI+bkT84f70stvfyIdKth6Nh75eM+Cyfl3mIUoiFwIaQeIXCGgqUPOceyBgZmvmg==", "contentType": "image/png" },
7 | "version": 1
8 | }
9 |
--------------------------------------------------------------------------------
/types/loader.d.ts:
--------------------------------------------------------------------------------
1 | export = loader;
2 | /**
3 | * @template T
4 | * @this {import("webpack").LoaderContext>}
5 | * @param {Buffer} content content
6 | * @returns {Promise} processed content
7 | */
8 | declare function loader(
9 | this: import("webpack").LoaderContext>,
10 | content: Buffer,
11 | ): Promise;
12 | declare namespace loader {
13 | export {
14 | raw,
15 | Schema,
16 | Compilation,
17 | WorkerResult,
18 | Minimizer,
19 | Generator,
20 | LoaderOptions,
21 | };
22 | }
23 | declare var raw: boolean;
24 | type Schema = import("schema-utils/declarations/validate").Schema;
25 | type Compilation = import("webpack").Compilation;
26 | type WorkerResult = import("./utils").WorkerResult;
27 | /**
28 | *
29 | */
30 | type Minimizer = import("./index").Minimizer;
31 | /**
32 | *
33 | */
34 | type Generator = import("./index").Generator;
35 | /**
36 | *
37 | */
38 | type LoaderOptions = {
39 | /**
40 | * allows to choose how errors are displayed.
41 | */
42 | severityError?: string | undefined;
43 | /**
44 | * minimizer configuration
45 | */
46 | minimizer?: (Minimizer | Minimizer[]) | undefined;
47 | /**
48 | * generator configuration
49 | */
50 | generator?: Generator[] | undefined;
51 | };
52 |
--------------------------------------------------------------------------------
/test/fixtures/emit-asset-in-child-compilation-loader.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 |
4 | // eslint-disable-next-line node/no-sync
5 | const imageContent = fs.readFileSync(
6 | path.resolve(__dirname, "./child-compilation-image.png")
7 | );
8 |
9 | class ChildCompilationPlugin {
10 | constructor(options = {}) {
11 | this.options = options.options || {};
12 | }
13 |
14 | apply(compiler) {
15 | const plugin = { name: this.constructor.name };
16 | const { RawSource } = compiler.webpack.sources;
17 |
18 | compiler.hooks.compilation.tap(plugin, (compilation) => {
19 | compilation.hooks.additionalAssets.tapAsync(plugin, (callback) => {
20 | compilation.emitAsset(
21 | "child-compilation-image.png",
22 | new RawSource(imageContent)
23 | );
24 |
25 | callback();
26 | });
27 | });
28 | }
29 | }
30 |
31 | export default function loader() {
32 | const callback = this.async();
33 |
34 | // eslint-disable-next-line no-underscore-dangle
35 | const childCompiler = this._compilation.createChildCompiler(
36 | "Child Compilation Plugin Test",
37 | this.options
38 | );
39 |
40 | new ChildCompilationPlugin().apply(childCompiler);
41 |
42 | childCompiler.runAsChild((error) => {
43 | if (error) {
44 | return callback(error);
45 | }
46 |
47 | return callback(null, "export default 1");
48 | });
49 | }
50 |
--------------------------------------------------------------------------------
/test/__snapshots__/ImageminPlugin.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`imagemin plugin should work with mini-css-extract-plugin (svgoMinify): main.css 1`] = `
4 | "a {
5 | background: url(url.png);
6 | background: url(url.webp);
7 | }
8 |
9 | body {
10 | content: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27%23000%27%2F%3E%3C%2Fsvg%3E");
11 | }
12 |
13 | body {
14 | content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyMCAyMCc+PHBhdGggZD0nTTE0LjgzIDQuODlsMS4zNC45NC01LjgxIDguMzhIOS4wMkw1Ljc4IDkuNjdsMS4zNC0xLjI1IDIuNTcgMi40eicgZmlsbD0nIzAwMCcvPjwvc3ZnPg==);
15 | }
16 |
17 | "
18 | `;
19 |
20 | exports[`imagemin plugin should work with mini-css-extract-plugin: main.css 1`] = `
21 | "a {
22 | background: url(url.png);
23 | background: url(url.webp);
24 | }
25 |
26 | body {
27 | content: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22m14.83%204.89%201.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%22%2F%3E%3C%2Fsvg%3E");
28 | }
29 |
30 | body {
31 | content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+PHBhdGggZD0ibTE0LjgzIDQuODkgMS4zNC45NC01LjgxIDguMzhIOS4wMkw1Ljc4IDkuNjdsMS4zNC0xLjI1IDIuNTcgMi40eiIvPjwvc3ZnPg==);
32 | }
33 |
34 | "
35 | `;
36 |
--------------------------------------------------------------------------------
/test/__snapshots__/api.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2 |
3 | exports[`api normalizeImageminConfig should works 1`] = `"No plugins found for \`imagemin\`, please read documentation"`;
4 |
5 | exports[`api normalizeImageminConfig should works 2`] = `"No plugins found for \`imagemin\`, please read documentation"`;
6 |
7 | exports[`api normalizeImageminConfig should works 3`] = `
8 | "Unknown plugin: imagemin-unknown
9 |
10 | Did you forget to install the plugin?
11 | You can install it with:
12 |
13 | $ npm install imagemin-unknown --save-dev
14 | $ yarn add imagemin-unknown --dev
15 | Cause: Cannot find module 'unknown' from 'src/utils.js'"
16 | `;
17 |
18 | exports[`api normalizeImageminConfig should works 4`] = `
19 | "Unknown plugin: imagemin-unknown
20 |
21 | Did you forget to install the plugin?
22 | You can install it with:
23 |
24 | $ npm install imagemin-unknown --save-dev
25 | $ yarn add imagemin-unknown --dev
26 | Cause: Cannot find module 'imagemin-unknown' from 'src/utils.js'"
27 | `;
28 |
29 | exports[`api normalizeImageminConfig should works 5`] = `"No plugins found for \`imagemin\`, please read documentation"`;
30 |
31 | exports[`api normalizeImageminConfig should works 6`] = `"Invalid plugin configuration 'true', plugin configuration should be 'string' or '[string, object]'""`;
32 |
33 | exports[`api normalizeImageminConfig should works 7`] = `
34 | {
35 | "plugins": [
36 | [Function],
37 | ],
38 | }
39 | `;
40 |
41 | exports[`api normalizeImageminConfig should works 8`] = `
42 | {
43 | "plugins": [
44 | [Function],
45 | ],
46 | }
47 | `;
48 |
49 | exports[`api normalizeImageminConfig should works 9`] = `
50 | {
51 | "plugins": [
52 | [Function],
53 | ],
54 | }
55 | `;
56 |
57 | exports[`api normalizeImageminConfig should works 10`] = `
58 | {
59 | "plugins": [
60 | [Function],
61 | ],
62 | }
63 | `;
64 |
--------------------------------------------------------------------------------
/test/api.test.js:
--------------------------------------------------------------------------------
1 | import ImageMinimizerPlugin from "../src/index";
2 |
3 | describe("api", () => {
4 | describe("basic", () => {
5 | it("should exported", () => {
6 | expect(ImageMinimizerPlugin).toBeInstanceOf(Object);
7 | expect(typeof ImageMinimizerPlugin.loader).toBe("string");
8 | expect(typeof ImageMinimizerPlugin.imageminNormalizeConfig).toBe(
9 | "function",
10 | );
11 | expect(typeof ImageMinimizerPlugin.imageminMinify).toBe("function");
12 | expect(typeof ImageMinimizerPlugin.imageminGenerate).toBe("function");
13 | expect(typeof ImageMinimizerPlugin.squooshMinify).toBe("function");
14 | expect(typeof ImageMinimizerPlugin.squooshGenerate).toBe("function");
15 | expect(typeof ImageMinimizerPlugin.sharpMinify).toBe("function");
16 | expect(typeof ImageMinimizerPlugin.sharpGenerate).toBe("function");
17 | });
18 | });
19 |
20 | describe("normalizeImageminConfig", () => {
21 | it("should works", async () => {
22 | await expect(() =>
23 | ImageMinimizerPlugin.imageminNormalizeConfig({}),
24 | ).rejects.toThrowErrorMatchingSnapshot();
25 | await expect(() =>
26 | ImageMinimizerPlugin.imageminNormalizeConfig({ plugins: [] }),
27 | ).rejects.toThrowErrorMatchingSnapshot();
28 | await expect(() =>
29 | ImageMinimizerPlugin.imageminNormalizeConfig({ plugins: ["unknown"] }),
30 | ).rejects.toThrowErrorMatchingSnapshot();
31 | await expect(() =>
32 | ImageMinimizerPlugin.imageminNormalizeConfig({
33 | plugins: ["imagemin-unknown"],
34 | }),
35 | ).rejects.toThrowErrorMatchingSnapshot();
36 | await expect(() =>
37 | ImageMinimizerPlugin.imageminNormalizeConfig({}),
38 | ).rejects.toThrowErrorMatchingSnapshot();
39 | await expect(() =>
40 | ImageMinimizerPlugin.imageminNormalizeConfig({ plugins: [true] }, {}),
41 | ).rejects.toThrowErrorMatchingSnapshot();
42 |
43 | await expect(
44 | ImageMinimizerPlugin.imageminNormalizeConfig({ plugins: ["mozjpeg"] }),
45 | ).resolves.toMatchSnapshot();
46 | await expect(
47 | ImageMinimizerPlugin.imageminNormalizeConfig({
48 | plugins: ["imagemin-mozjpeg"],
49 | }),
50 | ).resolves.toMatchSnapshot();
51 | await expect(
52 | ImageMinimizerPlugin.imageminNormalizeConfig({
53 | plugins: [["mozjpeg"]],
54 | }),
55 | ).resolves.toMatchSnapshot();
56 | await expect(
57 | ImageMinimizerPlugin.imageminNormalizeConfig({
58 | plugins: [["mozjpeg", { quality: 0 }]],
59 | }),
60 | ).resolves.toMatchSnapshot();
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/utils.test.js:
--------------------------------------------------------------------------------
1 | import { isAbsoluteURL, replaceFileExtension } from "../src/utils.js";
2 |
3 | describe("utils", () => {
4 | it("should distinguish between relative and absolute file paths", () => {
5 | expect(isAbsoluteURL("/home/user/img.jpg")).toBe(true);
6 | expect(isAbsoluteURL("user/img.jpg")).toBe(false);
7 | expect(isAbsoluteURL("./user/img.jpg")).toBe(false);
8 | expect(isAbsoluteURL("../user/img.jpg")).toBe(false);
9 |
10 | expect(isAbsoluteURL("C:\\user\\img.jpg")).toBe(true);
11 | expect(isAbsoluteURL("CC:\\user\\img.jpg")).toBe(true);
12 | expect(isAbsoluteURL("user\\img.jpg")).toBe(false);
13 | expect(isAbsoluteURL(".\\user\\img.jpg")).toBe(false);
14 | expect(isAbsoluteURL("..\\user\\img.jpg")).toBe(false);
15 |
16 | expect(isAbsoluteURL("file:/user/img.jpg")).toBe(true);
17 | expect(isAbsoluteURL("file-url:/user/img.jpg")).toBe(true);
18 | expect(isAbsoluteURL("0file:/user/img.jpg")).toBe(false);
19 | });
20 |
21 | it("should replace file extension", () => {
22 | expect(replaceFileExtension("img.jpg", "png")).toBe("img.png");
23 | expect(replaceFileExtension(".img.jpg", "png")).toBe(".img.png");
24 |
25 | expect(replaceFileExtension("/user/img.jpg", "png")).toBe("/user/img.png");
26 | expect(replaceFileExtension("file:///user/img.jpg", "png")).toBe(
27 | "file:///user/img.png",
28 | );
29 | expect(replaceFileExtension("C:\\user\\img.jpg", "png")).toBe(
30 | "C:\\user\\img.png",
31 | );
32 |
33 | expect(replaceFileExtension("user/img.jpg", "png")).toBe("user/img.png");
34 | expect(replaceFileExtension("user\\img.jpg", "png")).toBe("user\\img.png");
35 |
36 | expect(replaceFileExtension("/user/img.jpg.gz", "png")).toBe(
37 | "/user/img.jpg.png",
38 | );
39 | expect(replaceFileExtension("file:///user/img.jpg.gz", "png")).toBe(
40 | "file:///user/img.jpg.png",
41 | );
42 | expect(replaceFileExtension("C:\\user\\img.jpg.gz", "png")).toBe(
43 | "C:\\user\\img.jpg.png",
44 | );
45 |
46 | expect(replaceFileExtension("/user/img", "png")).toBe("/user/img");
47 | expect(replaceFileExtension("file:///user/img", "png")).toBe(
48 | "file:///user/img",
49 | );
50 | expect(replaceFileExtension("C:\\user\\img", "png")).toBe("C:\\user\\img");
51 |
52 | expect(replaceFileExtension("/user/.img", "png")).toBe("/user/.png");
53 | expect(replaceFileExtension("file:///user/.img", "png")).toBe(
54 | "file:///user/.png",
55 | );
56 | expect(replaceFileExtension("C:\\user\\.img", "png")).toBe(
57 | "C:\\user\\.png",
58 | );
59 |
60 | expect(replaceFileExtension("/use.r/img", "png")).toBe("/use.r/img");
61 | expect(replaceFileExtension("file:///use.r/img", "png")).toBe(
62 | "file:///use.r/img",
63 | );
64 | expect(replaceFileExtension("C:\\use.r\\img", "png")).toBe(
65 | "C:\\use.r\\img",
66 | );
67 |
68 | expect(replaceFileExtension("C:\\user/img.jpg", "png")).toBe(
69 | "C:\\user/img.png",
70 | );
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: image-minimizer-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@v4
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 | timeout-minutes: 10
80 |
81 | steps:
82 | - uses: actions/checkout@v4
83 |
84 | - name: Use Node.js ${{ matrix.node-version }}
85 | uses: actions/setup-node@v4
86 | with:
87 | node-version: ${{ matrix.node-version }}
88 | cache: "npm"
89 |
90 | - name: Install dependencies
91 | run: npm ci
92 |
93 | - name: Install webpack ${{ matrix.webpack-version }}
94 | if: matrix.webpack-version != 'latest'
95 | run: npm i webpack@${{ matrix.webpack-version }}
96 |
97 | - name: Run tests for webpack version ${{ matrix.webpack-version }}
98 | run: npm run test:coverage -- --ci
99 |
100 | - name: Submit coverage data to codecov
101 | uses: codecov/codecov-action@v5
102 | with:
103 | token: ${{ secrets.CODECOV_TOKEN }}
104 |
--------------------------------------------------------------------------------
/src/loader-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "definitions": {
3 | "Minimizer": {
4 | "type": "object",
5 | "additionalProperties": false,
6 | "properties": {
7 | "implementation": {
8 | "description": "Implementation of the minimizer function.",
9 | "instanceof": "Function"
10 | },
11 | "options": {
12 | "description": "Options for the minimizer function.",
13 | "type": "object",
14 | "additionalProperties": true
15 | },
16 | "filter": {
17 | "description": "Allows filtering of images.",
18 | "instanceof": "Function"
19 | },
20 | "filename": {
21 | "description": "Allows to set the filename for the minimized asset.",
22 | "anyOf": [
23 | {
24 | "type": "string",
25 | "minLength": 1
26 | },
27 | {
28 | "instanceof": "Function"
29 | }
30 | ]
31 | }
32 | }
33 | },
34 | "Generator": {
35 | "type": "object",
36 | "additionalProperties": false,
37 | "properties": {
38 | "type": {
39 | "description": "Type of generation",
40 | "enum": ["import"]
41 | },
42 | "preset": {
43 | "description": "Name of preset, i.e. using in '?as=webp'.",
44 | "type": "string",
45 | "minLength": 1
46 | },
47 | "implementation": {
48 | "description": "Implementation of the generator function.",
49 | "instanceof": "Function"
50 | },
51 | "options": {
52 | "description": "Options for the generator function.",
53 | "type": "object",
54 | "additionalProperties": true
55 | },
56 | "filter": {
57 | "description": "Allows filtering of images.",
58 | "instanceof": "Function"
59 | },
60 | "filename": {
61 | "description": "Allows to set the filename for the minimized asset.",
62 | "anyOf": [
63 | {
64 | "type": "string",
65 | "minLength": 1
66 | },
67 | {
68 | "instanceof": "Function"
69 | }
70 | ]
71 | }
72 | },
73 | "required": ["implementation", "preset"]
74 | }
75 | },
76 | "title": "Image Minimizer Plugin Loader options",
77 | "type": "object",
78 | "additionalProperties": false,
79 | "properties": {
80 | "minimizer": {
81 | "description": "Allows you to setup the minimizer function and options.",
82 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#minimizer",
83 | "anyOf": [
84 | {
85 | "type": "array",
86 | "minItems": 1,
87 | "items": {
88 | "$ref": "#/definitions/Minimizer"
89 | }
90 | },
91 | {
92 | "$ref": "#/definitions/Minimizer"
93 | }
94 | ]
95 | },
96 | "generator": {
97 | "description": "Allows you to setup the generator function and options.",
98 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#generator",
99 | "type": "array",
100 | "minItems": 1,
101 | "items": {
102 | "$ref": "#/definitions/Generator"
103 | }
104 | },
105 | "severityError": {
106 | "description": "Allows to choose how errors are displayed.",
107 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#severityerror",
108 | "enum": ["off", "warning", "error"]
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/test/loader-severityError-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 |
3 | import ImageMinimizerPlugin from "../src";
4 | import { fixturesPath, isOptimized, plugins, runWebpack } from "./helpers";
5 |
6 | describe("loader severityError option", () => {
7 | it("should throws error on corrupted images using `severityError` option with `error` value", async () => {
8 | const stats = await runWebpack({
9 | entry: path.join(fixturesPath, "loader-corrupted.js"),
10 | imageminLoaderOptions: {
11 | severityError: "error",
12 | minimizer: {
13 | implementation: ImageMinimizerPlugin.imageminMinify,
14 | options: {
15 | plugins,
16 | },
17 | },
18 | },
19 | });
20 | const { compilation } = stats;
21 | const { assets, warnings, errors } = compilation;
22 |
23 | expect(warnings).toHaveLength(0);
24 | expect(errors).toHaveLength(1);
25 | expect(errors[0].message).toMatch(
26 | /(Corrupt JPEG data|Command failed with EPIPE)/,
27 | );
28 | expect(Object.keys(assets)).toHaveLength(3);
29 |
30 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
31 | true,
32 | );
33 | });
34 |
35 | it("should throws error on corrupted images using `severityError` option with `warning` value", async () => {
36 | const stats = await runWebpack({
37 | entry: path.join(fixturesPath, "loader-corrupted.js"),
38 | imageminLoaderOptions: {
39 | minimizer: {
40 | implementation: ImageMinimizerPlugin.imageminMinify,
41 | options: {
42 | plugins,
43 | },
44 | },
45 | severityError: "warning",
46 | },
47 | });
48 | const { compilation } = stats;
49 | const { assets, warnings, errors } = compilation;
50 |
51 | expect(warnings).toHaveLength(1);
52 | expect(errors).toHaveLength(0);
53 | expect(warnings[0].message).toMatch(
54 | /(Corrupt JPEG data|Command failed with EPIPE)/,
55 | );
56 | expect(Object.keys(assets)).toHaveLength(3);
57 |
58 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
59 | true,
60 | );
61 | });
62 |
63 | it("should throws error on corrupted images using `severityError` option with `off` value", async () => {
64 | const stats = await runWebpack({
65 | entry: path.join(fixturesPath, "loader-corrupted.js"),
66 | imageminLoaderOptions: {
67 | minimizer: {
68 | implementation: ImageMinimizerPlugin.imageminMinify,
69 | options: {
70 | plugins,
71 | },
72 | },
73 | severityError: "off",
74 | },
75 | });
76 | const { compilation } = stats;
77 | const { assets, warnings, errors } = compilation;
78 |
79 | expect(warnings).toHaveLength(0);
80 | expect(errors).toHaveLength(0);
81 |
82 | expect(Object.keys(assets)).toHaveLength(3);
83 |
84 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
85 | true,
86 | );
87 | });
88 |
89 | it("should throws error on corrupted images using mode `production` and `severityError` option not specify value", async () => {
90 | const stats = await runWebpack({
91 | mode: "production",
92 | optimization: {
93 | emitOnErrors: true,
94 | },
95 | entry: path.join(fixturesPath, "loader-corrupted.js"),
96 | imageminLoaderOptions: {
97 | minimizer: {
98 | implementation: ImageMinimizerPlugin.imageminMinify,
99 | options: {
100 | plugins,
101 | },
102 | },
103 | },
104 | });
105 | const { compilation } = stats;
106 | const { assets, warnings, errors } = compilation;
107 |
108 | expect(warnings).toHaveLength(0);
109 | expect(errors).toHaveLength(1);
110 | expect(errors[0].message).toMatch(
111 | /(Corrupt JPEG data|Command failed with EPIPE)/,
112 | );
113 | expect(Object.keys(assets)).toHaveLength(3);
114 |
115 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
116 | true,
117 | );
118 | });
119 | });
120 |
--------------------------------------------------------------------------------
/src/worker.js:
--------------------------------------------------------------------------------
1 | /** @typedef {import("./index").WorkerResult} WorkerResult */
2 | /** @typedef {import("./index").FilenameFn} FilenameFn */
3 |
4 | /** @type {unique symbol} */
5 | const isFilenameProcessed = Symbol("isFilenameProcessed");
6 |
7 | /**
8 | * @template T
9 | * @param {WorkerResult} result worker result
10 | * @param {import("./index").InternalWorkerOptions} options worker options
11 | * @param {undefined | string | FilenameFn} filenameTemplate filename template
12 | */
13 | function processFilenameTemplate(result, options, filenameTemplate) {
14 | if (
15 | !result.info[isFilenameProcessed] &&
16 | typeof filenameTemplate !== "undefined" &&
17 | typeof options.generateFilename === "function"
18 | ) {
19 | result.filename = options.generateFilename(filenameTemplate, {
20 | filename: result.filename,
21 | });
22 |
23 | result.filename = result.filename
24 | .replaceAll(/\[width\]/gi, result.info.width)
25 | .replaceAll(/\[height\]/gi, result.info.height);
26 |
27 | result.info[isFilenameProcessed] = true;
28 | }
29 | }
30 |
31 | /**
32 | * @template T
33 | * @param {WorkerResult} result worker result
34 | * @param {import("./index").InternalWorkerOptions} options worker options
35 | */
36 | function processSeverityError(result, options) {
37 | if (options.severityError === "off") {
38 | result.warnings = [];
39 | result.errors = [];
40 | } else if (options.severityError === "warning") {
41 | result.warnings = [...result.warnings, ...result.errors];
42 | result.errors = [];
43 | }
44 | }
45 |
46 | /**
47 | * @template T
48 | * @param {import("./index").InternalWorkerOptions} options worker options
49 | * @returns {Promise} worker result
50 | */
51 | async function worker(options) {
52 | /** @type {WorkerResult} */
53 | let result = {
54 | data: options.input,
55 | filename: options.filename,
56 | warnings: [],
57 | errors: [],
58 | info: {
59 | sourceFilename:
60 | options.info &&
61 | typeof options.info === "object" &&
62 | typeof options.info.sourceFilename === "string"
63 | ? options.info.sourceFilename
64 | : typeof options.filename === "string"
65 | ? options.filename
66 | : undefined,
67 | },
68 | };
69 |
70 | if (!result.data) {
71 | result.errors.push(new Error("Empty input"));
72 | return result;
73 | }
74 |
75 | const transformers = Array.isArray(options.transformer)
76 | ? options.transformer
77 | : [options.transformer];
78 |
79 | /** @type {undefined | string | FilenameFn} */
80 | let filenameTemplate;
81 |
82 | for (const transformer of transformers) {
83 | if (
84 | typeof transformer.filter === "function" &&
85 | // eslint-disable-next-line unicorn/no-array-method-this-argument
86 | !transformer.filter(options.input, options.filename)
87 | ) {
88 | continue;
89 | }
90 |
91 | /** @type {WorkerResult | null} */
92 | let processedResult;
93 |
94 | try {
95 | processedResult = await transformer.implementation(
96 | result,
97 | transformer.options,
98 | );
99 | } catch (error) {
100 | result.errors.push(
101 | error instanceof Error
102 | ? error
103 | : new Error(/** @type {string} */ (error)),
104 | );
105 |
106 | return result;
107 | }
108 |
109 | if (processedResult && !Buffer.isBuffer(processedResult.data)) {
110 | result.errors.push(
111 | new Error(
112 | "minimizer function doesn't return the 'data' property or result is not a 'Buffer' value",
113 | ),
114 | );
115 |
116 | return result;
117 | }
118 |
119 | if (processedResult) {
120 | result = processedResult;
121 | filenameTemplate ??= transformer.filename;
122 | }
123 | }
124 |
125 | result.info ??= {};
126 | result.errors ??= [];
127 | result.warnings ??= [];
128 | result.filename ??= options.filename;
129 |
130 | processSeverityError(result, options);
131 | processFilenameTemplate(result, options, filenameTemplate);
132 |
133 | return result;
134 | }
135 |
136 | module.exports = worker;
137 | module.exports.isFilenameProcessed = isFilenameProcessed;
138 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-minimizer-webpack-plugin",
3 | "version": "4.1.4",
4 | "description": "Webpack loader and plugin to optimize (compress) images using imagemin",
5 | "keywords": [
6 | "webpack",
7 | "loader",
8 | "plugin",
9 | "imagemin",
10 | "images",
11 | "minify",
12 | "compress",
13 | "optimize"
14 | ],
15 | "homepage": "https://github.com/webpack/image-minimizer-webpack-plugin",
16 | "bugs": "https://github.com/webpack/image-minimizer-webpack-plugin/issues",
17 | "repository": "webpack/image-minimizer-webpack-plugin",
18 | "funding": {
19 | "type": "opencollective",
20 | "url": "https://opencollective.com/webpack"
21 | },
22 | "license": "MIT",
23 | "author": "Alexander Krasnoyarov (https://github.com/evilebottnawi)",
24 | "main": "dist/index.js",
25 | "types": "types/index.d.ts",
26 | "files": [
27 | "dist",
28 | "types"
29 | ],
30 | "scripts": {
31 | "start": "npm run build -- -w",
32 | "clean": "del-cli dist types",
33 | "prebuild": "npm run clean",
34 | "build:types": "tsc --declaration --emitDeclarationOnly && prettier \"types/**/*.ts\" --write",
35 | "build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
36 | "build": "npm-run-all -p \"build:**\"",
37 | "commitlint": "commitlint --from=main",
38 | "security": "npm audit --production",
39 | "lint:prettier": "prettier --cache --list-different .",
40 | "lint:code": "eslint --cache .",
41 | "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"",
42 | "lint:types": "tsc --pretty --noEmit",
43 | "lint": "npm-run-all -l -p \"lint:**\"",
44 | "fix:code": "npm run lint:code -- --fix",
45 | "fix:prettier": "npm run lint:prettier -- --write",
46 | "fix": "npm-run-all -l fix:code fix:prettier",
47 | "test:only": "cross-env NODE_ENV=test node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
48 | "test:watch": "npm run test:only -- --watch",
49 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
50 | "pretest": "npm run lint",
51 | "test": "npm run test:coverage",
52 | "prepare": "husky && npm run build",
53 | "release": "standard-version"
54 | },
55 | "overrides": {
56 | "sharp": "$sharp",
57 | "imagemin-avif": {
58 | "sharp": "$sharp"
59 | }
60 | },
61 | "dependencies": {
62 | "schema-utils": "^4.2.0",
63 | "serialize-javascript": "^6.0.2"
64 | },
65 | "devDependencies": {
66 | "@babel/cli": "^7.24.7",
67 | "@babel/core": "^7.25.2",
68 | "@babel/preset-env": "^7.25.3",
69 | "@commitlint/cli": "^19.3.0",
70 | "@commitlint/config-conventional": "^19.2.2",
71 | "@eslint/js": "^9.33.0",
72 | "@eslint/markdown": "^7.0.0",
73 | "@squoosh/lib": "^0.5.3",
74 | "@stylistic/eslint-plugin": "^5.2.3",
75 | "@types/imagemin": "^9.0.0",
76 | "@types/node": "^20.14.9",
77 | "@types/serialize-javascript": "^5.0.4",
78 | "copy-webpack-plugin": "^13.0.1",
79 | "cross-env": "^7.0.3",
80 | "cspell": "^8.13.1",
81 | "css-loader": "^7.1.2",
82 | "del-cli": "^6.0.0",
83 | "eslint": "^9.31.0",
84 | "eslint-config-prettier": "^10.1.8",
85 | "eslint-config-webpack": "^4.4.2",
86 | "eslint-plugin-import": "^2.32.0",
87 | "eslint-plugin-jest": "^29.0.1",
88 | "eslint-plugin-jsdoc": "^53.0.1",
89 | "eslint-plugin-n": "^17.21.0",
90 | "eslint-plugin-prettier": "^5.5.4",
91 | "eslint-plugin-unicorn": "^60.0.0",
92 | "file-loader": "^6.2.0",
93 | "file-type": "^16.5.4",
94 | "globals": "^16.3.0",
95 | "husky": "^9.1.4",
96 | "image-size": "^2.0.2",
97 | "imagemin": "^9.0.0",
98 | "imagemin-avif": "^0.1.6",
99 | "imagemin-gifsicle": "^7.0.0",
100 | "imagemin-mozjpeg": "^10.0.0",
101 | "imagemin-pngquant": "^10.0.0",
102 | "imagemin-svgo": "^11.0.1",
103 | "imagemin-webp": "^8.0.0",
104 | "jest": "^30.0.0",
105 | "lint-staged": "^15.2.8",
106 | "memfs": "^4.11.1",
107 | "mini-css-extract-plugin": "^2.9.0",
108 | "npm-run-all": "^4.1.5",
109 | "prettier": "^3.3.2",
110 | "remark-cli": "^12.0.1",
111 | "remark-preset-lint-itgalaxy": "^16.0.0",
112 | "sharp": "^0.34.3",
113 | "standard-version": "^9.5.0",
114 | "svgo": "^4.0.0",
115 | "tempy": "^3.1.0",
116 | "typescript": "^5.5.3",
117 | "typescript-eslint": "^8.38.0",
118 | "url-loader": "^4.1.1",
119 | "webpack": "^5.92.1"
120 | },
121 | "peerDependencies": {
122 | "webpack": "^5.1.0"
123 | },
124 | "peerDependenciesMeta": {
125 | "sharp": {
126 | "optional": true
127 | },
128 | "@squoosh/lib": {
129 | "optional": true
130 | },
131 | "imagemin": {
132 | "optional": true
133 | },
134 | "svgo": {
135 | "optional": true
136 | }
137 | },
138 | "engines": {
139 | "node": ">= 18.12.0"
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/plugin-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "definitions": {
3 | "Rule": {
4 | "description": "Filtering rule as regex or string.",
5 | "anyOf": [
6 | {
7 | "instanceof": "RegExp"
8 | },
9 | {
10 | "type": "string",
11 | "minLength": 1
12 | }
13 | ]
14 | },
15 | "Rules": {
16 | "description": "Filtering rules.",
17 | "anyOf": [
18 | {
19 | "type": "array",
20 | "items": {
21 | "description": "A rule condition.",
22 | "oneOf": [
23 | {
24 | "$ref": "#/definitions/Rule"
25 | }
26 | ]
27 | }
28 | },
29 | {
30 | "$ref": "#/definitions/Rule"
31 | }
32 | ]
33 | },
34 | "Minimizer": {
35 | "type": "object",
36 | "additionalProperties": false,
37 | "properties": {
38 | "implementation": {
39 | "description": "Implementation of the minimizer function.",
40 | "instanceof": "Function"
41 | },
42 | "filter": {
43 | "description": "Allows filtering of images.",
44 | "instanceof": "Function"
45 | },
46 | "filename": {
47 | "description": "Allows to set the filename for the minimized asset.",
48 | "anyOf": [
49 | {
50 | "type": "string",
51 | "minLength": 1
52 | },
53 | {
54 | "instanceof": "Function"
55 | }
56 | ]
57 | },
58 | "options": {
59 | "description": "Options for the minimizer function.",
60 | "type": "object",
61 | "additionalProperties": true
62 | }
63 | }
64 | },
65 | "Generator": {
66 | "type": "object",
67 | "additionalProperties": false,
68 | "properties": {
69 | "type": {
70 | "description": "Type of generation",
71 | "enum": ["import", "asset"]
72 | },
73 | "preset": {
74 | "description": "Name of preset, i.e. using in '?as=webp'.",
75 | "type": "string",
76 | "minLength": 1
77 | },
78 | "implementation": {
79 | "description": "Implementation of the generator function.",
80 | "instanceof": "Function"
81 | },
82 | "options": {
83 | "description": "Options for the generator function.",
84 | "type": "object",
85 | "additionalProperties": true
86 | },
87 | "filter": {
88 | "description": "Allows filtering of images.",
89 | "instanceof": "Function"
90 | },
91 | "filename": {
92 | "description": "Allows to set the filename for the minimized asset.",
93 | "anyOf": [
94 | {
95 | "type": "string",
96 | "minLength": 1
97 | },
98 | {
99 | "instanceof": "Function"
100 | }
101 | ]
102 | }
103 | },
104 | "required": ["implementation"]
105 | }
106 | },
107 | "type": "object",
108 | "additionalProperties": false,
109 | "properties": {
110 | "test": {
111 | "description": "Include all modules that pass test assertion.",
112 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#test",
113 | "oneOf": [
114 | {
115 | "$ref": "#/definitions/Rules"
116 | }
117 | ]
118 | },
119 | "include": {
120 | "description": "Include all modules matching any of these conditions.",
121 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#include",
122 | "oneOf": [
123 | {
124 | "$ref": "#/definitions/Rules"
125 | }
126 | ]
127 | },
128 | "exclude": {
129 | "description": "Exclude all modules matching any of these conditions.",
130 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#exclude",
131 | "oneOf": [
132 | {
133 | "$ref": "#/definitions/Rules"
134 | }
135 | ]
136 | },
137 | "minimizer": {
138 | "description": "Allows you to setup the minimizer function and options.",
139 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#minimizer",
140 | "anyOf": [
141 | {
142 | "type": "array",
143 | "minItems": 1,
144 | "items": {
145 | "$ref": "#/definitions/Minimizer"
146 | }
147 | },
148 | {
149 | "$ref": "#/definitions/Minimizer"
150 | }
151 | ]
152 | },
153 | "generator": {
154 | "description": "Allows you to setup the generator function and options.",
155 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#generator",
156 | "type": "array",
157 | "minItems": 1,
158 | "items": {
159 | "$ref": "#/definitions/Generator"
160 | }
161 | },
162 | "severityError": {
163 | "description": "Allows to choose how errors are displayed.",
164 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#severityerror",
165 | "enum": ["off", "warning", "error"]
166 | },
167 | "loader": {
168 | "description": "Automatically adding `imagemin-loader` (require for minification images using in `url-loader`, `svg-url-loader` or other).",
169 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#loader",
170 | "type": "boolean"
171 | },
172 | "concurrency": {
173 | "description": "Number of concurrency optimization processes in one time.",
174 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#concurrency",
175 | "type": "number"
176 | },
177 | "deleteOriginalAssets": {
178 | "type": "boolean",
179 | "description": "Allows to remove original assets after minimization.",
180 | "link": "https://github.com/webpack/image-minimizer-webpack-plugin#deleteoriginalassets"
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/test/validate-loader-options.test.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 |
3 | import ImageMinimizerPlugin from "../src";
4 |
5 | import { fixturesPath, plugins, runWebpack } from "./helpers";
6 |
7 | describe("validate loader options", () => {
8 | const tests = {
9 | minimizer: {
10 | success: [
11 | {
12 | implementation: ImageMinimizerPlugin.sharpMinify,
13 | },
14 | {
15 | implementation: ImageMinimizerPlugin.sharpMinify,
16 | options: {
17 | encodeOptions: {
18 | mozjpeg: {
19 | quality: 90,
20 | },
21 | },
22 | },
23 | },
24 | [
25 | {
26 | implementation: ImageMinimizerPlugin.sharpMinify,
27 | },
28 | ],
29 | [
30 | {
31 | implementation: ImageMinimizerPlugin.sharpMinify,
32 | options: {
33 | encodeOptions: {
34 | mozjpeg: {
35 | quality: 90,
36 | },
37 | },
38 | },
39 | },
40 | ],
41 | [
42 | {
43 | implementation: ImageMinimizerPlugin.sharpMinify,
44 | },
45 | {
46 | implementation: ImageMinimizerPlugin.sharpMinify,
47 | options: {
48 | encodeOptions: {
49 | mozjpeg: {
50 | quality: 90,
51 | },
52 | },
53 | },
54 | },
55 | ],
56 | {
57 | implementation: ImageMinimizerPlugin.sharpMinify,
58 | filter: () => false,
59 | },
60 | [
61 | {
62 | implementation: ImageMinimizerPlugin.sharpMinify,
63 | filter: () => false,
64 | },
65 | ],
66 | {
67 | implementation: ImageMinimizerPlugin.sharpMinify,
68 | filename: "[name].[ext]",
69 | },
70 | [
71 | {
72 | implementation: ImageMinimizerPlugin.sharpMinify,
73 | filename: () => "[name].[ext]",
74 | },
75 | ],
76 | ],
77 | failure: [
78 | 1,
79 | true,
80 | false,
81 | null,
82 | [],
83 | {
84 | implementation: ImageMinimizerPlugin.sharpMinify,
85 | filter: true,
86 | },
87 | {
88 | implementation: ImageMinimizerPlugin.sharpMinify,
89 | filename: true,
90 | },
91 | ],
92 | },
93 | generator: {
94 | success: [
95 | [
96 | {
97 | preset: "webp",
98 | implementation: ImageMinimizerPlugin.sharpGenerate,
99 | },
100 | ],
101 | [
102 | {
103 | preset: "one",
104 | implementation: ImageMinimizerPlugin.sharpGenerate,
105 | },
106 | {
107 | preset: "two",
108 | implementation: ImageMinimizerPlugin.sharpGenerate,
109 | },
110 | ],
111 | [
112 | {
113 | preset: "webp",
114 | implementation: ImageMinimizerPlugin.sharpGenerate,
115 | filter: () => false,
116 | },
117 | ],
118 | [
119 | {
120 | preset: "webp",
121 | implementation: ImageMinimizerPlugin.sharpGenerate,
122 | filename: "[name][ext]",
123 | },
124 | ],
125 | [
126 | {
127 | preset: "webp",
128 | implementation: ImageMinimizerPlugin.sharpGenerate,
129 | filename: () => "[name][ext]",
130 | },
131 | ],
132 | ],
133 | failure: [
134 | 1,
135 | true,
136 | false,
137 | null,
138 | [],
139 | [
140 | {
141 | preset: "webp",
142 | },
143 | ],
144 | [
145 | {
146 | preset: "webp",
147 | implementation: ImageMinimizerPlugin.sharpGenerate,
148 | filter: true,
149 | },
150 | ],
151 | [
152 | {
153 | preset: "webp",
154 | implementation: ImageMinimizerPlugin.sharpGenerate,
155 | filename: true,
156 | },
157 | ],
158 | ],
159 | },
160 | severityError: {
161 | success: ["error"],
162 | failure: [true, false, {}, [], () => {}],
163 | },
164 | unknown: {
165 | success: [],
166 | failure: [1, true, false, "test", /test/, [], {}, { foo: "bar" }],
167 | },
168 | };
169 |
170 | function stringifyValue(value) {
171 | if (
172 | Array.isArray(value) ||
173 | (value && typeof value === "object" && value.constructor === Object)
174 | ) {
175 | return JSON.stringify(value);
176 | }
177 |
178 | return value;
179 | }
180 |
181 | function createTestCase(key, value, type) {
182 | it(`should ${
183 | type === "success" ? "successfully validate" : "throw an error on"
184 | } the "${key}" option with "${stringifyValue(value)}" value`, async () => {
185 | const options = {
186 | entry: path.join(fixturesPath, "validate-options.js"),
187 | imageminLoaderOptions: {
188 | [key]: value,
189 | },
190 | };
191 |
192 | if (key !== "minimizer") {
193 | options.imageminLoaderOptions.minimizer = {
194 | implementation: ImageMinimizerPlugin.imageminMinify,
195 | options: { plugins },
196 | };
197 | }
198 |
199 | let stats;
200 |
201 | try {
202 | stats = await runWebpack(options);
203 | } finally {
204 | const shouldSuccess = type === "success";
205 | const {
206 | compilation: { errors },
207 | } = stats;
208 |
209 | expect(stats.hasErrors()).toBe(!shouldSuccess);
210 | expect(errors).toHaveLength(shouldSuccess ? 0 : 1);
211 |
212 | if (!shouldSuccess) {
213 | expect(() => {
214 | throw new Error(errors[0].error.message);
215 | }).toThrowErrorMatchingSnapshot();
216 | }
217 | }
218 | });
219 | }
220 |
221 | for (const [key, values] of Object.entries(tests)) {
222 | for (const type of Object.keys(values)) {
223 | for (const value of values[type]) {
224 | createTestCase(key, value, type);
225 | }
226 | }
227 | }
228 | });
229 |
--------------------------------------------------------------------------------
/types/utils.d.ts:
--------------------------------------------------------------------------------
1 | export type WorkerResult = import("./index").WorkerResult;
2 | export type CustomOptions = import("./index").CustomOptions;
3 | export type WebpackError = import("webpack").WebpackError;
4 | export type Module = import("webpack").Module;
5 | export type AssetInfo = import("webpack").AssetInfo;
6 | export type EXPECTED_ANY = any;
7 | export type Task = () => Promise;
8 | export type FunctionReturning = () => T;
9 | export type CustomSharpFormat = EXPECTED_ANY;
10 | export type Uint8ArrayUtf8ByteString = (
11 | array: number[] | Uint8Array,
12 | start: number,
13 | end: number,
14 | ) => string;
15 | export type StringToBytes = (string: string) => number[];
16 | export type MetaData = {
17 | /**
18 | * warnings
19 | */
20 | warnings: Array;
21 | /**
22 | * errors
23 | */
24 | errors: Array;
25 | };
26 | export type SquooshImage = {
27 | /**
28 | * preprocess
29 | */
30 | preprocess: (options: Record) => Promise;
31 | /**
32 | * encode
33 | */
34 | encode: (options: Record) => Promise;
35 | /**
36 | * encoded with
37 | */
38 | encodedWith: Record<
39 | string,
40 | {
41 | binary: Uint8Array;
42 | extension: string;
43 | }
44 | >;
45 | /**
46 | * decoded
47 | */
48 | decoded: {
49 | bitmap: {
50 | width: number;
51 | height: number;
52 | };
53 | };
54 | };
55 | export type SquooshImagePool = {
56 | /**
57 | * ingest image function
58 | */
59 | ingestImage: (data: Uint8Array) => SquooshImage;
60 | /**
61 | * close function
62 | */
63 | close: () => Promise;
64 | };
65 | export type SizeSuffix = (width: number, height: number) => string;
66 | export const ABSOLUTE_URL_REGEX: RegExp;
67 | /** @type {WeakMap} */
68 | export const IMAGE_MINIMIZER_PLUGIN_INFO_MAPPINGS: WeakMap;
69 | export const WINDOWS_PATH_REGEX: RegExp;
70 | /**
71 | * @param {WorkerResult} original original worker result
72 | * @param {CustomOptions=} options options
73 | * @returns {Promise} generated result
74 | */
75 | export function imageminGenerate(
76 | original: WorkerResult,
77 | options?: CustomOptions | undefined,
78 | ): Promise;
79 | /**
80 | * @param {WorkerResult} original original worker result
81 | * @param {CustomOptions=} options options
82 | * @returns {Promise} minified result
83 | */
84 | export function imageminMinify(
85 | original: WorkerResult,
86 | options?: CustomOptions | undefined,
87 | ): Promise;
88 | /**
89 | * @param {Record} imageminConfig imagemin configuration
90 | * @returns {Promise>} normalized imagemin configuration
91 | */
92 | export function imageminNormalizeConfig(
93 | imageminConfig: Record,
94 | ): Promise>;
95 | /**
96 | * @param {string} url URL
97 | * @returns {boolean} true when URL is absolute, otherwise false
98 | */
99 | export function isAbsoluteURL(url: string): boolean;
100 | /**
101 | * @template T
102 | * @typedef {() => T} FunctionReturning
103 | */
104 | /**
105 | * @template T
106 | * @param {FunctionReturning} fn memorized function
107 | * @returns {FunctionReturning} new function
108 | */
109 | export function memoize(fn: FunctionReturning): FunctionReturning;
110 | /** @typedef {import("./index").WorkerResult} WorkerResult */
111 | /** @typedef {import("./index").CustomOptions} CustomOptions */
112 | /** @typedef {import("webpack").WebpackError} WebpackError */
113 | /** @typedef {import("webpack").Module} Module */
114 | /** @typedef {import("webpack").AssetInfo} AssetInfo */
115 | /** @typedef {any} EXPECTED_ANY */
116 | /**
117 | * @template T
118 | * @typedef {() => Promise} Task
119 | */
120 | /**
121 | * @param {string} filename file path without query params (e.g. `path/img.png`)
122 | * @param {string} ext new file extension without `.` (e.g. `webp`)
123 | * @returns {string} new filename `path/img.png` -> `path/img.webp`
124 | */
125 | export function replaceFileExtension(filename: string, ext: string): string;
126 | /**
127 | * @param {WorkerResult} original original worker result
128 | * @param {CustomOptions=} options options
129 | * @returns {Promise} generated result
130 | */
131 | export function sharpGenerate(
132 | original: WorkerResult,
133 | options?: CustomOptions | undefined,
134 | ): Promise;
135 | /**
136 | * @param {WorkerResult} original original worker result
137 | * @param {CustomOptions=} options options
138 | * @returns {Promise} minified result
139 | */
140 | export function sharpMinify(
141 | original: WorkerResult,
142 | options?: CustomOptions | undefined,
143 | ): Promise;
144 | /**
145 | * @param {WorkerResult} original original worker result
146 | * @param {CustomOptions=} options options
147 | * @returns {Promise} generated result
148 | */
149 | export function squooshGenerate(
150 | original: WorkerResult,
151 | options?: CustomOptions | undefined,
152 | ): Promise;
153 | export namespace squooshGenerate {
154 | export { squooshImagePoolSetup as setup };
155 | export { squooshImagePoolTeardown as teardown };
156 | }
157 | /**
158 | * @param {WorkerResult} original original worker result
159 | * @param {CustomOptions=} options options
160 | * @returns {Promise} minified result
161 | */
162 | export function squooshMinify(
163 | original: WorkerResult,
164 | options?: CustomOptions | undefined,
165 | ): Promise;
166 | export namespace squooshMinify {
167 | export { squooshImagePoolSetup as setup };
168 | export { squooshImagePoolTeardown as teardown };
169 | }
170 | /**
171 | * @param {WorkerResult} original original worker result
172 | * @param {CustomOptions=} options options
173 | * @returns {Promise} minified result
174 | */
175 | export function svgoMinify(
176 | original: WorkerResult,
177 | options?: CustomOptions | undefined,
178 | ): Promise;
179 | /**
180 | * Run tasks with limited concurrency.
181 | * @template T
182 | * @param {number} limit Limit of tasks that run at once.
183 | * @param {Task[]} tasks List of tasks to run.
184 | * @returns {Promise} A promise that fulfills to an array of the results
185 | */
186 | export function throttleAll(limit: number, tasks: Task[]): Promise;
187 | /**
188 | * @returns {void}
189 | */
190 | declare function squooshImagePoolSetup(): void;
191 | /**
192 | * @returns {Promise}
193 | */
194 | declare function squooshImagePoolTeardown(): Promise;
195 | export {};
196 |
--------------------------------------------------------------------------------
/test/plugin-deleteOriginalAssets-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 |
3 | import ImageMinimizerPlugin from "../src/index.js";
4 | import { fixturesPath, plugins, runWebpack } from "./helpers";
5 |
6 | describe('plugin "deleteOriginalAssets" option', () => {
7 | it("should minimize asset and delete original asset (default behavior)", async () => {
8 | const stats = await runWebpack({
9 | entry: path.join(fixturesPath, "./empty-entry.js"),
10 | emitPlugin: true,
11 | emitPluginOptions: { fileNames: ["./nested/deep/plugin-test.png"] },
12 | imageminPluginOptions: {
13 | minimizer: {
14 | implementation: ImageMinimizerPlugin.imageminMinify,
15 | filename: "[path]minimizer-[name][ext]",
16 | options: { plugins },
17 | },
18 | },
19 | });
20 | const { compilation } = stats;
21 | const { warnings, errors, assets } = compilation;
22 | const newAsset = Object.keys(assets).filter((asset) =>
23 | asset.includes("./nested/deep/minimizer-plugin-test.png"),
24 | );
25 | const originalAsset = Object.keys(assets).filter((asset) =>
26 | asset.includes("./nested/deep/plugin-test.png"),
27 | );
28 |
29 | expect(newAsset).toHaveLength(1);
30 | expect(originalAsset).toHaveLength(0);
31 | expect(warnings).toHaveLength(0);
32 | expect(errors).toHaveLength(0);
33 | });
34 |
35 | it("should minimize asset and delete original asset when the name is the same (default behavior)", async () => {
36 | const stats = await runWebpack({
37 | entry: path.join(fixturesPath, "./empty-entry.js"),
38 | emitPlugin: true,
39 | emitPluginOptions: { fileNames: ["./nested/deep/plugin-test.png"] },
40 | imageminPluginOptions: {
41 | minimizer: {
42 | implementation: ImageMinimizerPlugin.imageminMinify,
43 | options: { plugins },
44 | },
45 | },
46 | });
47 | const { compilation } = stats;
48 | const { warnings, errors, assets } = compilation;
49 | const newAsset = Object.keys(assets).filter((asset) =>
50 | asset.includes("./nested/deep/plugin-test.png"),
51 | );
52 |
53 | expect(newAsset).toHaveLength(1);
54 | expect(Object.keys(assets)).toHaveLength(2);
55 | expect(warnings).toHaveLength(0);
56 | expect(errors).toHaveLength(0);
57 | });
58 |
59 | it('should minimize asset and delete original asset and keep original asset when the "deleteOriginalAssets" option is "false"', async () => {
60 | const stats = await runWebpack({
61 | entry: path.join(fixturesPath, "./empty-entry.js"),
62 | emitPlugin: true,
63 | emitPluginOptions: { fileNames: ["./nested/deep/plugin-test.png"] },
64 | imageminPluginOptions: {
65 | deleteOriginalAssets: false,
66 | minimizer: {
67 | implementation: ImageMinimizerPlugin.imageminMinify,
68 | filename: "[path]minimizer-[name][ext]",
69 | options: { plugins: ["imagemin-pngquant"] },
70 | },
71 | },
72 | });
73 | const { compilation } = stats;
74 | const { warnings, errors, assets } = compilation;
75 | const newAsset = Object.keys(assets).filter((asset) =>
76 | asset.includes("./nested/deep/minimizer-plugin-test.png"),
77 | );
78 | const originalAsset = Object.keys(assets).filter((asset) =>
79 | asset.includes("./nested/deep/plugin-test.png"),
80 | );
81 |
82 | expect(newAsset).toHaveLength(1);
83 | expect(originalAsset).toHaveLength(1);
84 | expect(warnings).toHaveLength(0);
85 | expect(errors).toHaveLength(0);
86 | });
87 |
88 | it('should transform asset and keep original asset when the "deleteOriginalAssets" option is "true"', async () => {
89 | const stats = await runWebpack({
90 | entry: path.join(fixturesPath, "./empty-entry.js"),
91 | emitPlugin: true,
92 | emitPluginOptions: { fileNames: ["./nested/deep/plugin-test.png"] },
93 | imageminPluginOptions: {
94 | deleteOriginalAssets: true,
95 | minimizer: {
96 | implementation: ImageMinimizerPlugin.imageminMinify,
97 | filename: "[path]minimizer-[name][ext]",
98 | options: { plugins: ["imagemin-pngquant"] },
99 | },
100 | },
101 | });
102 | const { compilation } = stats;
103 | const { warnings, errors, assets } = compilation;
104 | const newAsset = Object.keys(assets).filter((asset) =>
105 | asset.includes("./nested/deep/minimizer-plugin-test.png"),
106 | );
107 | const originalAsset = Object.keys(assets).filter((asset) =>
108 | asset.includes("./nested/deep/plugin-test.png"),
109 | );
110 |
111 | expect(newAsset).toHaveLength(1);
112 | expect(originalAsset).toHaveLength(0);
113 | expect(warnings).toHaveLength(0);
114 | expect(errors).toHaveLength(0);
115 | });
116 |
117 | it('should transform asset and keep original asset when the "deleteOriginalAssets" option is "true" (multi compiler mode)', async () => {
118 | const multiStats = await runWebpack([
119 | {
120 | entry: path.join(fixturesPath, "./empty-entry.js"),
121 | emitPlugin: true,
122 | emitPluginOptions: { fileNames: ["./nested/deep/plugin-test.png"] },
123 | imageminPluginOptions: {
124 | deleteOriginalAssets: true,
125 | minimizer: {
126 | implementation: ImageMinimizerPlugin.imageminMinify,
127 | filename: "[path]one-minimized-[name][ext]",
128 | options: { plugins },
129 | },
130 | },
131 | },
132 | {
133 | entry: path.join(fixturesPath, "./empty-entry.js"),
134 | emitPlugin: true,
135 | emitPluginOptions: { fileNames: ["./nested/deep/plugin-test.png"] },
136 | imageminPluginOptions: {
137 | minimizer: {
138 | implementation: ImageMinimizerPlugin.imageminMinify,
139 | filename: "[path]two-minimized-[name][ext]",
140 | options: { plugins },
141 | },
142 | },
143 | },
144 | ]);
145 |
146 | expect(multiStats.stats).toHaveLength(2);
147 |
148 | const [{ compilation }, { compilation: secondCompilation }] =
149 | multiStats.stats;
150 | const { warnings, errors, assets } = compilation;
151 |
152 | const transformedAssets = Object.keys(assets).filter((asset) =>
153 | asset.includes("./nested/deep/one-minimized-plugin-test.png"),
154 | );
155 |
156 | const originalAssets = Object.keys(assets).filter((asset) =>
157 | asset.includes("./nested/deep/plugin-test.png"),
158 | );
159 |
160 | const { warnings: secondWarnings, errors: secondErrors } =
161 | secondCompilation;
162 |
163 | expect(secondWarnings).toHaveLength(0);
164 | expect(secondErrors).toHaveLength(0);
165 |
166 | expect(transformedAssets).toHaveLength(1);
167 | expect(originalAssets).toHaveLength(0);
168 | expect(warnings).toHaveLength(0);
169 | expect(errors).toHaveLength(0);
170 | });
171 | });
172 |
--------------------------------------------------------------------------------
/test/plugin-severityError-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 |
3 | import ImageMinimizerPlugin from "../src/index.js";
4 | import { fixturesPath, isOptimized, plugins, runWebpack } from "./helpers";
5 |
6 | describe("plugin severityError option", () => {
7 | it("should optimizes images and throws error on corrupted images using `plugin.severityError` option with `error` value (by plugin)", async () => {
8 | const stats = await runWebpack({
9 | emitPluginOptions: {
10 | fileNames: ["test-corrupted.jpg", "plugin-test.png"],
11 | },
12 | entry: path.join(fixturesPath, "empty-entry.js"),
13 | imageminPluginOptions: {
14 | severityError: "error",
15 | minimizer: {
16 | implementation: ImageMinimizerPlugin.imageminMinify,
17 | options: { plugins },
18 | },
19 | },
20 | });
21 | const { compilation } = stats;
22 | const { warnings, errors } = compilation;
23 |
24 | expect(warnings).toHaveLength(0);
25 | expect(errors).toHaveLength(1);
26 | expect(errors[0].message).toMatch(
27 | /(Corrupt JPEG data|Command failed with EPIPE)/,
28 | );
29 |
30 | await expect(isOptimized("plugin-test.png", compilation)).resolves.toBe(
31 | true,
32 | );
33 | });
34 |
35 | it("should optimizes images and not throws error or warnings on corrupted images using `plugin.severityError` option with `off` value (by plugin)", async () => {
36 | const stats = await runWebpack({
37 | emitPluginOptions: {
38 | fileNames: ["test-corrupted.jpg", "plugin-test.png"],
39 | },
40 | entry: path.join(fixturesPath, "empty-entry.js"),
41 | imageminPluginOptions: {
42 | severityError: "off",
43 | minimizer: {
44 | implementation: ImageMinimizerPlugin.imageminMinify,
45 | options: { plugins },
46 | },
47 | },
48 | });
49 | const { compilation } = stats;
50 | const { warnings, errors } = compilation;
51 |
52 | expect(warnings).toHaveLength(0);
53 | expect(errors).toHaveLength(0);
54 |
55 | await expect(isOptimized("plugin-test.png", compilation)).resolves.toBe(
56 | true,
57 | );
58 | });
59 |
60 | it("should optimizes images and throws warnings on corrupted images using `plugin.severityError` option with `warning` value (by plugin)", async () => {
61 | const stats = await runWebpack({
62 | emitPluginOptions: {
63 | fileNames: ["test-corrupted.jpg", "plugin-test.png"],
64 | },
65 | entry: path.join(fixturesPath, "empty-entry.js"),
66 | imageminPluginOptions: {
67 | severityError: "warning",
68 | minimizer: {
69 | implementation: ImageMinimizerPlugin.imageminMinify,
70 | options: { plugins },
71 | },
72 | },
73 | });
74 | const { compilation } = stats;
75 | const { warnings, errors } = compilation;
76 |
77 | expect(warnings).toHaveLength(1);
78 | expect(errors).toHaveLength(0);
79 | expect(warnings[0].message).toMatch(
80 | /(Corrupt JPEG data|Command failed with EPIPE)/,
81 | );
82 |
83 | await expect(isOptimized("plugin-test.png", compilation)).resolves.toBe(
84 | true,
85 | );
86 | });
87 |
88 | it("should optimizes images and throws error on corrupted images when `plugin.severityError` option not specify (by plugin)", async () => {
89 | const stats = await runWebpack({
90 | emitPluginOptions: {
91 | fileNames: ["test-corrupted.jpg", "plugin-test.png"],
92 | },
93 | entry: path.join(fixturesPath, "empty-entry.js"),
94 | imageminPluginOptions: {
95 | minimizer: {
96 | implementation: ImageMinimizerPlugin.imageminMinify,
97 | options: { plugins },
98 | },
99 | },
100 | });
101 | const { compilation } = stats;
102 | const { warnings, errors } = compilation;
103 |
104 | expect(warnings).toHaveLength(0);
105 | expect(errors).toHaveLength(1);
106 | expect(errors[0].message).toMatch(
107 | /(Corrupt JPEG data|Command failed with EPIPE|Command failed with ENOTCONN)/,
108 | );
109 |
110 | await expect(isOptimized("plugin-test.png", compilation)).resolves.toBe(
111 | true,
112 | );
113 | });
114 |
115 | it("should optimizes images and throws errors on corrupted images using `plugin.severityError` option with `error` value (by loader)", async () => {
116 | const stats = await runWebpack({
117 | entry: path.join(fixturesPath, "loader-corrupted.js"),
118 | imageminPluginOptions: {
119 | severityError: "error",
120 | minimizer: {
121 | implementation: ImageMinimizerPlugin.imageminMinify,
122 | options: { plugins },
123 | },
124 | },
125 | });
126 | const { compilation } = stats;
127 | const { warnings, errors } = compilation;
128 |
129 | expect(warnings).toHaveLength(0);
130 | expect(errors).toHaveLength(2);
131 |
132 | // From loader
133 | expect(errors[0].message).toMatch(
134 | /(Corrupt JPEG data|Command failed with EPIPE)/,
135 | );
136 | // From plugin
137 | expect(errors[1].message).toMatch(
138 | /(Corrupt JPEG data|Command failed with EPIPE)/,
139 | );
140 |
141 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
142 | true,
143 | );
144 | });
145 |
146 | it("should throws errors on corrupted images when `plugin.severityError` option not specify (by loader)", async () => {
147 | const stats = await runWebpack({
148 | entry: path.join(fixturesPath, "loader-corrupted.js"),
149 | imageminPluginOptions: {
150 | minimizer: {
151 | implementation: ImageMinimizerPlugin.imageminMinify,
152 | options: { plugins },
153 | },
154 | },
155 | });
156 | const { compilation } = stats;
157 | const { warnings, errors } = compilation;
158 |
159 | expect(warnings).toHaveLength(0);
160 | expect(errors).toHaveLength(2);
161 |
162 | // From loader
163 | expect(errors[0].message).toMatch(
164 | /(Corrupt JPEG data|Command failed with EPIPE)/,
165 | );
166 | // From plugin
167 | expect(errors[1].message).toMatch(
168 | /(Corrupt JPEG data|Command failed with EPIPE)/,
169 | );
170 |
171 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
172 | true,
173 | );
174 | });
175 |
176 | it("should optimizes images and throws errors on corrupted images using `plugin.severityError` option with `off` value (by loader)", async () => {
177 | const stats = await runWebpack({
178 | entry: path.join(fixturesPath, "loader-corrupted.js"),
179 | imageminPluginOptions: {
180 | severityError: "off",
181 | minimizer: {
182 | implementation: ImageMinimizerPlugin.imageminMinify,
183 | options: { plugins },
184 | },
185 | },
186 | });
187 | const { compilation } = stats;
188 | const { warnings, errors } = compilation;
189 |
190 | expect(warnings).toHaveLength(0);
191 | expect(errors).toHaveLength(0);
192 |
193 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
194 | true,
195 | );
196 | });
197 | });
198 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export = ImageMinimizerPlugin;
2 | /**
3 | * @template T, [G=T]
4 | * @extends {WebpackPluginInstance}
5 | */
6 | declare class ImageMinimizerPlugin {
7 | /**
8 | * @param {PluginOptions=} options Plugin options.
9 | */
10 | constructor(options?: PluginOptions | undefined);
11 | /**
12 | * @private
13 | */
14 | private options;
15 | /**
16 | * @private
17 | * @param {Compiler} compiler compiler
18 | * @param {Compilation} compilation compilation
19 | * @param {Record} assets assets
20 | * @returns {Promise}
21 | */
22 | private optimize;
23 | /**
24 | * @private
25 | */
26 | private setupAll;
27 | /**
28 | * @private
29 | */
30 | private teardownAll;
31 | /**
32 | * @param {Compiler} compiler compiler
33 | */
34 | apply(compiler: Compiler): void;
35 | }
36 | declare namespace ImageMinimizerPlugin {
37 | export {
38 | loader,
39 | imageminNormalizeConfig,
40 | imageminMinify,
41 | imageminGenerate,
42 | squooshMinify,
43 | squooshGenerate,
44 | sharpMinify,
45 | sharpGenerate,
46 | svgoMinify,
47 | Schema,
48 | WebpackPluginInstance,
49 | Compiler,
50 | Compilation,
51 | Asset,
52 | AssetInfo,
53 | Source,
54 | Module,
55 | TemplatePath,
56 | PathData,
57 | ImageminMinifyFunction,
58 | SquooshMinifyFunction,
59 | Rule,
60 | Rules,
61 | FilterFn,
62 | IsFilenameProcessed,
63 | WorkerResult,
64 | Task,
65 | CustomOptions,
66 | InferDefaultType,
67 | BasicTransformerOptions,
68 | ResizeOptions,
69 | BasicTransformerImplementation,
70 | BasicTransformerHelpers,
71 | TransformerFunction,
72 | FilenameFn,
73 | Transformer,
74 | Minimizer,
75 | Generator,
76 | InternalWorkerOptions,
77 | InternalLoaderOptions,
78 | PluginOptions,
79 | };
80 | }
81 | declare var loader: string;
82 | import { imageminNormalizeConfig } from "./utils.js";
83 | import { imageminMinify } from "./utils.js";
84 | import { imageminGenerate } from "./utils.js";
85 | import { squooshMinify } from "./utils.js";
86 | import { squooshGenerate } from "./utils.js";
87 | import { sharpMinify } from "./utils.js";
88 | import { sharpGenerate } from "./utils.js";
89 | import { svgoMinify } from "./utils.js";
90 | type Schema = import("schema-utils").Schema;
91 | type WebpackPluginInstance = import("webpack").WebpackPluginInstance;
92 | type Compiler = import("webpack").Compiler;
93 | type Compilation = import("webpack").Compilation;
94 | type Asset = import("webpack").Asset;
95 | type AssetInfo = import("webpack").AssetInfo;
96 | type Source = import("webpack").sources.Source;
97 | type Module = import("webpack").Module;
98 | type TemplatePath = import("webpack").TemplatePath;
99 | type PathData = import("webpack").PathData;
100 | type ImageminMinifyFunction = typeof imageminMinify;
101 | type SquooshMinifyFunction = typeof squooshMinify;
102 | type Rule = RegExp | string;
103 | type Rules = Rule[] | Rule;
104 | type FilterFn = (source: Buffer, sourcePath: string) => boolean;
105 | type IsFilenameProcessed = typeof import("./worker").isFilenameProcessed;
106 | type WorkerResult = {
107 | /**
108 | * filename
109 | */
110 | filename: string;
111 | /**
112 | * data buffer
113 | */
114 | data: Buffer;
115 | /**
116 | * warnings
117 | */
118 | warnings: Array;
119 | /**
120 | * errors
121 | */
122 | errors: Array;
123 | /**
124 | * asset info
125 | */
126 | info: AssetInfo & {
127 | [worker.isFilenameProcessed]?: boolean;
128 | };
129 | };
130 | type Task = {
131 | /**
132 | * task name
133 | */
134 | name: string;
135 | /**
136 | * asset info
137 | */
138 | info: AssetInfo;
139 | /**
140 | * input source
141 | */
142 | inputSource: Source;
143 | /**
144 | * output
145 | */
146 | output:
147 | | (WorkerResult & {
148 | source?: Source;
149 | })
150 | | undefined;
151 | /**
152 | * cache item
153 | */
154 | cacheItem: ReturnType["getItemCache"]>;
155 | /**
156 | * transformer
157 | */
158 | transformer: Transformer | Transformer[];
159 | };
160 | type CustomOptions = {
161 | [key: string]: any;
162 | };
163 | type InferDefaultType = T extends infer U ? U : CustomOptions;
164 | type BasicTransformerOptions = InferDefaultType | undefined;
165 | type ResizeOptions = {
166 | /**
167 | * width
168 | */
169 | width?: number | undefined;
170 | /**
171 | * height
172 | */
173 | height?: number | undefined;
174 | /**
175 | * unit
176 | */
177 | unit?: ("px" | "percent") | undefined;
178 | /**
179 | * true when enabled, otherwise false
180 | */
181 | enabled?: boolean | undefined;
182 | };
183 | type BasicTransformerImplementation = (
184 | original: WorkerResult,
185 | options?: BasicTransformerOptions | undefined,
186 | ) => Promise;
187 | type BasicTransformerHelpers = {
188 | /**
189 | * setup function
190 | */
191 | setup?: (() => void) | undefined;
192 | /**
193 | * teardown function
194 | */
195 | teardown?: (() => void) | undefined;
196 | };
197 | type TransformerFunction = BasicTransformerImplementation &
198 | BasicTransformerHelpers;
199 | type FilenameFn = (
200 | pathData: PathData,
201 | assetInfo?: AssetInfo | undefined,
202 | ) => string;
203 | type Transformer = {
204 | /**
205 | * implementation
206 | */
207 | implementation: TransformerFunction;
208 | /**
209 | * options
210 | */
211 | options?: BasicTransformerOptions | undefined;
212 | /**
213 | * filter
214 | */
215 | filter?: FilterFn | undefined;
216 | /**
217 | * filename
218 | */
219 | filename?: (string | FilenameFn) | undefined;
220 | /**
221 | * preset
222 | */
223 | preset?: string | undefined;
224 | /**
225 | * type
226 | */
227 | type?: ("import" | "asset") | undefined;
228 | };
229 | type Minimizer = Omit, "preset" | "type">;
230 | type Generator = Transformer;
231 | type InternalWorkerOptions = {
232 | /**
233 | * filename
234 | */
235 | filename: string;
236 | /**
237 | * asset info
238 | */
239 | info?: AssetInfo | undefined;
240 | /**
241 | * input buffer
242 | */
243 | input: Buffer;
244 | /**
245 | * transformer
246 | */
247 | transformer: Transformer | Transformer[];
248 | /**
249 | * severity error setting
250 | */
251 | severityError?: string | undefined;
252 | /**
253 | * filename generator function
254 | */
255 | generateFilename: (filename: TemplatePath, data: PathData) => string;
256 | };
257 | type InternalLoaderOptions = import("./loader").LoaderOptions;
258 | type PluginOptions = {
259 | /**
260 | * test to match files against
261 | */
262 | test?: Rule | undefined;
263 | /**
264 | * files to include
265 | */
266 | include?: Rule | undefined;
267 | /**
268 | * files to exclude
269 | */
270 | exclude?: Rule | undefined;
271 | /**
272 | * allows to set the minimizer
273 | */
274 | minimizer?:
275 | | (T extends any[]
276 | ? { [P in keyof T]: Minimizer }
277 | : Minimizer | Minimizer[])
278 | | undefined;
279 | /**
280 | * allows to set the generator
281 | */
282 | generator?:
283 | | (G extends any[] ? { [P in keyof G]: Generator } : Generator[])
284 | | undefined;
285 | /**
286 | * automatically adding `image-loader`.
287 | */
288 | loader?: boolean | undefined;
289 | /**
290 | * maximum number of concurrency optimization processes in one time
291 | */
292 | concurrency?: number | undefined;
293 | /**
294 | * allows to choose how errors are displayed
295 | */
296 | severityError?: string | undefined;
297 | /**
298 | * allows to remove original assets, useful for converting to a `webp` and remove original assets
299 | */
300 | deleteOriginalAssets?: boolean | undefined;
301 | };
302 | import worker = require("./worker");
303 |
--------------------------------------------------------------------------------
/src/loader.js:
--------------------------------------------------------------------------------
1 | const path = require("node:path");
2 |
3 | const schema = require("./loader-options.json");
4 | const {
5 | ABSOLUTE_URL_REGEX,
6 | IMAGE_MINIMIZER_PLUGIN_INFO_MAPPINGS,
7 | WINDOWS_PATH_REGEX,
8 | } = require("./utils.js");
9 | const worker = require("./worker");
10 |
11 | /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
12 | /** @typedef {import("webpack").Compilation} Compilation */
13 | /** @typedef {import("./utils").WorkerResult} WorkerResult */
14 |
15 | /**
16 | * @template T
17 | * @typedef {import("./index").Minimizer} Minimizer
18 | */
19 |
20 | /**
21 | * @template T
22 | * @typedef {import("./index").Generator} Generator
23 | */
24 |
25 | /**
26 | * @template T
27 | * @typedef {object} LoaderOptions
28 | * @property {string=} severityError allows to choose how errors are displayed.
29 | * @property {Minimizer | Minimizer[]=} minimizer minimizer configuration
30 | * @property {Generator[]=} generator generator configuration
31 | */
32 |
33 | // Workaround - https://github.com/webpack/image-minimizer-webpack-plugin/issues/341
34 | /**
35 | * @template T
36 | * @param {import("webpack").LoaderContext>} loaderContext loader context
37 | * @param {WorkerResult} output worker result
38 | * @param {string} query query string
39 | */
40 | function changeResource(loaderContext, output, query) {
41 | loaderContext.resourcePath = path.join(
42 | loaderContext.rootContext,
43 | output.filename,
44 | );
45 | loaderContext.resourceQuery = query;
46 | }
47 |
48 | /**
49 | * @template T
50 | * @param {Minimizer[]} transformers transformers
51 | * @param {string | null} widthQuery width query
52 | * @param {string | null} heightQuery height query
53 | * @param {string | null} unitQuery unit query
54 | * @returns {Minimizer[]} processed transformers
55 | */
56 | function processSizeQuery(transformers, widthQuery, heightQuery, unitQuery) {
57 | return transformers.map((transformer) => {
58 | const minimizer = { ...transformer };
59 |
60 | const minimizerOptions = {
61 | .../** @type {{ options: import("./index").BasicTransformerOptions & { resize?: import("./index").ResizeOptions }}} */
62 | (minimizer).options,
63 | };
64 |
65 | minimizerOptions.resize = { ...minimizerOptions?.resize };
66 | minimizer.options = minimizerOptions;
67 |
68 | if (widthQuery === "auto") {
69 | delete minimizerOptions.resize.width;
70 | } else if (widthQuery) {
71 | const width = Number.parseInt(widthQuery, 10);
72 |
73 | if (Number.isFinite(width) && width > 0) {
74 | minimizerOptions.resize.width = width;
75 | }
76 | }
77 |
78 | if (heightQuery === "auto") {
79 | delete minimizerOptions.resize.height;
80 | } else if (heightQuery) {
81 | const height = Number.parseInt(heightQuery, 10);
82 |
83 | if (Number.isFinite(height) && height > 0) {
84 | minimizerOptions.resize.height = height;
85 | }
86 | }
87 |
88 | if (unitQuery === "px" || unitQuery === "percent") {
89 | minimizerOptions.resize.unit = unitQuery;
90 | }
91 |
92 | return minimizer;
93 | });
94 | }
95 |
96 | /**
97 | * @template T
98 | * @this {import("webpack").LoaderContext>}
99 | * @param {Buffer} content content
100 | * @returns {Promise} processed content
101 | */
102 | async function loader(content) {
103 | // Avoid optimize twice
104 | const imageMinimizerPluginInfo = this._module
105 | ? IMAGE_MINIMIZER_PLUGIN_INFO_MAPPINGS.get(this._module)
106 | : undefined;
107 |
108 | if (
109 | imageMinimizerPluginInfo?.minimized ||
110 | imageMinimizerPluginInfo?.generated
111 | ) {
112 | return content;
113 | }
114 |
115 | const options = this.getOptions(/** @type {Schema} */ (schema));
116 | const callback = this.async();
117 | const { generator, minimizer, severityError } = options;
118 |
119 | if (!minimizer && !generator) {
120 | callback(
121 | new Error(
122 | "Not configured 'minimizer' or 'generator' options, please setup them",
123 | ),
124 | );
125 |
126 | return;
127 | }
128 |
129 | let transformer = minimizer;
130 |
131 | const parsedQuery =
132 | this.resourceQuery.length > 0
133 | ? new URLSearchParams(this.resourceQuery)
134 | : null;
135 |
136 | if (parsedQuery) {
137 | const presetName = parsedQuery.get("as");
138 |
139 | if (presetName) {
140 | if (!generator) {
141 | callback(
142 | new Error(
143 | "Please specify the 'generator' option to use 'as' query param for generation purposes.",
144 | ),
145 | );
146 |
147 | return;
148 | }
149 |
150 | const presets = generator.filter((item) => item.preset === presetName);
151 |
152 | if (presets.length > 1) {
153 | callback(
154 | new Error(
155 | "Found several identical preset names, the 'preset' option should be unique",
156 | ),
157 | );
158 |
159 | return;
160 | }
161 |
162 | if (presets.length === 0) {
163 | callback(
164 | new Error(
165 | `Can't find '${presetName}' preset in the 'generator' option`,
166 | ),
167 | );
168 |
169 | return;
170 | }
171 |
172 | [transformer] = presets;
173 | }
174 | }
175 |
176 | if (!transformer) {
177 | callback(null, content);
178 |
179 | return;
180 | }
181 |
182 | if (parsedQuery) {
183 | const widthQuery = parsedQuery.get("width") ?? parsedQuery.get("w");
184 | const heightQuery = parsedQuery.get("height") ?? parsedQuery.get("h");
185 | const unitQuery = parsedQuery.get("unit") ?? parsedQuery.get("u");
186 |
187 | if (widthQuery || heightQuery || unitQuery) {
188 | if (Array.isArray(transformer)) {
189 | transformer = processSizeQuery(
190 | transformer,
191 | widthQuery,
192 | heightQuery,
193 | unitQuery,
194 | );
195 | } else {
196 | [transformer] = processSizeQuery(
197 | [transformer],
198 | widthQuery,
199 | heightQuery,
200 | unitQuery,
201 | );
202 | }
203 | }
204 | }
205 |
206 | const filename =
207 | ABSOLUTE_URL_REGEX.test(this.resourcePath) &&
208 | !WINDOWS_PATH_REGEX.test(this.resourcePath)
209 | ? this.resourcePath
210 | : path.relative(this.rootContext, this.resourcePath);
211 |
212 | const minifyOptions =
213 | /** @type {import("./index").InternalWorkerOptions} */ ({
214 | input: content,
215 | filename,
216 | severityError,
217 | transformer,
218 | generateFilename:
219 | /** @type {Compilation} */
220 | (this._compilation).getAssetPath.bind(this._compilation),
221 | });
222 |
223 | const output = await worker(minifyOptions);
224 |
225 | if (output.errors && output.errors.length > 0) {
226 | for (const error of output.errors) {
227 | this.emitError(error);
228 | }
229 |
230 | callback(null, content);
231 |
232 | return;
233 | }
234 |
235 | if (output.warnings && output.warnings.length > 0) {
236 | for (const warning of output.warnings) {
237 | this.emitWarning(warning);
238 | }
239 | }
240 |
241 | // Change content of the data URI after minimizer
242 | if (this._module?.resourceResolveData?.encodedContent) {
243 | const isBase64 = /^base64$/i.test(
244 | /** @type string */
245 | (this._module.resourceResolveData.encoding),
246 | );
247 |
248 | this._module.resourceResolveData.encodedContent = isBase64
249 | ? output.data.toString("base64")
250 | : encodeURIComponent(output.data.toString("utf8")).replaceAll(
251 | /[!'()*]/g,
252 | (character) =>
253 | `%${/** @type {number} */ (character.codePointAt(0)).toString(16)}`,
254 | );
255 | } else {
256 | let query = this.resourceQuery;
257 |
258 | if (parsedQuery) {
259 | // Remove query param from the bundle due we need that only for bundle purposes
260 | for (const key of ["as", "width", "w", "height", "h"]) {
261 | parsedQuery.delete(key);
262 | }
263 |
264 | query = parsedQuery.toString();
265 | query = query.length > 0 ? `?${query}` : "";
266 | }
267 |
268 | // Old approach for `file-loader` and other old loaders
269 | changeResource(this, output, query);
270 |
271 | // Change name of assets modules after generator
272 | if (this._module && !this._module.matchResource) {
273 | this._module.matchResource = `${output.filename}${query}`;
274 | }
275 | }
276 |
277 | if (this._module) {
278 | IMAGE_MINIMIZER_PLUGIN_INFO_MAPPINGS.set(this._module, output.info);
279 | }
280 |
281 | callback(null, output.data);
282 | }
283 |
284 | loader.raw = true;
285 |
286 | module.exports = loader;
287 |
--------------------------------------------------------------------------------
/test/__snapshots__/validate-plugin-options.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`validate plugin options should work 1`] = `
4 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
5 | - options.test should be one of these:
6 | [RegExp | non-empty string, ...] | RegExp | non-empty string
7 | -> Filtering rules.
8 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#test
9 | Details:
10 | * options.test should be an array:
11 | [RegExp | non-empty string, ...]
12 | * options.test should be one of these:
13 | RegExp | non-empty string
14 | -> Filtering rule as regex or string.
15 | Details:
16 | * options.test should be an instance of RegExp.
17 | * options.test should be a non-empty string."
18 | `;
19 |
20 | exports[`validate plugin options should work 2`] = `
21 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
22 | - options.test should be one of these:
23 | [RegExp | non-empty string, ...] | RegExp | non-empty string
24 | -> Filtering rules.
25 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#test
26 | Details:
27 | * options.test[0] should be one of these:
28 | RegExp | non-empty string
29 | -> Filtering rule as regex or string.
30 | Details:
31 | * options.test[0] should be an instance of RegExp.
32 | * options.test[0] should be a non-empty string."
33 | `;
34 |
35 | exports[`validate plugin options should work 3`] = `
36 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
37 | - options.include should be one of these:
38 | [RegExp | non-empty string, ...] | RegExp | non-empty string
39 | -> Filtering rules.
40 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#include
41 | Details:
42 | * options.include should be an array:
43 | [RegExp | non-empty string, ...]
44 | * options.include should be one of these:
45 | RegExp | non-empty string
46 | -> Filtering rule as regex or string.
47 | Details:
48 | * options.include should be an instance of RegExp.
49 | * options.include should be a non-empty string."
50 | `;
51 |
52 | exports[`validate plugin options should work 4`] = `
53 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
54 | - options.include should be one of these:
55 | [RegExp | non-empty string, ...] | RegExp | non-empty string
56 | -> Filtering rules.
57 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#include
58 | Details:
59 | * options.include[0] should be one of these:
60 | RegExp | non-empty string
61 | -> Filtering rule as regex or string.
62 | Details:
63 | * options.include[0] should be an instance of RegExp.
64 | * options.include[0] should be a non-empty string."
65 | `;
66 |
67 | exports[`validate plugin options should work 5`] = `
68 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
69 | - options.exclude should be one of these:
70 | [RegExp | non-empty string, ...] | RegExp | non-empty string
71 | -> Filtering rules.
72 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#exclude
73 | Details:
74 | * options.exclude should be an array:
75 | [RegExp | non-empty string, ...]
76 | * options.exclude should be one of these:
77 | RegExp | non-empty string
78 | -> Filtering rule as regex or string.
79 | Details:
80 | * options.exclude should be an instance of RegExp.
81 | * options.exclude should be a non-empty string."
82 | `;
83 |
84 | exports[`validate plugin options should work 6`] = `
85 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
86 | - options.exclude should be one of these:
87 | [RegExp | non-empty string, ...] | RegExp | non-empty string
88 | -> Filtering rules.
89 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#exclude
90 | Details:
91 | * options.exclude[0] should be one of these:
92 | RegExp | non-empty string
93 | -> Filtering rule as regex or string.
94 | Details:
95 | * options.exclude[0] should be an instance of RegExp.
96 | * options.exclude[0] should be a non-empty string."
97 | `;
98 |
99 | exports[`validate plugin options should work 7`] = `
100 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
101 | - options.severityError should be one of these:
102 | "off" | "warning" | "error"
103 | -> Allows to choose how errors are displayed.
104 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
105 | `;
106 |
107 | exports[`validate plugin options should work 8`] = `
108 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
109 | - options.severityError should be one of these:
110 | "off" | "warning" | "error"
111 | -> Allows to choose how errors are displayed.
112 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
113 | `;
114 |
115 | exports[`validate plugin options should work 9`] = `
116 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
117 | - options.severityError should be one of these:
118 | "off" | "warning" | "error"
119 | -> Allows to choose how errors are displayed.
120 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
121 | `;
122 |
123 | exports[`validate plugin options should work 10`] = `
124 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
125 | - options.deleteOriginalAssets should be a boolean.
126 | -> Allows to remove original assets after minimization.
127 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#deleteoriginalassets"
128 | `;
129 |
130 | exports[`validate plugin options should work 11`] = `
131 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
132 | - options.minimizer should be one of these:
133 | [object { implementation?, filter?, filename?, options? }, ...] (should not have fewer than 1 item) | object { implementation?, filter?, filename?, options? }
134 | -> Allows you to setup the minimizer function and options.
135 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
136 | Details:
137 | * options.minimizer should be an array:
138 | [object { implementation?, filter?, filename?, options? }, ...] (should not have fewer than 1 item)
139 | * options.minimizer should be an object:
140 | object { implementation?, filter?, filename?, options? }"
141 | `;
142 |
143 | exports[`validate plugin options should work 12`] = `
144 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
145 | - options.minimizer.filter should be an instance of function.
146 | -> Allows filtering of images."
147 | `;
148 |
149 | exports[`validate plugin options should work 13`] = `
150 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
151 | - options.minimizer[0].filter should be an instance of function.
152 | -> Allows filtering of images."
153 | `;
154 |
155 | exports[`validate plugin options should work 14`] = `
156 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
157 | - options.minimizer should be one of these:
158 | [object { implementation?, filter?, filename?, options? }, ...] (should not have fewer than 1 item) | object { implementation?, filter?, filename?, options? }
159 | -> Allows you to setup the minimizer function and options.
160 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
161 | Details:
162 | * options.minimizer.filename should be one of these:
163 | non-empty string | function
164 | -> Allows to set the filename for the minimized asset.
165 | Details:
166 | * options.minimizer.filename should be a non-empty string.
167 | * options.minimizer.filename should be an instance of function."
168 | `;
169 |
170 | exports[`validate plugin options should work 15`] = `
171 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
172 | - options.minimizer should be one of these:
173 | [object { implementation?, filter?, filename?, options? }, ...] (should not have fewer than 1 item) | object { implementation?, filter?, filename?, options? }
174 | -> Allows you to setup the minimizer function and options.
175 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
176 | Details:
177 | * options.minimizer.filename should be one of these:
178 | non-empty string | function
179 | -> Allows to set the filename for the minimized asset.
180 | Details:
181 | * options.minimizer.filename should be a non-empty string.
182 | * options.minimizer.filename should be an instance of function."
183 | `;
184 |
185 | exports[`validate plugin options should work 16`] = `
186 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
187 | - options.generator should be an array:
188 | [object { implementation, type?, preset?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
189 | -> Allows you to setup the generator function and options.
190 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#generator"
191 | `;
192 |
193 | exports[`validate plugin options should work 17`] = `
194 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
195 | - options.generator[0].filename should be one of these:
196 | non-empty string | function
197 | -> Allows to set the filename for the minimized asset.
198 | Details:
199 | * options.generator[0].filename should be a non-empty string.
200 | * options.generator[0].filename should be an instance of function."
201 | `;
202 |
203 | exports[`validate plugin options should work 18`] = `
204 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
205 | - options.generator[0].filter should be an instance of function.
206 | -> Allows filtering of images."
207 | `;
208 |
209 | exports[`validate plugin options should work 19`] = `
210 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
211 | - options.generator[0].type should be one of these:
212 | "import" | "asset"
213 | -> Type of generation
214 | - options.generator[0].filename should be one of these:
215 | non-empty string | function
216 | -> Allows to set the filename for the minimized asset.
217 | Details:
218 | * options.generator[0].filename should be a non-empty string.
219 | * options.generator[0].filename should be an instance of function."
220 | `;
221 |
222 | exports[`validate plugin options should work 20`] = `
223 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
224 | - options.concurrency should be a number.
225 | -> Number of concurrency optimization processes in one time.
226 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#concurrency"
227 | `;
228 |
229 | exports[`validate plugin options should work 21`] = `
230 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
231 | - options.concurrency should be a number.
232 | -> Number of concurrency optimization processes in one time.
233 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#concurrency"
234 | `;
235 |
236 | exports[`validate plugin options should work 22`] = `
237 | "Invalid options object. Image Minimizer Plugin has been initialized using an options object that does not match the API schema.
238 | - options.loader should be a boolean.
239 | -> Automatically adding \`imagemin-loader\` (require for minification images using in \`url-loader\`, \`svg-url-loader\` or other).
240 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#loader"
241 | `;
242 |
--------------------------------------------------------------------------------
/test/helpers.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | import path from "node:path";
3 |
4 | import MiniCssExtractPlugin from "mini-css-extract-plugin";
5 | import webpack from "webpack";
6 |
7 | import ImageMinimizerPlugin from "../src/index";
8 |
9 | import EmitPlugin from "./fixtures/EmitWepbackPlugin";
10 |
11 | const plugins = ["gifsicle", "mozjpeg", "pngquant", "svgo"];
12 |
13 | const fixturesPath = path.join(__dirname, "./fixtures");
14 |
15 | /**
16 | * @param {import("webpack").Compiler} compiler The webpack compiler
17 | * @returns {Promise} The compilation stats
18 | */
19 | function compile(compiler) {
20 | return new Promise((resolve, reject) => {
21 | compiler.run((err, stats) => {
22 | if (err) {
23 | return reject(err);
24 | }
25 |
26 | return resolve(stats);
27 | });
28 | });
29 | }
30 |
31 | /**
32 | * @param {unknown} maybeOptions The webpack options
33 | * @param {boolean} getCompiler Whether to get compiler
34 | * @returns {Promise} The compilation result
35 | */
36 | async function runWebpack(maybeOptions, getCompiler = false) {
37 | const maybeMultiCompiler = Array.isArray(maybeOptions)
38 | ? maybeOptions
39 | : [maybeOptions];
40 |
41 | const configs = [];
42 | const CopyPlugin = (await import("copy-webpack-plugin")).default;
43 | const { temporaryDirectory } = await import("tempy");
44 |
45 | for (const options of maybeMultiCompiler) {
46 | const config = {
47 | externals: options.externals,
48 | experiments: options.experiments,
49 | devtool: false,
50 | bail: options.bail,
51 | context: fixturesPath,
52 | entry: options.entry || path.join(fixturesPath, "./loader.js"),
53 | mode: options.mode || "development",
54 | optimization: options.optimization,
55 | cache: options.cache,
56 | module: {
57 | rules: [
58 | ...(!options.fileLoaderOff
59 | ? [
60 | {
61 | test: options.test || /\.(jpe?g|png|gif|svg|webp)$/i,
62 | use: [
63 | {
64 | loader: "file-loader",
65 | options: {
66 | name: options.name || "[path][name].[ext]",
67 | },
68 | },
69 | ],
70 | },
71 | ]
72 | : []),
73 | ...(options.MCEP
74 | ? [
75 | {
76 | test: /\.css$/,
77 | use: [
78 | {
79 | loader: MiniCssExtractPlugin.loader,
80 | },
81 | "css-loader",
82 | ],
83 | },
84 | ]
85 | : []),
86 | ...(options.childPlugin
87 | ? [
88 | {
89 | test: /child-compilation\.js$/,
90 | loader: path.resolve(
91 | __dirname,
92 | "./fixtures/emit-asset-in-child-compilation-loader.js",
93 | ),
94 | },
95 | ]
96 | : []),
97 | ...(options.emitAssetPlugin
98 | ? [
99 | {
100 | test: /simple-emit\.js$/,
101 | loader: path.resolve(
102 | __dirname,
103 | "./fixtures/emitAssetLoader.js",
104 | ),
105 | },
106 | ]
107 | : []),
108 | ...(options.assetResource
109 | ? [
110 | {
111 | test: /\.(jpe?g|png|gif|svg)$/i,
112 | type: "asset/resource",
113 | },
114 | ]
115 | : []),
116 | ...(options.assetInline
117 | ? [
118 | {
119 | test: /\.(jpe?g|png|gif|svg)$/i,
120 | type: "asset/inline",
121 | },
122 | ]
123 | : []),
124 | ],
125 | },
126 | output: {
127 | publicPath: "",
128 | filename: "bundle.js",
129 | pathinfo: false,
130 | assetModuleFilename: options.name || "[name][ext]",
131 | path:
132 | options.output && options.output.path
133 | ? options.output.path
134 | : temporaryDirectory(),
135 | },
136 | plugins: [],
137 | };
138 |
139 | if (options.experiments) {
140 | config.experiments = options.experiments;
141 | }
142 |
143 | if (options.output && options.output.assetModuleFilename) {
144 | config.output.assetModuleFilename = options.output.assetModuleFilename;
145 | }
146 |
147 | if (options.imageminLoaderOptions) {
148 | if (config.module.rules[0].use) {
149 | config.module.rules[0].use = [
150 | ...config.module.rules[0].use,
151 | {
152 | loader: ImageMinimizerPlugin.loader,
153 | options: options.imageminLoaderOptions,
154 | },
155 | ];
156 | } else {
157 | config.module.rules.push({
158 | test: /\.(jpe?g|png|gif|svg)$/i,
159 | loader: ImageMinimizerPlugin.loader,
160 | options: options.imageminLoaderOptions,
161 | });
162 | }
163 | }
164 |
165 | if (options.emitPlugin || options.emitPluginOptions) {
166 | config.plugins = [
167 | ...config.plugins,
168 | new EmitPlugin(options.emitPluginOptions),
169 | ];
170 | }
171 |
172 | if (options.imageminPlugin || options.imageminPluginOptions) {
173 | const imageminPluginsOptions =
174 | Array.isArray(options.imageminPlugin) ||
175 | Array.isArray(options.imageminPluginOptions)
176 | ? options.imageminPlugin || options.imageminPluginOptions
177 | : [options.imageminPlugin || options.imageminPluginOptions];
178 |
179 | for (const imageminPluginOptions of imageminPluginsOptions) {
180 | const ImageMinimizerPluginCreated = new ImageMinimizerPlugin(
181 | typeof imageminPluginOptions === "boolean"
182 | ? {
183 | minimizerOptions: {
184 | plugins,
185 | },
186 | }
187 | : imageminPluginOptions,
188 | );
189 |
190 | if (options.asMinimizer) {
191 | if (!config.optimization) {
192 | config.optimization = {};
193 | }
194 |
195 | config.optimization.minimize = true;
196 | config.optimization.minimizer = [ImageMinimizerPluginCreated];
197 | } else {
198 | config.plugins = [...config.plugins, ImageMinimizerPluginCreated];
199 | }
200 | }
201 | }
202 |
203 | if (options.MCEP) {
204 | config.plugins = [
205 | ...config.plugins,
206 | new MiniCssExtractPlugin({
207 | // Options similar to the same options in webpackOptions.output
208 | // both options are optional
209 | filename: "[name].css",
210 | chunkFilename: "[id].css",
211 | }),
212 | ];
213 | }
214 |
215 | if (options.copyPlugin) {
216 | config.plugins = [
217 | ...config.plugins,
218 | new CopyPlugin({
219 | patterns: [{ from: "plugin-test.jpg" }],
220 | }),
221 | ];
222 | }
223 |
224 | if (options.EmitNewAssetPlugin) {
225 | config.plugins = [
226 | ...config.plugins,
227 | // eslint-disable-next-line no-use-before-define
228 | new EmitNewAssetPlugin({
229 | name: "newImg.png",
230 | }),
231 | ];
232 | }
233 |
234 | configs.push(config);
235 | }
236 |
237 | if (getCompiler) {
238 | return webpack(configs.length === 1 ? configs[0] : configs);
239 | }
240 |
241 | return new Promise((resolve, reject) => {
242 | webpack(configs.length === 1 ? configs[0] : configs, (err, stats) => {
243 | if (err) {
244 | reject(err);
245 | return;
246 | }
247 |
248 | resolve(stats);
249 | });
250 | });
251 | }
252 |
253 | /**
254 | * @param {string | string[]} originalPath The original path
255 | * @param {import("webpack").Compilation} compilation The compilation
256 | * @returns {Promise} Whether the asset is optimized
257 | */
258 | async function isOptimized(originalPath, compilation) {
259 | const { assets } = compilation;
260 | let name = originalPath;
261 | let realName = originalPath;
262 |
263 | if (Array.isArray(originalPath)) {
264 | [name, realName] = originalPath;
265 | }
266 |
267 | const source = assets[name];
268 |
269 | if (!source) {
270 | throw new Error("Can't find asset");
271 | }
272 |
273 | const { path: outputPath } = compilation.options.output;
274 | const pathToOriginal = path.join(fixturesPath, realName);
275 | const pathToEmitted = path.join(outputPath, name);
276 |
277 | const imagemin = (await import("imagemin")).default;
278 | const imageminSvgo = (await import("imagemin-svgo")).default;
279 | const imageminGifsicle = (await import("imagemin-gifsicle")).default;
280 | const imageminMozjpeg = (await import("imagemin-mozjpeg")).default;
281 | const imageminPngquant = (await import("imagemin-pngquant")).default;
282 | const data = await fs.promises.readFile(pathToOriginal);
283 | const optimizedBuffer = Buffer.from(
284 | await imagemin.buffer(data, {
285 | plugins: [
286 | imageminGifsicle(),
287 | imageminMozjpeg(),
288 | imageminPngquant(),
289 | imageminSvgo(),
290 | ],
291 | }),
292 | );
293 | const generatedBuffer = await fs.promises.readFile(pathToEmitted);
294 |
295 | return optimizedBuffer.equals(generatedBuffer);
296 | }
297 |
298 | /**
299 | * @param {string} id The module id
300 | * @param {import("webpack").Module[]} modules The modules
301 | * @returns {boolean} Whether the module has the loader
302 | */
303 | function hasLoader(id, modules) {
304 | return [...modules].some((module) => {
305 | if (!module.id.endsWith(id)) {
306 | return false;
307 | }
308 |
309 | const { loaders } = module;
310 |
311 | return loaders.find(
312 | (loader) => loader.loader === ImageMinimizerPlugin.loader,
313 | );
314 | });
315 | }
316 |
317 | /**
318 | * @param {string} asset The asset name
319 | * @param {import("webpack").Compiler} compiler The compiler
320 | * @param {import("webpack").Stats} stats The stats
321 | * @returns {string} The asset content
322 | */
323 | function readAsset(asset, compiler, stats) {
324 | const usedFs = compiler.outputFileSystem;
325 | const outputPath = stats.compilation.outputOptions.path;
326 |
327 | let data = "";
328 | let targetFile = asset;
329 |
330 | const queryStringIdx = targetFile.indexOf("?");
331 |
332 | if (queryStringIdx >= 0) {
333 | targetFile = targetFile.slice(0, Math.max(0, queryStringIdx));
334 | }
335 |
336 | try {
337 | data = usedFs.readFileSync(path.join(outputPath, targetFile));
338 | } catch (error) {
339 | data = error.toString();
340 | }
341 |
342 | return data;
343 | }
344 |
345 | /**
346 | * @param {string} string The string to normalize
347 | * @returns {string} The normalized path
348 | */
349 | function normalizePath(string) {
350 | const isWin = process.platform === "win32";
351 |
352 | if (isWin) {
353 | return string.replaceAll("\\", "/");
354 | }
355 |
356 | return string;
357 | }
358 |
359 | /**
360 | * @param {string} dirPath The directory path
361 | * @returns {void}
362 | */
363 | function clearDirectory(dirPath) {
364 | let files;
365 |
366 | try {
367 | files = fs.readdirSync(dirPath);
368 | } catch {
369 | return;
370 | }
371 |
372 | if (files.length > 0) {
373 | for (let i = 0; i < files.length; i++) {
374 | const filePath = `${dirPath}/${files[i]}`;
375 | if (fs.statSync(filePath).isFile()) {
376 | fs.unlinkSync(filePath);
377 | } else {
378 | clearDirectory(filePath);
379 | }
380 | }
381 | }
382 |
383 | fs.rmdirSync(dirPath);
384 | }
385 |
386 | /**
387 | * @param {boolean | () => boolean} predicate The predicate condition
388 | * @returns {import("@jest/globals").it | import("@jest/globals").xit} The test function
389 | */
390 | function ifit(predicate) {
391 | const cond = typeof predicate === "function" ? predicate() : predicate;
392 | /* global it */
393 | return cond ? it : it.skip;
394 | }
395 |
396 | /**
397 | * @returns {boolean} Whether squoosh tests should be run
398 | */
399 | function needSquooshTest() {
400 | const needTest = typeof process.env.SQUOOSH_TEST !== "undefined";
401 |
402 | // Disable tests for all and Nodejs > 16
403 | // see: https://github.com/webpack/image-minimizer-webpack-plugin/pull/345
404 | return needTest;
405 | }
406 |
407 | export default class EmitNewAssetPlugin {
408 | constructor(options = {}) {
409 | this.options = options;
410 | }
411 |
412 | apply(compiler) {
413 | const pluginName = this.constructor.name;
414 |
415 | const { RawSource } = compiler.webpack.sources;
416 |
417 | compiler.hooks.compilation.tap(pluginName, (compilation) => {
418 | compilation.hooks.processAssets.tap(
419 | {
420 | name: pluginName,
421 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT,
422 | },
423 | () => {
424 | const file = fs.readFileSync(
425 | path.resolve(__dirname, "fixtures", "newImg.png"),
426 | );
427 |
428 | compilation.emitAsset(this.options.name, new RawSource(file));
429 | },
430 | );
431 | });
432 | }
433 | }
434 |
435 | export {
436 | EmitNewAssetPlugin,
437 | clearDirectory,
438 | compile,
439 | fixturesPath,
440 | hasLoader,
441 | ifit,
442 | isOptimized,
443 | needSquooshTest,
444 | normalizePath,
445 | plugins,
446 | readAsset,
447 | runWebpack,
448 | };
449 |
--------------------------------------------------------------------------------
/test/__snapshots__/validate-loader-options.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`validate loader options should throw an error on the "generator" option with "[]" value 1`] = `
4 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
5 | - options.generator should be a non-empty array.
6 | -> Allows you to setup the generator function and options.
7 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#generator"
8 | `;
9 |
10 | exports[`validate loader options should throw an error on the "generator" option with "[{"preset":"webp","filename":true}]" value 1`] = `
11 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
12 | - options.generator[0].filename should be one of these:
13 | non-empty string | function
14 | -> Allows to set the filename for the minimized asset.
15 | Details:
16 | * options.generator[0].filename should be a non-empty string.
17 | * options.generator[0].filename should be an instance of function."
18 | `;
19 |
20 | exports[`validate loader options should throw an error on the "generator" option with "[{"preset":"webp","filter":true}]" value 1`] = `
21 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
22 | - options.generator[0].filter should be an instance of function.
23 | -> Allows filtering of images."
24 | `;
25 |
26 | exports[`validate loader options should throw an error on the "generator" option with "[{"preset":"webp"}]" value 1`] = `
27 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
28 | - options.generator[0] misses the property 'implementation'. Should be:
29 | function
30 | -> Implementation of the generator function."
31 | `;
32 |
33 | exports[`validate loader options should throw an error on the "generator" option with "1" value 1`] = `
34 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
35 | - options.generator should be an array:
36 | [object { implementation, preset, type?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
37 | -> Allows you to setup the generator function and options.
38 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#generator"
39 | `;
40 |
41 | exports[`validate loader options should throw an error on the "generator" option with "false" value 1`] = `
42 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
43 | - options.generator should be an array:
44 | [object { implementation, preset, type?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
45 | -> Allows you to setup the generator function and options.
46 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#generator"
47 | `;
48 |
49 | exports[`validate loader options should throw an error on the "generator" option with "null" value 1`] = `
50 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
51 | - options.generator should be an array:
52 | [object { implementation, preset, type?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
53 | -> Allows you to setup the generator function and options.
54 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#generator"
55 | `;
56 |
57 | exports[`validate loader options should throw an error on the "generator" option with "true" value 1`] = `
58 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
59 | - options.generator should be an array:
60 | [object { implementation, preset, type?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
61 | -> Allows you to setup the generator function and options.
62 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#generator"
63 | `;
64 |
65 | exports[`validate loader options should throw an error on the "minimizer" option with "[]" value 1`] = `
66 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
67 | - options.minimizer should be a non-empty array."
68 | `;
69 |
70 | exports[`validate loader options should throw an error on the "minimizer" option with "{"filename":true}" value 1`] = `
71 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
72 | - options.minimizer should be one of these:
73 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item) | object { implementation?, options?, filter?, filename? }
74 | -> Allows you to setup the minimizer function and options.
75 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
76 | Details:
77 | * options.minimizer.filename should be one of these:
78 | non-empty string | function
79 | -> Allows to set the filename for the minimized asset.
80 | Details:
81 | * options.minimizer.filename should be a non-empty string.
82 | * options.minimizer.filename should be an instance of function."
83 | `;
84 |
85 | exports[`validate loader options should throw an error on the "minimizer" option with "{"filter":true}" value 1`] = `
86 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
87 | - options.minimizer.filter should be an instance of function.
88 | -> Allows filtering of images."
89 | `;
90 |
91 | exports[`validate loader options should throw an error on the "minimizer" option with "1" value 1`] = `
92 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
93 | - options.minimizer should be one of these:
94 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item) | object { implementation?, options?, filter?, filename? }
95 | -> Allows you to setup the minimizer function and options.
96 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
97 | Details:
98 | * options.minimizer should be an array:
99 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
100 | * options.minimizer should be an object:
101 | object { implementation?, options?, filter?, filename? }"
102 | `;
103 |
104 | exports[`validate loader options should throw an error on the "minimizer" option with "false" value 1`] = `
105 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
106 | - options.minimizer should be one of these:
107 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item) | object { implementation?, options?, filter?, filename? }
108 | -> Allows you to setup the minimizer function and options.
109 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
110 | Details:
111 | * options.minimizer should be an array:
112 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
113 | * options.minimizer should be an object:
114 | object { implementation?, options?, filter?, filename? }"
115 | `;
116 |
117 | exports[`validate loader options should throw an error on the "minimizer" option with "null" value 1`] = `
118 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
119 | - options.minimizer should be one of these:
120 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item) | object { implementation?, options?, filter?, filename? }
121 | -> Allows you to setup the minimizer function and options.
122 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
123 | Details:
124 | * options.minimizer should be an array:
125 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
126 | * options.minimizer should be an object:
127 | object { implementation?, options?, filter?, filename? }"
128 | `;
129 |
130 | exports[`validate loader options should throw an error on the "minimizer" option with "true" value 1`] = `
131 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
132 | - options.minimizer should be one of these:
133 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item) | object { implementation?, options?, filter?, filename? }
134 | -> Allows you to setup the minimizer function and options.
135 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#minimizer
136 | Details:
137 | * options.minimizer should be an array:
138 | [object { implementation?, options?, filter?, filename? }, ...] (should not have fewer than 1 item)
139 | * options.minimizer should be an object:
140 | object { implementation?, options?, filter?, filename? }"
141 | `;
142 |
143 | exports[`validate loader options should throw an error on the "severityError" option with "() => {}" value 1`] = `
144 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
145 | - options.severityError should be one of these:
146 | "off" | "warning" | "error"
147 | -> Allows to choose how errors are displayed.
148 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
149 | `;
150 |
151 | exports[`validate loader options should throw an error on the "severityError" option with "[]" value 1`] = `
152 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
153 | - options.severityError should be one of these:
154 | "off" | "warning" | "error"
155 | -> Allows to choose how errors are displayed.
156 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
157 | `;
158 |
159 | exports[`validate loader options should throw an error on the "severityError" option with "{}" value 1`] = `
160 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
161 | - options.severityError should be one of these:
162 | "off" | "warning" | "error"
163 | -> Allows to choose how errors are displayed.
164 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
165 | `;
166 |
167 | exports[`validate loader options should throw an error on the "severityError" option with "false" value 1`] = `
168 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
169 | - options.severityError should be one of these:
170 | "off" | "warning" | "error"
171 | -> Allows to choose how errors are displayed.
172 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
173 | `;
174 |
175 | exports[`validate loader options should throw an error on the "severityError" option with "true" value 1`] = `
176 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
177 | - options.severityError should be one of these:
178 | "off" | "warning" | "error"
179 | -> Allows to choose how errors are displayed.
180 | -> Read more at https://github.com/webpack/image-minimizer-webpack-plugin#severityerror"
181 | `;
182 |
183 | exports[`validate loader options should throw an error on the "unknown" option with "/test/" value 1`] = `
184 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
185 | - options has an unknown property 'unknown'. These properties are valid:
186 | object { minimizer?, generator?, severityError? }"
187 | `;
188 |
189 | exports[`validate loader options should throw an error on the "unknown" option with "[]" value 1`] = `
190 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
191 | - options has an unknown property 'unknown'. These properties are valid:
192 | object { minimizer?, generator?, severityError? }"
193 | `;
194 |
195 | exports[`validate loader options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
196 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
197 | - options has an unknown property 'unknown'. These properties are valid:
198 | object { minimizer?, generator?, severityError? }"
199 | `;
200 |
201 | exports[`validate loader options should throw an error on the "unknown" option with "{}" value 1`] = `
202 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
203 | - options has an unknown property 'unknown'. These properties are valid:
204 | object { minimizer?, generator?, severityError? }"
205 | `;
206 |
207 | exports[`validate loader options should throw an error on the "unknown" option with "1" value 1`] = `
208 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
209 | - options has an unknown property 'unknown'. These properties are valid:
210 | object { minimizer?, generator?, severityError? }"
211 | `;
212 |
213 | exports[`validate loader options should throw an error on the "unknown" option with "false" value 1`] = `
214 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
215 | - options has an unknown property 'unknown'. These properties are valid:
216 | object { minimizer?, generator?, severityError? }"
217 | `;
218 |
219 | exports[`validate loader options should throw an error on the "unknown" option with "test" value 1`] = `
220 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
221 | - options has an unknown property 'unknown'. These properties are valid:
222 | object { minimizer?, generator?, severityError? }"
223 | `;
224 |
225 | exports[`validate loader options should throw an error on the "unknown" option with "true" value 1`] = `
226 | "Invalid options object. Image Minimizer Plugin Loader has been initialized using an options object that does not match the API schema.
227 | - options has an unknown property 'unknown'. These properties are valid:
228 | object { minimizer?, generator?, severityError? }"
229 | `;
230 |
--------------------------------------------------------------------------------
/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 | ### [4.1.4](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.1.3...v4.1.4) (2025-08-14)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * concurrency default value to avoid overload CPU ([#479](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/479)) ([c6f0ea3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/c6f0ea3338415519c31efb04515b34fb237ef3d4))
11 | * types ([#477](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/477)) ([3b61bc8](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/3b61bc8e3e061ebaa4de1211cf3abb45eb453064))
12 |
13 | ### [4.1.3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.1.2...v4.1.3) (2024-12-18)
14 |
15 |
16 | ### Bug Fixes
17 |
18 | * crash when using filesystem cache ([#461](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/461)) ([383d3d3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/383d3d3262b9bc44dcd7af527c8b82da4ecbd4aa))
19 |
20 | ### [4.1.2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.1.1...v4.1.2) (2024-12-18)
21 |
22 |
23 | ### Bug Fixes
24 |
25 | * crash when using filesystem cache ([#460](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/460)) ([7627f0e](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/7627f0ea2142440ad8c8f29fbbd2649fbf20b382))
26 |
27 | ### [4.1.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.1.0...v4.1.1) (2024-11-19)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * better way to avoid optimize twice ([#457](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/457)) ([1b2d40e](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/1b2d40ed143a6c3ed1ef9db188dd48655de2e97b))
33 |
34 | ## [4.1.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.0.2...v4.1.0) (2024-07-26)
35 |
36 |
37 | ### Features
38 |
39 | * add unit for percentage resize with sharp ([a83f491](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/a83f4919bec28804fe3e8a03a453a30d3537aff6))
40 |
41 | ### [4.0.2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.0.1...v4.0.2) (2024-06-04)
42 |
43 |
44 | ### Bug Fixes
45 |
46 | * compatibility with `imagemin@9` ([#444](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/444)) ([859c346](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/859c346f35b28f230c627409cd91be0dd8bbf72f))
47 |
48 | ### [4.0.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v4.0.0...v4.0.1) (2024-05-29)
49 |
50 |
51 | ### Bug Fixes
52 |
53 | * pass the original image path to svgo ([#441](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/441)) ([d5522a6](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/d5522a6326b52184b663b0c02035ce9464144307))
54 |
55 | ## [4.0.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.8.3...v4.0.0) (2024-01-16)
56 |
57 |
58 | ### ⚠ BREAKING CHANGES
59 |
60 | * minimum supported Node.js version is `18.12.0` ([d3f2531](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/d3f2531241c04e6115dfb7bab3baef1e04da3410))
61 |
62 | ### [3.8.3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.8.2...v3.8.3) (2023-06-17)
63 |
64 |
65 | ### Bug Fixes
66 |
67 | * lazy loading deps ([#411](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/411)) ([93412bb](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/93412bbed4ff96015d884e537ea81ceea5c285f9))
68 |
69 | ### [3.8.2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.8.1...v3.8.2) (2023-03-10)
70 |
71 |
72 | ### Bug Fixes
73 |
74 | * support for generating images from `svg` (`sharp`) ([5076734](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/5076734ef843df125de765e8eb02ecf987ab839c))
75 |
76 | ### [3.8.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.8.0...v3.8.1) (2022-11-13)
77 |
78 |
79 | ### Bug Fixes
80 |
81 | * fix some possible issues with file paths ([#377](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/377)) ([e40308a](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/e40308a8b6b83456aa9a15fbb572e6267b7b1763))
82 |
83 | ## [3.8.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.7.0...v3.8.0) (2022-11-01)
84 |
85 |
86 | ### Features
87 |
88 | * add `svgo` implementation ([#369](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/369)) ([0701188](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/07011881677f0e4ba3e9ab0d8cbe7e7f3e3b8d59))
89 |
90 | ## [3.7.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.6.1...v3.7.0) (2022-10-26)
91 |
92 |
93 | ### Features
94 |
95 | * added `info.sourceFilename` ([f8b3378](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/f8b337833759835f3f67277c8bfab85986416de9))
96 |
97 | ### [3.6.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.6.0...v3.6.1) (2022-09-19)
98 |
99 |
100 | ### Bug Fixes
101 |
102 | * fix resize option enabled flag (`squoosh`) ([#356](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/356)) ([b2a5015](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/b2a50151b9dcd9f79307695e52a2b12db72a0a7e))
103 | * support for animated images (`sharp`) ([#358](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/358)) ([3c30355](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/3c3035536303f95cc74ebaed5247731789422965))
104 | * throw an error on unsupported image formats (`sharp`) ([#359](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/359)) ([c0b193b](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/c0b193b38d4e488ca0651f2bea13065700cb3bf1))
105 |
106 | ## [3.6.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.5.0...v3.6.0) (2022-09-16)
107 |
108 |
109 | ### Features
110 |
111 | * supported more resize options (only `sharp`) ([#355](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/355)) ([d365db3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/d365db3db18476435cf8952aaa23801dd7e466ee))
112 |
113 | ## [3.5.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.4.0...v3.5.0) (2022-09-15)
114 |
115 |
116 | ### Features
117 |
118 | * add `width`/`w` and `height`/`h` query parameters to resize image ([52ee1c8](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/52ee1c84cf935e2ebbf4bfe38567a0cb73bd6c13))
119 |
120 |
121 | ### Bug Fixes
122 |
123 | * `implementation` types ([#353](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/353)) ([a57fcdf](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/a57fcdfc7121f1ceda178dd7987623433745b21e))
124 |
125 | ## [3.4.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.3.1...v3.4.0) (2022-09-09)
126 |
127 |
128 | ### Features
129 |
130 | * add `[width]` and `[height]` placeholders for the `filename` option ([#346](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/346)) ([682c22b](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/682c22b81f2f1af96e83f0e0805fd5406a209324))
131 |
132 |
133 | ### Bug Fixes
134 |
135 | * types ([cd7c7a7](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/cd7c7a7c7f12eb883399cf01ad7c9102e90b845b))
136 |
137 | ### [3.3.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.3.0...v3.3.1) (2022-09-05)
138 |
139 |
140 | ### Bug Fixes
141 |
142 | * assets info for sharp ([#338](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/338)) ([c897d30](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/c897d30bed8532fec1312be62483281589402b0b))
143 | * avoid renaming unsupported formats ([#339](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/339)) ([18e30ef](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/18e30ef3da70b39384f389e6729d56fb5b24af59))
144 | * sharp types ([#337](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/337)) ([ae3a03b](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/ae3a03b926a6bce29dee2829490a99d16394a501))
145 |
146 | ## [3.3.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.2.3...v3.3.0) (2022-08-12)
147 |
148 |
149 | ### Features
150 |
151 | * add `sharp` minifier/generator implementation ([#329](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/329)) ([5c440f6](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/5c440f6e8257fe0a4ebabcbe22a09063902a6c5e))
152 |
153 | ### [3.2.3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.2.2...v3.2.3) (2022-01-13)
154 |
155 |
156 | ### Bug Fixes
157 |
158 | * types ([#297](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/297)) ([c61642f](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/c61642f85b9dc17d45d79a42760c48fe41ffcd27))
159 |
160 | ### [3.2.2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.2.1...v3.2.2) (2022-01-07)
161 |
162 |
163 | ### Bug Fixes
164 |
165 | * perf for `squoosh` ([#295](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/295)) ([2f4d1a2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/2f4d1a291e30b737ebff118804f7fee93c90fcd1))
166 |
167 | ### [3.2.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.2.0...v3.2.1) (2022-01-03)
168 |
169 |
170 | ### Bug Fixes
171 |
172 | * memory leaking ([#293](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/293)) ([043e571](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/043e57114d701cf9dfe87b9dda3b185b99cbd399))
173 | * respect encoding of data uri ([#294](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/294)) ([a89b316](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/a89b3164a41f403a2a48d18cb7f9b92353dd18b7))
174 |
175 | ## [3.2.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.1.2...v3.2.0) (2021-12-25)
176 |
177 |
178 | ### Features
179 |
180 | * allow generating images from copied assets using the `type` option for the `generator` option ([fab9103](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/fab910337ef3c119f991f0d71c682d5ab3a65b5c))
181 |
182 | ### [3.1.2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.1.1...v3.1.2) (2021-12-17)
183 |
184 |
185 | ### Bug Fixes
186 |
187 | * improve perf ([#285](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/285)) ([435879d](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/435879dd74528850e0ade0ec24c9db968cbc7344))
188 |
189 | ### [3.1.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.1.0...v3.1.1) (2021-12-17)
190 |
191 |
192 | ### Bug Fixes
193 |
194 | * ignore unsupported data URI by mime type ([#284](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/284)) ([d1b68c2](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/d1b68c204ab604b37effc6614e939e2e36662095))
195 |
196 | ## [3.1.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.0.1...v3.1.0) (2021-12-16)
197 |
198 |
199 | ### Features
200 |
201 | * removed cjs wrapper and generated types in commonjs format (`export =` and `namespaces` used in types), now you can directly use exported types ([#282](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/282)) ([f0fa0a7](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/f0fa0a7fb2531d9e78e37778dae5c0b267724c1b))
202 |
203 | ### [3.0.1](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v3.0.0...v3.0.1) (2021-12-07)
204 |
205 |
206 | ### Bug Fixes
207 |
208 | * reduced memory consumption for `squoosh` ([#279](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/279)) ([0d597b7](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/0d597b751ca5eda293929ce8d71349572fbf0fb8))
209 | * types ([028fad3](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/028fad3403c890d691ebd636c7f55f6bf801a3b7))
210 |
211 | ## [3.0.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v2.2.0...v3.0.0) (2021-12-05)
212 |
213 | There are a lot of breaking changes, the plugin has been completely rewritten, see the documentation for information and examples.
214 |
215 | ### ⚠ BREAKING CHANGES
216 |
217 | * minimum supported Node.js version is `12.13.0`, `imagemin` uses dynamic `import()` to load plugins, so your Node.js version should support it
218 | * by default, we don't install `imagemin`, so you need to run `npm i -D imagemin` to install `imagemin`
219 | * union `minify` and `minizerOptions` in one option - `minimizer`, you can use `minimizer.implementation` and `minimizer.options` to specify minimizer and options
220 | * image generation was rewritten, please use the `generator` option to configure image generation and use `new URL("./image.png?as=webp")`/`div { backgaround: url("./image.png?as=webp"); }`/etc in code to enable it (`import` and `require` are supported too)
221 | * `filter` and `filename` option was moved in the `minimizer`/`generator` option
222 | * `imageminNormalizeConfig` is now async function
223 | * default value of the `severityError` option is `"error"`, removed values: `true`, `false` and `auto`
224 | * don't add `.` (dot) before `[ext]` in the `filename` option
225 |
226 | ### Features
227 |
228 | * added `squoosh` support
229 | * added the `minimizer` option for image optimization
230 | * added the `generator` option for image generation
231 | * added ability to use multiple `minimizer` option feature
232 | * allow the `filename` option will be `Function`
233 | * improve error reporting
234 | * improve types
235 | * output helpful descriptions and links on errors
236 | * improve stats output
237 |
238 | ### Bug Fixes
239 |
240 | * support esm `imagemin` plugin
241 | * supports absolute URLs, i.e. `data:`/`http:`/`https:`/`file:`
242 | * double minification and memory leak
243 | * respect original errors
244 | * compatibility with asset modules
245 |
246 | ## [2.2.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v2.1.0...v2.2.0) (2021-01-09)
247 |
248 |
249 | ### Features
250 |
251 | * run optimize image assets added later by plugins ([#178](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/issues/178)) ([4939f93](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/4939f93a55962c5812a693acc5eb441b78fe663c))
252 |
253 | ## [2.1.0](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/compare/v2.0.0...v2.1.0) (2020-12-23)
254 |
255 |
256 | ### Features
257 |
258 | * add TypeScript definitions ([e78497b](https://github.com/webpack-contrib/image-minimizer-webpack-plugin/commit/e78497b3f50d2cfc6368fcdc9de548a7ad76f559))
259 |
260 | ## 2.0.0 (2020-12-17)
261 |
262 |
263 | ### ⚠ BREAKING CHANGES
264 |
265 | * minimum supported `webpack` version is `5.1.0`
266 | * removed the `cache` option in favor the [`cache`](https://webpack.js.org/configuration/other-options/#cache) option from webpack
267 |
268 | ## 1.0.0 (2020-10-07)
269 |
270 | Initial release.
271 |
--------------------------------------------------------------------------------
/test/loader-minimizer-option.test.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 | import { imageSize } from "image-size";
4 |
5 | import ImageMinimizerPlugin from "../src";
6 |
7 | import {
8 | ifit,
9 | isOptimized,
10 | needSquooshTest,
11 | plugins,
12 | runWebpack,
13 | } from "./helpers";
14 |
15 | describe("loader minimizer option", () => {
16 | it("should work with object notation of the 'minifier' option", async () => {
17 | const stats = await runWebpack({
18 | imageminLoader: true,
19 | imageminLoaderOptions: {
20 | minimizer: {
21 | implementation: ImageMinimizerPlugin.imageminMinify,
22 | options: {
23 | plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"],
24 | },
25 | },
26 | },
27 | });
28 | const { compilation } = stats;
29 | const { warnings, errors } = compilation;
30 |
31 | expect(warnings).toHaveLength(0);
32 | expect(errors).toHaveLength(0);
33 |
34 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
35 | true,
36 | );
37 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
38 | true,
39 | );
40 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
41 | true,
42 | );
43 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
44 | true,
45 | );
46 | });
47 |
48 | it("should work with array notation of the 'minifier' option", async () => {
49 | const stats = await runWebpack({
50 | imageminLoader: true,
51 | imageminLoaderOptions: {
52 | minimizer: [
53 | {
54 | implementation: ImageMinimizerPlugin.imageminMinify,
55 | options: {
56 | plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"],
57 | },
58 | },
59 | ],
60 | },
61 | });
62 | const { compilation } = stats;
63 | const { warnings, errors } = compilation;
64 |
65 | expect(warnings).toHaveLength(0);
66 | expect(errors).toHaveLength(0);
67 |
68 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
69 | true,
70 | );
71 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
72 | true,
73 | );
74 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
75 | true,
76 | );
77 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
78 | true,
79 | );
80 | });
81 |
82 | it("should work with array notation of the 'minifier' option #2", async () => {
83 | expect.assertions(14);
84 |
85 | const stats = await runWebpack({
86 | imageminLoader: true,
87 | imageminLoaderOptions: {
88 | minimizer: [
89 | {
90 | implementation: ImageMinimizerPlugin.imageminMinify,
91 | options: {
92 | plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"],
93 | },
94 | },
95 | {
96 | implementation: (input, minifiOptions) => {
97 | expect(input).toBeDefined();
98 | expect(minifiOptions).toBeUndefined();
99 |
100 | return input;
101 | },
102 | },
103 | ],
104 | },
105 | });
106 | const { compilation } = stats;
107 | const { warnings, errors } = compilation;
108 |
109 | expect(warnings).toHaveLength(0);
110 | expect(errors).toHaveLength(0);
111 |
112 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
113 | true,
114 | );
115 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
116 | true,
117 | );
118 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
119 | true,
120 | );
121 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
122 | true,
123 | );
124 | });
125 |
126 | it("should work with array notation of the 'minifier' option #3", async () => {
127 | expect.assertions(14);
128 |
129 | const stats = await runWebpack({
130 | imageminLoader: true,
131 | imageminLoaderOptions: {
132 | minimizer: [
133 | {
134 | implementation: ImageMinimizerPlugin.imageminMinify,
135 | options: {
136 | plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"],
137 | },
138 | },
139 | {
140 | implementation: (input, minifiOptions) => {
141 | expect("options2" in minifiOptions).toBe(true);
142 |
143 | return input;
144 | },
145 | options: {
146 | options2: "passed",
147 | },
148 | },
149 | {
150 | implementation: (input, minifiOptions) => {
151 | expect("options3" in minifiOptions).toBe(true);
152 |
153 | return input;
154 | },
155 | options: {
156 | options3: "passed",
157 | },
158 | },
159 | ],
160 | },
161 | });
162 | const { compilation } = stats;
163 | const { warnings, errors } = compilation;
164 |
165 | expect(warnings).toHaveLength(0);
166 | expect(errors).toHaveLength(0);
167 |
168 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
169 | true,
170 | );
171 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
172 | true,
173 | );
174 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
175 | true,
176 | );
177 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
178 | true,
179 | );
180 | });
181 |
182 | it("should work when minify is custom function", async () => {
183 | expect.assertions(14);
184 |
185 | const stats = await runWebpack({
186 | imageminLoader: true,
187 | imageminLoaderOptions: {
188 | minimizer: {
189 | implementation: (input, minifiOptions) => {
190 | expect(input).toBeDefined();
191 | expect(minifiOptions).toBeDefined();
192 |
193 | return input;
194 | },
195 | options: {
196 | plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"],
197 | },
198 | },
199 | },
200 | });
201 | const { compilation } = stats;
202 | const { warnings, errors } = compilation;
203 |
204 | expect(warnings).toHaveLength(0);
205 | expect(errors).toHaveLength(0);
206 |
207 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
208 | false,
209 | );
210 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
211 | false,
212 | );
213 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
214 | false,
215 | );
216 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
217 | false,
218 | );
219 | });
220 |
221 | it("should emit errors", async () => {
222 | const stats = await runWebpack({
223 | imageminLoader: true,
224 | imageminLoaderOptions: {
225 | minimizer: {
226 | implementation: () => {
227 | throw new Error("test error");
228 | },
229 | },
230 | },
231 | });
232 | const { compilation } = stats;
233 | const { warnings, errors } = compilation;
234 |
235 | expect(errors).toHaveLength(4);
236 | expect(warnings).toHaveLength(0);
237 |
238 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
239 | false,
240 | );
241 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
242 | false,
243 | );
244 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
245 | false,
246 | );
247 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
248 | false,
249 | );
250 | });
251 |
252 | it('should work with "imageminMinify" minifier', async () => {
253 | const stats = await runWebpack({
254 | imageminLoader: true,
255 | imageminLoaderOptions: {
256 | minimizer: {
257 | implementation: ImageMinimizerPlugin.imageminMinify,
258 | options: {
259 | plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"],
260 | },
261 | },
262 | },
263 | });
264 | const { compilation } = stats;
265 | const { errors } = compilation;
266 |
267 | expect(errors).toHaveLength(0);
268 | expect(compilation.getAsset("loader-test.jpg").info.size).toBeLessThan(631);
269 | expect(compilation.getAsset("loader-test.png").info.size).toBeLessThan(
270 | 71000,
271 | );
272 | });
273 |
274 | ifit(needSquooshTest)(
275 | 'should work with "squooshMinify" minifier',
276 | async () => {
277 | const stats = await runWebpack({
278 | imageminLoader: true,
279 | imageminLoaderOptions: {
280 | minimizer: {
281 | implementation: ImageMinimizerPlugin.squooshMinify,
282 | },
283 | },
284 | });
285 | const { compilation } = stats;
286 | const { errors } = compilation;
287 |
288 | expect(errors).toHaveLength(0);
289 | expect(compilation.getAsset("loader-test.jpg").info.size).toBeLessThan(
290 | 631,
291 | );
292 | expect(compilation.getAsset("loader-test.png").info.size).toBeLessThan(
293 | 71000,
294 | );
295 | },
296 | );
297 |
298 | it('should work with "sharpMinify" minifier', async () => {
299 | const stats = await runWebpack({
300 | imageminLoader: true,
301 | imageminLoaderOptions: {
302 | minimizer: {
303 | implementation: ImageMinimizerPlugin.sharpMinify,
304 | },
305 | },
306 | });
307 | const { compilation } = stats;
308 | const { errors } = compilation;
309 |
310 | expect(errors).toHaveLength(0);
311 | expect(compilation.getAsset("loader-test.jpg").info.size).toBeLessThan(631);
312 | expect(compilation.getAsset("loader-test.png").info.size).toBeLessThan(
313 | 71000,
314 | );
315 | });
316 |
317 | it("should optimizes all images exclude filtered", async () => {
318 | const stats = await runWebpack({
319 | imageminLoaderOptions: {
320 | minimizer: {
321 | implementation: ImageMinimizerPlugin.imageminMinify,
322 | filter: (source, filename) => {
323 | expect(source).toBeInstanceOf(Buffer);
324 | expect(typeof filename).toBe("string");
325 |
326 | if (source.byteLength === 631) {
327 | return false;
328 | }
329 |
330 | return true;
331 | },
332 | options: { plugins },
333 | },
334 | },
335 | });
336 |
337 | const { compilation } = stats;
338 | const { warnings, errors, assets } = compilation;
339 |
340 | expect(warnings).toHaveLength(0);
341 | expect(errors).toHaveLength(0);
342 | expect(Object.keys(assets)).toHaveLength(5);
343 |
344 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
345 | true,
346 | );
347 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
348 | true,
349 | );
350 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
351 | true,
352 | );
353 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
354 | false,
355 | );
356 | });
357 |
358 | it("should optimizes all images exclude filtered for #2", async () => {
359 | const stats = await runWebpack({
360 | imageminLoaderOptions: {
361 | minimizer: [
362 | {
363 | implementation: ImageMinimizerPlugin.imageminMinify,
364 | filter: (source, filename) => {
365 | expect(source).toBeInstanceOf(Buffer);
366 | expect(typeof filename).toBe("string");
367 |
368 | if (source.byteLength === 631) {
369 | return false;
370 | }
371 |
372 | return true;
373 | },
374 | options: { plugins },
375 | },
376 | ],
377 | },
378 | });
379 |
380 | const { compilation } = stats;
381 | const { warnings, errors, assets } = compilation;
382 |
383 | expect(warnings).toHaveLength(0);
384 | expect(errors).toHaveLength(0);
385 | expect(Object.keys(assets)).toHaveLength(5);
386 |
387 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
388 | true,
389 | );
390 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
391 | true,
392 | );
393 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
394 | true,
395 | );
396 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
397 | false,
398 | );
399 | });
400 |
401 | it("should optimizes all images exclude filtered for #3", async () => {
402 | const stats = await runWebpack({
403 | imageminLoaderOptions: {
404 | minimizer: [
405 | {
406 | implementation: ImageMinimizerPlugin.imageminMinify,
407 | options: { plugins },
408 | filter: (source, filename) => {
409 | expect(source).toBeInstanceOf(Buffer);
410 | expect(typeof filename).toBe("string");
411 |
412 | if (source.byteLength === 631 || /\.png$/.test(filename)) {
413 | return false;
414 | }
415 |
416 | return true;
417 | },
418 | },
419 | {
420 | implementation: ImageMinimizerPlugin.imageminMinify,
421 | options: { plugins },
422 | },
423 | ],
424 | },
425 | });
426 |
427 | const { compilation } = stats;
428 | const { warnings, errors, assets } = compilation;
429 |
430 | expect(warnings).toHaveLength(0);
431 | expect(errors).toHaveLength(0);
432 | expect(Object.keys(assets)).toHaveLength(5);
433 |
434 | await expect(isOptimized("loader-test.gif", compilation)).resolves.toBe(
435 | true,
436 | );
437 | await expect(isOptimized("loader-test.png", compilation)).resolves.toBe(
438 | true,
439 | );
440 | await expect(isOptimized("loader-test.svg", compilation)).resolves.toBe(
441 | true,
442 | );
443 | await expect(isOptimized("loader-test.jpg", compilation)).resolves.toBe(
444 | true,
445 | );
446 | });
447 |
448 | ifit(needSquooshTest)(
449 | 'should minimize and resize with "squooshMinify" minifier',
450 | async () => {
451 | const stats = await runWebpack({
452 | imageminLoader: true,
453 | imageminLoaderOptions: {
454 | minimizer: {
455 | implementation: ImageMinimizerPlugin.squooshMinify,
456 | options: {
457 | resize: {
458 | enabled: true,
459 | width: 100,
460 | height: 50,
461 | },
462 | rotate: {
463 | numRotations: 90,
464 | },
465 | },
466 | },
467 | },
468 | });
469 | const { compilation } = stats;
470 | const { errors } = compilation;
471 |
472 | const transformedAsset = path.resolve(
473 | __dirname,
474 | compilation.options.output.path,
475 | "./loader-test.png",
476 | );
477 | const dimensions = imageSize(await fs.readFile(transformedAsset));
478 |
479 | expect(dimensions.height).toBe(50);
480 | expect(dimensions.width).toBe(100);
481 | expect(errors).toHaveLength(0);
482 | expect(compilation.getAsset("loader-test.jpg").info.size).toBeLessThan(
483 | 631,
484 | );
485 | expect(compilation.getAsset("loader-test.png").info.size).toBeLessThan(
486 | 71000,
487 | );
488 | },
489 | );
490 |
491 | it('should minimize and resize with "sharpMinify" minifier', async () => {
492 | const stats = await runWebpack({
493 | imageminLoader: true,
494 | imageminLoaderOptions: {
495 | minimizer: {
496 | implementation: ImageMinimizerPlugin.sharpMinify,
497 | options: {
498 | resize: {
499 | enabled: true,
500 | width: 100,
501 | height: 50,
502 | },
503 | rotate: {
504 | numRotations: 90,
505 | },
506 | },
507 | },
508 | },
509 | });
510 | const { compilation } = stats;
511 | const { errors } = compilation;
512 |
513 | const transformedAsset = path.resolve(
514 | __dirname,
515 | compilation.options.output.path,
516 | "./loader-test.png",
517 | );
518 | const dimensions = imageSize(await fs.readFile(transformedAsset));
519 |
520 | expect(dimensions.height).toBe(50);
521 | expect(dimensions.width).toBe(100);
522 | expect(errors).toHaveLength(0);
523 | expect(compilation.getAsset("loader-test.jpg").info.size).toBeLessThan(631);
524 | expect(compilation.getAsset("loader-test.png").info.size).toBeLessThan(
525 | 71000,
526 | );
527 | });
528 | });
529 |
--------------------------------------------------------------------------------
/test/loader-generator-option.test.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs/promises";
2 | import path from "node:path";
3 | import fileType from "file-type";
4 | import { imageSize } from "image-size";
5 | import ImageMinimizerPlugin from "../src";
6 |
7 | import { fixturesPath, ifit, needSquooshTest, runWebpack } from "./helpers";
8 |
9 | jest.setTimeout(10000);
10 |
11 | describe("loader generator option", () => {
12 | it("should work", async () => {
13 | const stats = await runWebpack({
14 | entry: path.join(fixturesPath, "./loader-single.js"),
15 | imageminLoaderOptions: {
16 | generator: [
17 | {
18 | preset: "webp",
19 | implementation: ImageMinimizerPlugin.sharpGenerate,
20 | options: {
21 | encodeOptions: {
22 | webp: {
23 | lossless: true,
24 | },
25 | },
26 | },
27 | },
28 | ],
29 | },
30 | });
31 |
32 | const { compilation } = stats;
33 | const { warnings, errors } = compilation;
34 |
35 | const transformedAsset = path.resolve(
36 | __dirname,
37 | compilation.options.output.path,
38 | "./nested/deep/loader-test.webp",
39 | );
40 |
41 | const transformedExt = await fileType.fromFile(transformedAsset);
42 |
43 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
44 | expect(warnings).toHaveLength(0);
45 | expect(errors).toHaveLength(0);
46 | });
47 |
48 | it("should throw error on duplicate presets", async () => {
49 | const stats = await runWebpack({
50 | entry: path.join(fixturesPath, "./loader-single.js"),
51 | imageminLoaderOptions: {
52 | generator: [
53 | {
54 | preset: "webp",
55 | implementation: ImageMinimizerPlugin.sharpGenerate,
56 | options: {
57 | encodeOptions: {
58 | webp: {
59 | lossless: true,
60 | },
61 | },
62 | },
63 | },
64 | {
65 | preset: "webp",
66 | implementation: ImageMinimizerPlugin.sharpGenerate,
67 | options: {
68 | encodeOptions: {
69 | webp: {
70 | lossless: true,
71 | },
72 | },
73 | },
74 | },
75 | ],
76 | },
77 | });
78 |
79 | const { compilation } = stats;
80 | const { warnings, errors } = compilation;
81 |
82 | expect(warnings).toHaveLength(0);
83 | expect(errors).toHaveLength(1);
84 | expect(errors[0].message).toMatch(
85 | /Found several identical preset names, the 'preset' option should be unique/,
86 | );
87 | });
88 |
89 | it("should throw error on not found preset", async () => {
90 | const stats = await runWebpack({
91 | entry: path.join(fixturesPath, "./loader-single.js"),
92 | imageminLoaderOptions: {
93 | generator: [
94 | {
95 | preset: "webpz",
96 | implementation: ImageMinimizerPlugin.sharpGenerate,
97 | options: {
98 | encodeOptions: {
99 | webp: {
100 | lossless: true,
101 | },
102 | },
103 | },
104 | },
105 | ],
106 | },
107 | });
108 |
109 | const { compilation } = stats;
110 | const { warnings, errors } = compilation;
111 |
112 | expect(warnings).toHaveLength(0);
113 | expect(errors).toHaveLength(1);
114 | expect(errors[0].message).toMatch(
115 | /Can't find 'webp' preset in the 'generator' option/,
116 | );
117 | });
118 |
119 | it("should generate the new webp image with other name using old loader approach", async () => {
120 | const stats = await runWebpack({
121 | entry: path.join(fixturesPath, "./loader-single.js"),
122 | name: "foo-[name].[ext]",
123 | imageminLoaderOptions: {
124 | generator: [
125 | {
126 | preset: "webp",
127 | implementation: ImageMinimizerPlugin.sharpGenerate,
128 | options: {
129 | encodeOptions: {
130 | webp: {
131 | lossless: true,
132 | },
133 | },
134 | },
135 | },
136 | ],
137 | },
138 | });
139 |
140 | const { compilation } = stats;
141 | const { warnings, errors } = compilation;
142 |
143 | const transformedAsset = path.resolve(
144 | __dirname,
145 | compilation.options.output.path,
146 | "foo-loader-test.webp",
147 | );
148 |
149 | const transformedExt = await fileType.fromFile(transformedAsset);
150 |
151 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
152 | expect(warnings).toHaveLength(0);
153 | expect(errors).toHaveLength(0);
154 | });
155 |
156 | it("should generate the new webp image with other name using asset modules name", async () => {
157 | const stats = await runWebpack({
158 | entry: path.join(fixturesPath, "./loader-single.js"),
159 | fileLoaderOff: true,
160 | assetResource: true,
161 | name: "foo-[name][ext]",
162 | imageminLoaderOptions: {
163 | generator: [
164 | {
165 | preset: "webp",
166 | implementation: ImageMinimizerPlugin.sharpGenerate,
167 | options: {
168 | encodeOptions: {
169 | webp: {
170 | lossless: true,
171 | },
172 | },
173 | },
174 | },
175 | ],
176 | },
177 | });
178 |
179 | const { compilation } = stats;
180 | const { warnings, errors } = compilation;
181 |
182 | const transformedAsset = path.resolve(
183 | __dirname,
184 | compilation.options.output.path,
185 | "foo-loader-test.webp",
186 | );
187 |
188 | const transformedExt = await fileType.fromFile(transformedAsset);
189 |
190 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
191 | expect(warnings).toHaveLength(0);
192 | expect(errors).toHaveLength(0);
193 | });
194 |
195 | ifit(needSquooshTest)(
196 | "should generate and resize (squooshGenerate)",
197 | async () => {
198 | const stats = await runWebpack({
199 | entry: path.join(fixturesPath, "./loader-single.js"),
200 | imageminLoaderOptions: {
201 | generator: [
202 | {
203 | preset: "webp",
204 | implementation: ImageMinimizerPlugin.squooshGenerate,
205 | options: {
206 | resize: {
207 | enabled: true,
208 | width: 100,
209 | height: 50,
210 | },
211 | rotate: {
212 | numRotations: 90,
213 | },
214 | encodeOptions: {
215 | webp: {
216 | lossless: 1,
217 | },
218 | },
219 | },
220 | },
221 | ],
222 | },
223 | });
224 |
225 | const { compilation } = stats;
226 | const { warnings, errors } = compilation;
227 |
228 | const transformedAsset = path.resolve(
229 | __dirname,
230 | compilation.options.output.path,
231 | "./nested/deep/loader-test.webp",
232 | );
233 |
234 | const transformedExt = await fileType.fromFile(transformedAsset);
235 | const dimensions = imageSize(await fs.readFile(transformedAsset));
236 |
237 | expect(dimensions.height).toBe(50);
238 | expect(dimensions.width).toBe(100);
239 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
240 | expect(warnings).toHaveLength(0);
241 | expect(errors).toHaveLength(0);
242 | },
243 | );
244 |
245 | ifit(needSquooshTest)(
246 | "should generate and not resize (squooshGenerate)",
247 | async () => {
248 | const stats = await runWebpack({
249 | entry: path.join(fixturesPath, "./loader-single.js"),
250 | imageminLoaderOptions: {
251 | generator: [
252 | {
253 | preset: "webp",
254 | implementation: ImageMinimizerPlugin.squooshGenerate,
255 | options: {
256 | resize: {
257 | enabled: false,
258 | width: 100,
259 | height: 50,
260 | },
261 | rotate: {
262 | numRotations: 90,
263 | },
264 | encodeOptions: {
265 | webp: {
266 | lossless: 1,
267 | },
268 | },
269 | },
270 | },
271 | ],
272 | },
273 | });
274 |
275 | const { compilation } = stats;
276 | const { warnings, errors } = compilation;
277 |
278 | const transformedAsset = path.resolve(
279 | __dirname,
280 | compilation.options.output.path,
281 | "./nested/deep/loader-test.webp",
282 | );
283 |
284 | const transformedExt = await fileType.fromFile(transformedAsset);
285 | const dimensions = imageSize(await fs.readFile(transformedAsset));
286 |
287 | expect(dimensions.height).toBe(1);
288 | expect(dimensions.width).toBe(1);
289 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
290 | expect(warnings).toHaveLength(0);
291 | expect(errors).toHaveLength(0);
292 | },
293 | );
294 |
295 | it("should generate and resize (sharpGenerate)", async () => {
296 | const stats = await runWebpack({
297 | entry: path.join(fixturesPath, "./loader-single.js"),
298 | imageminLoaderOptions: {
299 | generator: [
300 | {
301 | preset: "webp",
302 | implementation: ImageMinimizerPlugin.sharpGenerate,
303 | options: {
304 | resize: {
305 | enabled: true,
306 | width: 100,
307 | height: 50,
308 | },
309 | encodeOptions: {
310 | webp: {
311 | lossless: true,
312 | },
313 | },
314 | },
315 | },
316 | ],
317 | },
318 | });
319 |
320 | const { compilation } = stats;
321 | const { warnings, errors } = compilation;
322 |
323 | const transformedAsset = path.resolve(
324 | __dirname,
325 | compilation.options.output.path,
326 | "./nested/deep/loader-test.webp",
327 | );
328 |
329 | const transformedExt = await fileType.fromFile(transformedAsset);
330 | const dimensions = imageSize(await fs.readFile(transformedAsset));
331 |
332 | expect(dimensions.height).toBe(50);
333 | expect(dimensions.width).toBe(100);
334 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
335 | expect(warnings).toHaveLength(0);
336 | expect(errors).toHaveLength(0);
337 | });
338 |
339 | it("should generate and not resize (sharpGenerate)", async () => {
340 | const stats = await runWebpack({
341 | entry: path.join(fixturesPath, "./loader-single.js"),
342 | imageminLoaderOptions: {
343 | generator: [
344 | {
345 | preset: "webp",
346 | implementation: ImageMinimizerPlugin.sharpGenerate,
347 | options: {
348 | resize: {
349 | enabled: false,
350 | width: 100,
351 | height: 50,
352 | },
353 | encodeOptions: {
354 | webp: {
355 | lossless: true,
356 | },
357 | },
358 | },
359 | },
360 | ],
361 | },
362 | });
363 |
364 | const { compilation } = stats;
365 | const { warnings, errors } = compilation;
366 |
367 | const transformedAsset = path.resolve(
368 | __dirname,
369 | compilation.options.output.path,
370 | "./nested/deep/loader-test.webp",
371 | );
372 |
373 | const transformedExt = await fileType.fromFile(transformedAsset);
374 | const dimensions = imageSize(await fs.readFile(transformedAsset));
375 |
376 | expect(dimensions.height).toBe(1);
377 | expect(dimensions.width).toBe(1);
378 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
379 | expect(warnings).toHaveLength(0);
380 | expect(errors).toHaveLength(0);
381 | });
382 |
383 | it("should minify and rename (sharpMinify)", async () => {
384 | const stats = await runWebpack({
385 | entry: path.join(fixturesPath, "./simple.js"),
386 | imageminLoaderOptions: {
387 | minimizer: {
388 | implementation: ImageMinimizerPlugin.sharpMinify,
389 | filename: "sharp-minify-[name]-[width]x[height][ext]",
390 | options: {
391 | encodeOptions: {
392 | jpeg: { quality: 90 },
393 | },
394 | },
395 | },
396 | },
397 | });
398 |
399 | const { compilation } = stats;
400 | const { warnings, errors } = compilation;
401 |
402 | const transformedAsset = path.resolve(
403 | __dirname,
404 | compilation.options.output.path,
405 | "./sharp-minify-loader-test-1x1.jpg",
406 | );
407 |
408 | const transformedExt = await fileType.fromFile(transformedAsset);
409 |
410 | expect(/image\/jpeg/i.test(transformedExt.mime)).toBe(true);
411 | expect(warnings).toHaveLength(0);
412 | expect(errors).toHaveLength(0);
413 | });
414 |
415 | it("should generate, resize and rename (sharpGenerate)", async () => {
416 | const stats = await runWebpack({
417 | entry: path.join(fixturesPath, "./generator.js"),
418 | imageminLoaderOptions: {
419 | generator: [
420 | {
421 | preset: "webp",
422 | implementation: ImageMinimizerPlugin.sharpGenerate,
423 | filename: "sharp-generate-[name]-[width]x[height][ext]",
424 | options: {
425 | resize: {
426 | enabled: true,
427 | width: 100,
428 | height: 50,
429 | },
430 | encodeOptions: {
431 | webp: {
432 | lossless: true,
433 | },
434 | },
435 | },
436 | },
437 | ],
438 | },
439 | });
440 |
441 | const { compilation } = stats;
442 | const { warnings, errors } = compilation;
443 |
444 | const transformedAsset = path.resolve(
445 | __dirname,
446 | compilation.options.output.path,
447 | "./sharp-generate-loader-test-100x50.webp",
448 | );
449 |
450 | const transformedExt = await fileType.fromFile(transformedAsset);
451 | const dimensions = imageSize(await fs.readFile(transformedAsset));
452 |
453 | expect(dimensions.height).toBe(50);
454 | expect(dimensions.width).toBe(100);
455 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
456 | expect(warnings).toHaveLength(0);
457 | expect(errors).toHaveLength(0);
458 | });
459 |
460 | it("should minify and rename", async () => {
461 | const stats = await runWebpack({
462 | entry: path.join(fixturesPath, "./simple.js"),
463 | imageminLoaderOptions: {
464 | minimizer: {
465 | implementation: ImageMinimizerPlugin.sharpMinify,
466 | filename: "sharp-minify-[name]-[width]x[height][ext]",
467 | options: {
468 | encodeOptions: {
469 | jpeg: { quality: 90 },
470 | },
471 | },
472 | },
473 | },
474 | });
475 |
476 | const { compilation } = stats;
477 | const { warnings, errors } = compilation;
478 |
479 | const transformedAsset = path.resolve(
480 | __dirname,
481 | compilation.options.output.path,
482 | "./sharp-minify-loader-test-1x1.jpg",
483 | );
484 |
485 | const transformedExt = await fileType.fromFile(transformedAsset);
486 |
487 | expect(/image\/jpeg/i.test(transformedExt.mime)).toBe(true);
488 | expect(warnings).toHaveLength(0);
489 | expect(errors).toHaveLength(0);
490 | });
491 |
492 | it("should generate, resize and rename", async () => {
493 | const stats = await runWebpack({
494 | entry: path.join(fixturesPath, "./generator.js"),
495 | imageminLoaderOptions: {
496 | generator: [
497 | {
498 | preset: "webp",
499 | implementation: ImageMinimizerPlugin.sharpGenerate,
500 | filename: "sharp-generate-[name]-[width]x[height][ext]",
501 | options: {
502 | resize: {
503 | enabled: true,
504 | width: 100,
505 | height: 50,
506 | },
507 | encodeOptions: {
508 | webp: {
509 | lossless: true,
510 | },
511 | },
512 | },
513 | },
514 | ],
515 | },
516 | });
517 |
518 | const { compilation } = stats;
519 | const { warnings, errors } = compilation;
520 |
521 | const transformedAsset = path.resolve(
522 | __dirname,
523 | compilation.options.output.path,
524 | "./sharp-generate-loader-test-100x50.webp",
525 | );
526 |
527 | const transformedExt = await fileType.fromFile(transformedAsset);
528 | const dimensions = imageSize(await fs.readFile(transformedAsset));
529 |
530 | expect(dimensions.height).toBe(50);
531 | expect(dimensions.width).toBe(100);
532 | expect(/image\/webp/i.test(transformedExt.mime)).toBe(true);
533 | expect(warnings).toHaveLength(0);
534 | expect(errors).toHaveLength(0);
535 | });
536 | });
537 |
--------------------------------------------------------------------------------