From 981b2167750f0d1caae09f852f6355cc3ffc0e51 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 May 2024 18:04:36 +0700 Subject: [PATCH] Require Node.js 18 --- .github/workflows/test.yml | 6 +-- index.d.ts | 11 +----- index.js | 75 +++++++++++++++++--------------------- index.test-d.ts | 18 --------- package.json | 22 +++++------ readme.md | 64 ++++++++++++++------------------ test.js | 26 ++++--------- tsconfig.json | 7 ---- 8 files changed, 85 insertions(+), 144 deletions(-) delete mode 100644 index.test-d.ts delete mode 100644 tsconfig.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc73b82..3132799 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 @@ -36,7 +36,7 @@ jobs: strategy: matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 @@ -54,7 +54,7 @@ jobs: strategy: matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 diff --git a/index.d.ts b/index.d.ts index 8bb2889..570ae17 100644 --- a/index.d.ts +++ b/index.d.ts @@ -43,19 +43,12 @@ export interface Options { // eslint-disable-line @typescript-eslint/consistent- Use this when image will be output on low-depth displays (e.g. 16-bit RGB). pngquant will make almost-opaque pixels fully opaque and will reduce amount of semi-transparent colors. */ posterize?: number; - - /** - Print verbose status messages. - - @default false - */ - verbose?: boolean; } /** -Buffer or stream to optimize. +Image data to optimize. */ -export type Plugin = (input: Buffer | NodeJS.ReadableStream) => Promise; +export type Plugin = (input: Uint8Array) => Promise; /** Imagemin plugin for pngquant. diff --git a/index.js b/index.js index 8e00477..0c3d58b 100644 --- a/index.js +++ b/index.js @@ -1,86 +1,77 @@ import {execa} from 'execa'; import isPng from 'is-png'; -import {isStream} from 'is-stream'; import pngquant from 'pngquant-bin'; import ow from 'ow'; +import {isUint8Array} from 'uint8array-extras'; +import {isBrowser} from 'environment'; -export function imageminPngquant(options = {}) { - return input => { - const isBuffer = Buffer.isBuffer(input); +export default function imageminPngquant(options = {}) { + if (isBrowser) { + throw new Error('This package does not work in the browser.'); + } - if (!isBuffer && !isStream(input)) { - return Promise.reject(new TypeError(`Expected a Buffer or Stream, got ${typeof input}`)); + return async input => { + const isData = isUint8Array(input); + + if (!isUint8Array(input)) { + throw new TypeError(`Expected a Uint8Array, got ${typeof input}`); } - if (isBuffer && !isPng(input)) { - return Promise.resolve(input); + if (isData && !isPng(input)) { + return input; } - const args = ['-']; + const arguments_ = ['-']; if (options.speed !== undefined) { ow(options.speed, ow.number.integer.inRange(1, 11)); - args.push('--speed', options.speed); + arguments_.push('--speed', options.speed.toString()); } if (options.strip !== undefined) { ow(options.strip, ow.boolean); if (options.strip) { - args.push('--strip'); + arguments_.push('--strip'); } } if (options.quality !== undefined) { ow(options.quality, ow.array.length(2).ofType(ow.number.inRange(0, 1))); const [min, max] = options.quality; - args.push('--quality', `${Math.round(min * 100)}-${Math.round(max * 100)}`); + arguments_.push('--quality', `${Math.round(min * 100)}-${Math.round(max * 100)}`); } if (options.dithering !== undefined) { ow(options.dithering, ow.any(ow.number.inRange(0, 1), ow.boolean.false)); if (typeof options.dithering === 'number') { - args.push(`--floyd=${options.dithering}`); + arguments_.push(`--floyd=${options.dithering}`); } else if (options.dithering === false) { - args.push('--ordered'); + arguments_.push('--ordered'); } } if (options.posterize !== undefined) { ow(options.posterize, ow.number); - args.push('--posterize', options.posterize); - } - - if (options.verbose !== undefined) { - ow(options.verbose, ow.boolean); - args.push('--verbose'); + arguments_.push('--posterize', options.posterize.toString()); } - const subprocess = execa(pngquant, args, { - encoding: null, - maxBuffer: Number.POSITIVE_INFINITY, - input, - }); - - const promise = subprocess - .then(result => result.stdout) - .catch(error => { - // We use `error.exitCode` to check for a special condition when running the pngquant binary. - // See details on handling of "99" code at https://pngquant.org (search for "status code 99"). - if (error.exitCode === 99) { - return input; - } - - error.message = error.stderr || error.message; - throw error; + try { + const {stdout} = await execa(pngquant, arguments_, { + encoding: 'buffer', + maxBuffer: Number.POSITIVE_INFINITY, + input, }); - subprocess.stdout.then = promise.then.bind(promise); // eslint-disable-line unicorn/no-thenable - subprocess.stdout.catch = promise.catch.bind(promise); + return stdout; + } catch (error) { + // Handling special condition from pngquant binary (status code 99). + if (error.exitCode === 99) { + return input; + } - return subprocess.stdout; + throw error; + } }; } - -export {imageminPngquant as default}; diff --git a/index.test-d.ts b/index.test-d.ts deleted file mode 100644 index e292ffc..0000000 --- a/index.test-d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import * as url from 'node:url'; -import {expectType} from 'tsd'; -import pngquant from './index.js'; - -const thisDirname = url.fileURLToPath(new URL('.', import.meta.url)); -const buffer = fs.readFileSync(path.join(thisDirname, 'fixture.png')); - -async function test() { - expectType(await pngquant()(buffer)); - expectType(await pngquant({ - speed: 10, - quality: [0.8, 1], - })(buffer)); -} - -await test(); diff --git a/package.json b/package.json index a3d3305..da2af61 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "default": "./index.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "scripts": { - "test": "xo && npm run test:cover && tsd", + "test": "xo && npm run test:cover && tsc --noEmit index.d.ts", "test:cover": "c8 --check-coverage --statements 90 ava" }, "files": [ @@ -30,18 +30,18 @@ "pngquant" ], "dependencies": { + "environment": "^1.0.0", "execa": "^8.0.1", "is-png": "^3.0.1", - "is-stream": "^3.0.0", - "ow": "^1.1.1", - "pngquant-bin": "^8.0.1" + "ow": "^2.0.0", + "pngquant-bin": "^9.0.0", + "uint8array-extras": "^1.1.0" }, "devDependencies": { - "@types/node": "^20.8.3", - "c8": "^8.0.1", - "ava": "^5.3.1", - "get-stream": "^6.0.1", - "tsd": "^0.29.0", - "xo": "^0.56.0" + "@types/node": "^20.12.10", + "ava": "^6.1.3", + "c8": "^9.1.0", + "typescript": "^5.4.5", + "xo": "^0.58.0" } } diff --git a/readme.md b/readme.md index d8547a7..879f2df 100644 --- a/readme.md +++ b/readme.md @@ -1,43 +1,42 @@ -# imagemin-pngquant +# imagemin-pngquant > [Imagemin](https://github.com/imagemin/imagemin) plugin for [`pngquant`](https://github.com/kornelski/pngquant) - ## Install +```sh +npm install imagemin-pngquant ``` -$ npm install imagemin-pngquant -``` + ### Prerequisites + > **Linux** machines must have the following packages prior to install: `libpng-dev libimagequant-dev` -``` + +```sh sudo apt-get -y install libpng-dev libimagequant-dev ``` ## Usage ```js -const imagemin = require('imagemin'); -const imageminPngquant = require('imagemin-pngquant'); - -(async () => { - await imagemin(['images/*.png'], { - destination: 'build/images', - plugins: [ - imageminPngquant() - ] - }); - - console.log('Images optimized'); -})(); -``` +import imagemin from 'imagemin'; +import imageminPngquant from 'imagemin-pngquant'; +await imagemin(['images/*.png'], { + destination: 'build/images', + plugins: [ + imageminPngquant() + ] +}); + +console.log('Images optimized'); +``` ## API ### imageminPngquant(options?)(input) -Returns `Promise`. +Returns `Promise`. #### options @@ -45,23 +44,23 @@ Type: `object` ##### speed -Type: `number`
-Default: `4`
+Type: `number`\ +Default: `4`\ Values: `1` (brute-force) to `11` (fastest) Speed `10` has 5% lower quality, but is about 8 times faster than the default. Speed `11` disables dithering and lowers compression level. ##### strip -Type: `boolean`
+Type: `boolean`\ Default: `false` Remove optional metadata. ##### quality -Type: `Array`
-Values: `Array<0...1, 0...1>`
+Type: `Array`\ +Values: `Array<0...1, 0...1>`\ Example: `[0.3, 0.5]` Instructs pngquant to use the least amount of colors required to meet or exceed @@ -72,8 +71,8 @@ Min and max are numbers in range 0 (worst) to 1 (perfect), similar to JPEG. ##### dithering -Type: `number | boolean`
-Default: `1` (full)
+Type: `number | boolean`\ +Default: `1` (full)\ Values: `0...1` Set the dithering level using a fractional number between 0 (none) and 1 (full). @@ -86,15 +85,8 @@ Type: `number` Truncate number of least significant bits of color (per channel). Use this when image will be output on low-depth displays (e.g. 16-bit RGB). pngquant will make almost-opaque pixels fully opaque and will reduce amount of semi-transparent colors. -##### verbose - -Type: `boolean`
-Default: `false` - -Print verbose status messages. - #### input -Type: `Buffer | Stream` +Type: `Uint8Array` -Buffer or stream to optimize. +Image data to optimize. diff --git a/test.js b/test.js index 904c8a2..36cb914 100644 --- a/test.js +++ b/test.js @@ -1,54 +1,44 @@ import {promisify} from 'node:util'; import fs from 'node:fs'; import path from 'node:path'; -import url from 'node:url'; +import url, {fileURLToPath} from 'node:url'; import test from 'ava'; -import getStream from 'get-stream'; import isPng from 'is-png'; import imageminPngquant from './index.js'; const readFile = promisify(fs.readFile); -const thisDirname = url.fileURLToPath(new URL('.', import.meta.url)); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const thisFilename = url.fileURLToPath(import.meta.url); test('optimize a PNG', async t => { - const buffer = await readFile(path.join(thisDirname, 'fixture.png')); + const buffer = await readFile(path.join(__dirname, 'fixture.png')); const data = await imageminPngquant()(buffer); t.true(data.length < buffer.length); t.true(isPng(data)); }); test('support pngquant options', async t => { - const buffer = await readFile(path.join(thisDirname, 'fixture.png')); + const buffer = await readFile(path.join(__dirname, 'fixture.png')); const data = await imageminPngquant({ speed: 10, quality: [0.8, 1], - strip: true, + strip: false, dithering: false, posterize: 1, - verbose: false, })(buffer); t.true(data.length > (30 * 1000)); t.true(isPng(data)); }); -test('support streams', async t => { - const buffer = await readFile(path.join(thisDirname, 'fixture.png')); - const stream = fs.createReadStream(path.join(thisDirname, 'fixture.png')); - const data = await getStream.buffer(imageminPngquant()(stream)); - t.true(data.length < buffer.length); - t.true(isPng(data)); -}); - test('skip optimizing a non-PNG file', async t => { const buffer = await readFile(thisFilename); const data = await imageminPngquant()(buffer); t.is(data.length, buffer.length); }); -test('handles non-buffer, non-stream input', t => { +test('handles non-buffer, non-stream input', async t => { const badInput = {}; - return t.throwsAsync(() => imageminPngquant()(badInput), { - message: `Expected a Buffer or Stream, got ${typeof badInput}`, + await t.throwsAsync(imageminPngquant()(badInput), { + message: `Expected a Uint8Array, got ${typeof badInput}`, }); }); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 16b0ca1..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "strictNullChecks": true, - "module": "NodeNext", - "moduleResolution": "NodeNext" - } -}