Skip to content

Commit

Permalink
Require Node.js 18
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed May 7, 2024
1 parent 5ca38bd commit 981b216
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 144 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
11 changes: 2 additions & 9 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Buffer>;
export type Plugin = (input: Uint8Array) => Promise<Uint8Array>;

/**
Imagemin plugin for pngquant.
Expand Down
75 changes: 33 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
@@ -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};
18 changes: 0 additions & 18 deletions index.test-d.ts

This file was deleted.

22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -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"
}
}
64 changes: 28 additions & 36 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,66 @@
# 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<Buffer>`.
Returns `Promise<Uint8Array>`.

#### options

Type: `object`

##### speed

Type: `number`<br>
Default: `4`<br>
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`<br>
Type: `boolean`\
Default: `false`

Remove optional metadata.

##### quality

Type: `Array<min: number, max: number>`<br>
Values: `Array<0...1, 0...1>`<br>
Type: `Array<min: number, max: number>`\
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
Expand All @@ -72,8 +71,8 @@ Min and max are numbers in range 0 (worst) to 1 (perfect), similar to JPEG.

##### dithering

Type: `number | boolean`<br>
Default: `1` (full)<br>
Type: `number | boolean`\
Default: `1` (full)\
Values: `0...1`

Set the dithering level using a fractional number between 0 (none) and 1 (full).
Expand All @@ -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`<br>
Default: `false`

Print verbose status messages.

#### input

Type: `Buffer | Stream`
Type: `Uint8Array`

Buffer or stream to optimize.
Image data to optimize.
Loading

0 comments on commit 981b216

Please sign in to comment.