diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76c979b..087252a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,12 +14,12 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -32,12 +32,12 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -50,12 +50,12 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..71fc9a2 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,21 @@ +import globals from "globals"; +import js from "@eslint/js"; + +export default [ + js.configs.recommended, + { + languageOptions: { + globals: { + ...globals.browser + } + }, + rules: { + "no-const-assign": "error", + "no-unused-vars": "warn", + "no-console": "error", + "prefer-const": "error", + "no-eq-null": "error", + "no-var": "error" + } + }, +]; diff --git a/index.js b/index.js index c2fd802..3643d2e 100644 --- a/index.js +++ b/index.js @@ -1,62 +1,66 @@ -'use strict'; -const execBuffer = require('exec-buffer'); -const isPng = require('is-png'); -const optipng = require('optipng-bin'); +import { execa } from "execa"; +import { Buffer } from "buffer"; +import isPng from "is-png"; +import tmp from 'tmp'; +import fs from 'fs' +import optipng from "optipng-bin"; -module.exports = options => async buffer => { +export default (options) => async (buffer) => { options = { optimizationLevel: 3, + stripMetadata: true, bitDepthReduction: true, colorTypeReduction: true, paletteReduction: true, interlaced: false, errorRecovery: true, - ...options + ...options, }; if (!Buffer.isBuffer(buffer)) { - throw new TypeError('Expected a buffer'); + throw new TypeError("Expected a buffer"); } if (!isPng(buffer)) { return buffer; } - const arguments_ = [ - '-strip', - 'all', - '-clobber', - '-o', - options.optimizationLevel, - '-out', - execBuffer.output - ]; + const inputFile = tmp.fileSync(); + const outputFile = tmp.fileSync(); + + fs.writeSync(inputFile.fd, buffer); + + const args = [ + "-clobber", + "-o", options.optimizationLevel, + inputFile.name, + "-out", outputFile.name + ] + + if (options.stripMetadata) { + args.push("-strip", "all") + } if (options.errorRecovery) { - arguments_.push('-fix'); + args.push("-fix"); } if (!options.bitDepthReduction) { - arguments_.push('-nb'); + args.push("-nb"); } - if (typeof options.interlaced === 'boolean') { - arguments_.push('-i', options.interlaced ? '1' : '0'); + if (typeof options.interlaced === "boolean") { + args.push("-i", options.interlaced ? "1" : "0"); } if (!options.colorTypeReduction) { - arguments_.push('-nc'); + args.push("-nc"); } if (!options.paletteReduction) { - arguments_.push('-np'); + args.push("-np"); } - arguments_.push(execBuffer.input); - - return execBuffer({ - input: buffer, - bin: optipng, - args: arguments_ - }); + await execa(optipng, args); + return fs.readFileSync(outputFile.name); }; diff --git a/package.json b/package.json index 2678f22..953788b 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "version": "8.0.0", "description": "Imagemin plugin for OptiPNG", "license": "MIT", + "type": "module", "repository": "imagemin/imagemin-optipng", "engines": { - "node": ">=10" + "node": ">=18" }, "scripts": { - "test": "xo && ava" + "test": "eslint && ava" }, "files": [ "index.js" @@ -23,12 +24,15 @@ "png" ], "dependencies": { - "exec-buffer": "^3.0.0", - "is-png": "^2.0.0", - "optipng-bin": "^7.0.0" + "execa": "^9.3.1", + "is-png": "3.0.1", + "optipng-bin": "^9.0.0", + "tmp": "^0.2.3" }, "devDependencies": { - "ava": "^3.8.0", - "xo": "^0.30.0" + "@eslint/js": "^9.9.0", + "ava": "^6.1.3", + "eslint": "^9.9.0", + "globals": "^15.9.0" } } diff --git a/readme.md b/readme.md index 1c62710..b798b11 100644 --- a/readme.md +++ b/readme.md @@ -62,6 +62,13 @@ Default: `true` Apply bit depth reduction. +##### stripMetadata + +Type: `boolean`\ +Default: `true` + +Strip Metadata. + ##### colorTypeReduction Type: `boolean`\ diff --git a/test.js b/test.js index 4a53ce7..94fc4ae 100644 --- a/test.js +++ b/test.js @@ -1,11 +1,11 @@ -const fs = require('fs'); -const path = require('path'); -const isPng = require('is-png'); -const test = require('ava'); -const optipng = require('.'); +import fs from 'fs'; +import path from 'path'; +import test from 'ava'; +import optipng from './index.js'; +import isPng from 'is-png'; -const fixture = fs.readFileSync(path.join(__dirname, 'fixture.png')); -const fixtureBroken = fs.readFileSync(path.join(__dirname, 'fixture_broken.png')); +const fixture = fs.readFileSync(path.resolve('fixture.png')); +const fixtureBroken = fs.readFileSync(path.resolve('fixture_broken.png')); test('optimize a PNG', async t => { const data = await optipng()(fixture); @@ -14,52 +14,55 @@ test('optimize a PNG', async t => { }); test('throw on empty input', async t => { - await t.throwsAsync(optipng()(), {message: /Expected a buffer/}); + await t.throwsAsync(optipng()(), { message: /Expected a buffer/ }); }); test('bitDepthReduction option', async t => { - await t.notThrowsAsync(optipng({bitDepthReduction: true})(fixture)); + await t.notThrowsAsync(optipng({ bitDepthReduction: true })(fixture)); }); test('colorTypeReduction option', async t => { - await t.notThrowsAsync(optipng({colorTypeReduction: true})(fixture)); + await t.notThrowsAsync(optipng({ colorTypeReduction: true })(fixture)); }); test('paletteReduction option', async t => { - await t.notThrowsAsync(optipng({paletteReduction: true})(fixture)); + await t.notThrowsAsync(optipng({ paletteReduction: true })(fixture)); }); test('errorRecovery default', async t => { const data = await optipng()(fixtureBroken); - t.true(isPng(data)); + t.true(isPng(data), "return data is not a PNG."); }); test('errorRecovery explicit', async t => { - const data = await optipng({errorRecovery: true})(fixtureBroken); - t.true(isPng(data)); + const data = await optipng({ errorRecovery: true })(fixtureBroken); + t.true(isPng(data), "return data is not a PNG."); }); test('errorRecovery is set to false', async t => { - await t.throwsAsync(optipng({errorRecovery: false})(fixtureBroken)); + await t.throwsAsync(optipng({ errorRecovery: false })(fixtureBroken)); }); test('interlaced is set to true', async t => { const [data1, data2] = await Promise.all([ - optipng({interlaced: true})(fixture), + optipng({ interlaced: true })(fixture), optipng()(fixture) ]); - t.true(isPng(data1)); - t.true(data1.length > data2.length); + t.true(isPng(data1), "return data is not a PNG."); + t.true(data1.length > data2.length, "interlaced image should be bigger"); }); test('interlaced is set to undefined and null', async t => { const [data1, data2, data3] = await Promise.all([ - optipng({interlaced: undefined})(fixture), - optipng({interlaced: null})(fixture), - optipng({interlaced: true})(fixture) + optipng({ interlaced: undefined })(fixture), + optipng({ interlaced: null })(fixture), + optipng({ interlaced: true })(fixture) ]); - t.true(isPng(data1) && isPng(data2)); - t.true(data1.length === data2.length && data1.length < data3.length); + t.true(isPng(data1), "return data1 is not a PNG."); + t.true(isPng(data2), "return data2 is not a PNG."); + t.true(isPng(data3), "return data3 is not a PNG."); + t.true(data1.length < data3.length, "interlaced image should be bigger"); + t.true(data1.length === data2.length, "interlacing options 'undefined' and 'null' should be equal"); });