UNPKG

pngquant

Version:

The pngquant utility as a readable/writable stream

236 lines (210 loc) 6.45 kB
'use strict'; var childProcess = require('child_process'); var stream = require('stream'); var util = require('util'); var which = require('which'); var memoizeAsync = require('memoizeasync'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var childProcess__default = /*#__PURE__*/_interopDefaultLegacy(childProcess); var util__default = /*#__PURE__*/_interopDefaultLegacy(util); var which__default = /*#__PURE__*/_interopDefaultLegacy(which); var memoizeAsync__default = /*#__PURE__*/_interopDefaultLegacy(memoizeAsync); function PngQuant(pngQuantArgs) { stream.Stream.call(this); this.pngQuantArgs = pngQuantArgs; if (!this.pngQuantArgs || this.pngQuantArgs.length === 0) { this.pngQuantArgs = [256]; } this.writable = this.readable = true; this.hasEnded = false; this.seenDataOnStdout = false; } util__default["default"].inherits(PngQuant, stream.Stream); PngQuant.getBinaryPath = memoizeAsync__default["default"]((cb) => { if (PngQuant.binaryPath !== undefined) { setImmediate(() => { cb(null, PngQuant.binaryPath); }); return; } which__default["default"]('pngquant', async (err, pngQuantBinaryPath) => { if (err) { const pngQuantModule = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('pngquant-bin')); }); pngQuantBinaryPath = pngQuantModule.default; } if (pngQuantBinaryPath) { cb(null, pngQuantBinaryPath); } else { cb( new Error( 'No pngquant binary in PATH and pngquant-bin does not provide a pre-built binary for your architecture' ) ); } }); }); PngQuant.setBinaryPath = (binaryPath) => { PngQuant.binaryPath = binaryPath; }; PngQuant.prototype._error = function (err) { if (!this.hasEnded) { this.hasEnded = true; this.cleanUp(); this.emit('error', err); } }; PngQuant.prototype.cleanUp = function () { if (this.pngQuantProcess) { this.pngQuantProcess.kill(); this.pngQuantProcess = null; } this.bufferedChunks = null; }; PngQuant.prototype.destroy = function () { if (!this.hasEnded) { this.hasEnded = true; this.cleanUp(); this.bufferedChunks = null; } }; PngQuant.prototype.write = function (chunk) { if (this.hasEnded) { return; } if (!this.pngQuantProcess && !this.bufferedChunks) { this.bufferedChunks = []; PngQuant.getBinaryPath((err, pngCrushBinaryPath) => { if (this.hasEnded) { return; } if (err) { return this._error(err); } this.commandLine = pngCrushBinaryPath + (this.pngQuantArgs ? ` ${this.pngQuantArgs.join(' ')}` : ''); // For debugging this.pngQuantProcess = childProcess__default["default"].spawn( pngCrushBinaryPath, this.pngQuantArgs, { windowsHide: true, } ); this.pngQuantProcess.once('error', this._error.bind(this)); this.pngQuantProcess.stdin.once('error', this._error.bind(this)); this.pngQuantProcess.stdout.once('error', this._error.bind(this)); this.pngQuantProcess.stderr.on('data', (data) => { if (!this.hasEnded) { this._error( new Error( `Saw pngquant output on stderr: ${data.toString('ascii')}` ) ); this.hasEnded = true; } }); this.pngQuantProcess.once('exit', (exitCode) => { if (this.hasEnded) { return; } if (exitCode > 0 && !this.hasEnded) { this._error( new Error( `The pngquant process exited with a non-zero exit code: ${exitCode}` ) ); this.hasEnded = true; } }); this.pngQuantProcess.stdout .on('data', (chunk) => { this.seenDataOnStdout = true; this.emit('data', chunk); }) .once('end', () => { this.pngQuantProcess = null; if (!this.hasEnded) { if (this.seenDataOnStdout) { this.emit('end'); } else { this._error( new Error( 'PngQuant: The stdout stream ended without emitting any data' ) ); } this.hasEnded = true; } }); if (this.pauseStdoutOfPngQuantProcessAfterStartingIt) { this.pngQuantProcess.stdout.pause(); } this.bufferedChunks.forEach(function (bufferedChunk) { // In node.js 12.6.0+ the error event be invoked syncronously, which means // that we might clean up between the chunks. Guard against dereferencing // 'stdin' of null in that case: if (this.pngQuantProcess) { if (bufferedChunk === null) { this.pngQuantProcess.stdin.end(); } else { this.pngQuantProcess.stdin.write(bufferedChunk); } } }, this); this.bufferedChunks = null; }); } if (this.bufferedChunks) { this.bufferedChunks.push(chunk); } else { this.pngQuantProcess.stdin.write(chunk); } }; PngQuant.prototype.end = function (chunk) { if (this.hasEnded) { return; } if (chunk) { this.write(chunk); } else if (!this.pngQuantProcess) { // No chunks have been rewritten. Write an empty one to make sure there's pngquant process. this.write(Buffer.from([])); } if (this.bufferedChunks) { this.bufferedChunks.push(null); } else { this.pngQuantProcess.stdin.end(); } }; PngQuant.prototype.pause = function () { if (this.pngQuantProcess) { this.pngQuantProcess.stdout.pause(); } else { this.pauseStdoutOfPngQuantProcessAfterStartingIt = true; } }; PngQuant.prototype.resume = function () { if (this.pngQuantProcess) { this.pngQuantProcess.stdout.resume(); } else { this.pauseStdoutOfPngQuantProcessAfterStartingIt = false; } }; module.exports = PngQuant;