├── .npmrc ├── .gitattributes ├── .gitignore ├── fixtures ├── test-corrupt.webp ├── test.png └── test-unsupported.bmp ├── .editorconfig ├── .github └── workflows │ └── test.yml ├── package.json ├── test.js ├── license ├── index.js └── readme.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /fixtures/test-corrupt.webp: -------------------------------------------------------------------------------- 1 | RIFFWEBPVP8L/Alok -------------------------------------------------------------------------------- /fixtures/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagemin/imagemin-webp/HEAD/fixtures/test.png -------------------------------------------------------------------------------- /fixtures/test-unsupported.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagemin/imagemin-webp/HEAD/fixtures/test-unsupported.bmp -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | node-version: 12 | - 16 13 | - 14 14 | os: 15 | - ubuntu-latest 16 | - macos-latest 17 | - windows-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm install 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imagemin-webp", 3 | "version": "8.0.0", 4 | "description": "WebP imagemin plugin", 5 | "license": "MIT", 6 | "repository": "imagemin/imagemin-webp", 7 | "type": "module", 8 | "exports": "./index.js", 9 | "engines": { 10 | "node": ">=14.16" 11 | }, 12 | "scripts": { 13 | "test": "xo && ava" 14 | }, 15 | "files": [ 16 | "index.js" 17 | ], 18 | "keywords": [ 19 | "compress", 20 | "cwebp", 21 | "image", 22 | "imageminplugin", 23 | "img", 24 | "jpg", 25 | "minify", 26 | "optimize", 27 | "png", 28 | "tif", 29 | "webp" 30 | ], 31 | "dependencies": { 32 | "cwebp-bin": "^8.0.0", 33 | "exec-buffer": "^3.2.0", 34 | "is-cwebp-readable": "^3.0.0" 35 | }, 36 | "devDependencies": { 37 | "ava": "^5.1.1", 38 | "is-webp": "^2.0.0", 39 | "xo": "^0.53.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import isWebP from 'is-webp'; 3 | import test from 'ava'; 4 | import imageminWebp from './index.js'; 5 | 6 | test('convert an image into a WebP', async t => { 7 | const buffer = await fs.readFile(new URL('fixtures/test.png', import.meta.url)); 8 | const data = await imageminWebp()(buffer); 9 | 10 | t.true(data.length < buffer.length); 11 | t.true(isWebP(data)); 12 | }); 13 | 14 | test('skip optimizing unsupported files', async t => { 15 | const buffer = await fs.readFile(new URL('fixtures/test-unsupported.bmp', import.meta.url)); 16 | const data = await imageminWebp()(buffer); 17 | 18 | t.deepEqual(data, buffer); 19 | }); 20 | 21 | test('throw error when an image is corrupt', async t => { 22 | const buffer = await fs.readFile(new URL('fixtures/test-corrupt.webp', import.meta.url)); 23 | await t.throwsAsync(() => imageminWebp()(buffer), {message: /BITSTREAM_ERROR/}); 24 | }); 25 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Imagemin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {Buffer} from 'node:buffer'; 2 | import execBuffer from 'exec-buffer'; 3 | import isCwebpReadable from 'is-cwebp-readable'; 4 | import cwebp from 'cwebp-bin'; 5 | 6 | const imageminWebp = (options = {}) => input => { 7 | if (!Buffer.isBuffer(input)) { 8 | return Promise.reject(new TypeError(`Expected \`input\` to be of type \`Buffer\` but received type \`${typeof input}\``)); 9 | } 10 | 11 | if (!isCwebpReadable(input)) { 12 | return Promise.resolve(input); 13 | } 14 | 15 | const args = [ 16 | '-quiet', 17 | '-mt', 18 | ]; 19 | 20 | if (options.preset) { 21 | args.push('-preset', options.preset); 22 | } 23 | 24 | if (options.quality) { 25 | args.push('-q', options.quality); 26 | } 27 | 28 | if (options.alphaQuality) { 29 | args.push('-alpha_q', options.alphaQuality); 30 | } 31 | 32 | if (options.method) { 33 | args.push('-m', options.method); 34 | } 35 | 36 | if (options.size > 0) { 37 | args.push('-size', options.size); 38 | } 39 | 40 | if (options.sns) { 41 | args.push('-sns', options.sns); 42 | } 43 | 44 | if (options.filter) { 45 | args.push('-f', options.filter); 46 | } 47 | 48 | if (options.autoFilter) { 49 | args.push('-af'); 50 | } 51 | 52 | if (options.sharpness) { 53 | args.push('-sharpness', options.sharpness); 54 | } 55 | 56 | if (options.lossless) { 57 | if (typeof options.lossless === 'number') { 58 | args.push('-z', options.lossless); 59 | } else { 60 | args.push('-lossless'); 61 | } 62 | } 63 | 64 | if (options.nearLossless) { 65 | args.push('-near_lossless', options.nearLossless); 66 | } 67 | 68 | if (options.crop) { 69 | args.push('-crop', options.crop.x, options.crop.y, options.crop.width, options.crop.height); 70 | } 71 | 72 | if (options.resize) { 73 | args.push('-resize', options.resize.width, options.resize.height); 74 | } 75 | 76 | if (options.metadata) { 77 | args.push('-metadata', Array.isArray(options.metadata) ? options.metadata.join(',') : options.metadata); 78 | } 79 | 80 | args.push('-o', execBuffer.output, execBuffer.input); 81 | 82 | return execBuffer({ 83 | args, 84 | bin: cwebp, 85 | input, 86 | }).catch(error => { 87 | error.message = error.stderr || error.message; 88 | throw error; 89 | }); 90 | }; 91 | 92 | export default imageminWebp; 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # imagemin-webp ![GitHub Actions Status](https://github.com/imagemin/imagemin-webp/workflows/test/badge.svg?branch=main) 2 | 3 | > WebP [imagemin](https://github.com/imagemin/imagemin) plugin 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install imagemin-webp 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import imagemin from 'imagemin'; 15 | import imageminWebp from 'imagemin-webp'; 16 | 17 | await imagemin(['images/*.{jpg,png}'], { 18 | destination: 'build/images', 19 | plugins: [ 20 | imageminWebp({quality: 50}) 21 | ] 22 | }); 23 | 24 | console.log('Images optimized'); 25 | ``` 26 | 27 | ## API 28 | 29 | ### imageminWebp(options?)(buffer) 30 | 31 | Returns a `Promise` with the optimized image. 32 | 33 | #### options 34 | 35 | Type: `object` 36 | 37 | ##### preset 38 | 39 | Type: `string`\ 40 | Default: `default` 41 | 42 | Preset setting, one of `default`, `photo`, `picture`, `drawing`, `icon` and `text`. 43 | 44 | ##### quality 45 | 46 | Type: `number`\ 47 | Default: `75` 48 | 49 | Set quality factor between `0` and `100`. 50 | 51 | ##### alphaQuality 52 | 53 | Type: `number`\ 54 | Default: `100` 55 | 56 | Set transparency-compression quality between `0` and `100`. 57 | 58 | ##### method 59 | 60 | Type: `number`\ 61 | Default: `4` 62 | 63 | Specify the compression method to use, between `0` (fastest) and `6` (slowest). This parameter controls the trade off between encoding speed and the compressed file size and quality. 64 | 65 | ##### size 66 | 67 | Type: `number` 68 | 69 | Set target size in bytes. 70 | 71 | ##### sns 72 | 73 | Type: `number`\ 74 | Default: `50` 75 | 76 | Set the amplitude of spatial noise shaping between `0` and `100`. 77 | 78 | ##### filter 79 | 80 | Type: `number` 81 | 82 | Set deblocking filter strength between `0` (off) and `100`. 83 | 84 | ##### autoFilter 85 | 86 | Type: `boolean`\ 87 | Default: `false` 88 | 89 | Adjust filter strength automatically. 90 | 91 | ##### sharpness 92 | 93 | Type: `number`\ 94 | Default: `0` 95 | 96 | Set filter sharpness between `0` (sharpest) and `7` (least sharp). 97 | 98 | ##### lossless 99 | 100 | Type: `boolean | number`\ 101 | Default: `false` 102 | 103 | Encode images losslessly. If set to a number, activates lossless preset with given level between `0` (fastest, larger files) and `9` (slowest, smaller files). 104 | 105 | ##### nearLossless 106 | 107 | Type: `number`\ 108 | Default: `100` 109 | 110 | Encode losslessly with an additional [lossy pre-processing step](https://groups.google.com/a/webmproject.org/forum/#!msg/webp-discuss/0GmxDmlexek/3ggyYsaYdFEJ), with a quality factor between `0` (maximum pre-processing) and `100` (same as `lossless`). 111 | 112 | ##### crop 113 | 114 | Type: `object { x: number, y: number, width: number, height: number }` 115 | 116 | Crop the image. 117 | 118 | ##### resize 119 | 120 | Type: `object { width: number, height: number }` 121 | 122 | Resize the image. Happens after `crop`. 123 | 124 | ##### metadata 125 | 126 | Type: `string | string[]`\ 127 | Default: `none`\ 128 | Values: `all` `none` `exif` `icc` `xmp` 129 | 130 | A list of metadata to copy from the input to the output if present. 131 | 132 | #### buffer 133 | 134 | Type: `Buffer` 135 | 136 | Buffer to optimize. 137 | --------------------------------------------------------------------------------