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 Nov 2, 2023
1 parent 2f7ecc9 commit 70e07e8
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 107 deletions.
3 changes: 3 additions & 0 deletions .github/security.md
@@ -0,0 +1,3 @@
# Security Policy

To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
7 changes: 4 additions & 3 deletions .github/workflows/main.yml
Expand Up @@ -10,10 +10,11 @@ jobs:
fail-fast: false
matrix:
node-version:
- 16
- 20
- 18
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
120 changes: 50 additions & 70 deletions index.js
@@ -1,30 +1,27 @@
import {createRequire} from 'node:module';
import path from 'node:path';
import process from 'node:process';
import log from 'fancy-log';
import PluginError from 'plugin-error';
import through from 'through2-concurrent';
import prettyBytes from 'pretty-bytes';
import chalk from 'chalk';
import imagemin from 'imagemin';
import plur from 'plur';

const require = createRequire(import.meta.url);
import {gulpPlugin} from 'gulp-plugin-extras';

const PLUGIN_NAME = 'gulp-imagemin';
const defaultPlugins = ['gifsicle', 'mozjpeg', 'optipng', 'svgo'];

const loadPlugin = (plugin, ...args) => {
const loadPlugin = async (pluginName, ...arguments_) => {
try {
return require(`imagemin-${plugin}`)(...args);
} catch {
log(`${PLUGIN_NAME}: Could not load default plugin \`${plugin}\``);
const {default: plugin} = await import(`imagemin-${pluginName}`);
return plugin(...arguments_);
} catch (error) {
console.log('er', error);
console.log(`${PLUGIN_NAME}: Could not load default plugin \`${pluginName}\``);
}
};

const exposePlugin = plugin => (...args) => loadPlugin(plugin, ...args);
const exposePlugin = async plugin => (...arguments_) => loadPlugin(plugin, ...arguments_);

const getDefaultPlugins = () => defaultPlugins.flatMap(plugin => loadPlugin(plugin));
const getDefaultPlugins = async () => Promise.all(defaultPlugins.flatMap(plugin => loadPlugin(plugin)));

export default function gulpImagemin(plugins, options) {
if (typeof plugins === 'object' && !Array.isArray(plugins)) {
Expand All @@ -45,75 +42,58 @@ export default function gulpImagemin(plugins, options) {
let totalSavedBytes = 0;
let totalFiles = 0;

return through.obj({
maxConcurrency: 8,
}, (file, encoding, callback) => {
if (file.isNull()) {
callback(null, file);
return;
return gulpPlugin('gulp-imagemin', async file => {
if (!validExtensions.has(path.extname(file.path).toLowerCase())) {
if (options.verbose) {
console.log(`${PLUGIN_NAME}: Skipping unsupported image ${chalk.blue(file.relative)}`);
}

return file;
}

if (file.isStream()) {
callback(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
return;
if (Array.isArray(plugins)) {
plugins = await Promise.all(plugins);
}

if (!validExtensions.has(path.extname(file.path).toLowerCase())) {
if (options.verbose) {
log(`${PLUGIN_NAME}: Skipping unsupported image ${chalk.blue(file.relative)}`);
}
const localPlugins = plugins ?? await getDefaultPlugins();
const data = await imagemin.buffer(file.contents, {plugins: localPlugins});
const originalSize = file.contents.length;
const optimizedSize = data.length;
const saved = originalSize - optimizedSize;
const percent = originalSize > 0 ? (saved / originalSize) * 100 : 0;
const savedMessage = `saved ${prettyBytes(saved)} - ${percent.toFixed(1).replace(/\.0$/, '')}%`;
const message = saved > 0 ? savedMessage : 'already optimized';

if (saved > 0) {
totalBytes += originalSize;
totalSavedBytes += saved;
totalFiles++;
}

callback(null, file);
return;
if (options.verbose) {
console.log(`${PLUGIN_NAME}:`, chalk.green('✔ ') + file.relative + chalk.gray(` (${message})`));
}

const localPlugins = plugins || getDefaultPlugins();

(async () => {
try {
const data = await imagemin.buffer(file.contents, {
plugins: localPlugins,
});
const originalSize = file.contents.length;
const optimizedSize = data.length;
const saved = originalSize - optimizedSize;
const percent = originalSize > 0 ? (saved / originalSize) * 100 : 0;
const savedMessage = `saved ${prettyBytes(saved)} - ${percent.toFixed(1).replace(/\.0$/, '')}%`;
const message = saved > 0 ? savedMessage : 'already optimized';

if (saved > 0) {
totalBytes += originalSize;
totalSavedBytes += saved;
totalFiles++;
}
file.contents = data;

if (options.verbose) {
log(`${PLUGIN_NAME}:`, chalk.green('✔ ') + file.relative + chalk.gray(` (${message})`));
return file;
}, {
async * onFinish() { // eslint-disable-line require-yield
if (!options.silent) {
const percent = totalBytes > 0 ? (totalSavedBytes / totalBytes) * 100 : 0;
let message = `Minified ${totalFiles} ${plur('image', totalFiles)}`;

if (totalFiles > 0) {
message += chalk.gray(` (saved ${prettyBytes(totalSavedBytes)} - ${percent.toFixed(1).replace(/\.0$/, '')}%)`);
}

file.contents = data;
callback(null, file);
} catch (error) {
callback(new PluginError(PLUGIN_NAME, error, {fileName: file.path}));
}
})();
}, callback => {
if (!options.silent) {
const percent = totalBytes > 0 ? (totalSavedBytes / totalBytes) * 100 : 0;
let message = `Minified ${totalFiles} ${plur('image', totalFiles)}`;

if (totalFiles > 0) {
message += chalk.gray(` (saved ${prettyBytes(totalSavedBytes)} - ${percent.toFixed(1).replace(/\.0$/, '')}%)`);
console.log(`${PLUGIN_NAME}:`, message);
}

log(`${PLUGIN_NAME}:`, message);
}

callback();
},
});
}

export const gifsicle = exposePlugin('gifsicle');
export const mozjpeg = exposePlugin('mozjpeg');
export const optipng = exposePlugin('optipng');
export const svgo = exposePlugin('svgo');
export const gifsicle = await exposePlugin('gifsicle');
export const mozjpeg = await exposePlugin('mozjpeg');
export const optipng = await exposePlugin('optipng');
export const svgo = await exposePlugin('svgo');
23 changes: 10 additions & 13 deletions package.json
Expand Up @@ -13,7 +13,7 @@
"type": "module",
"exports": "./index.js",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"scripts": {
"test": "xo && ava"
Expand All @@ -38,26 +38,23 @@
"svg"
],
"dependencies": {
"chalk": "^4.1.2",
"fancy-log": "^1.3.3",
"chalk": "^5.3.0",
"gulp-plugin-extras": "^0.2.2",
"imagemin": "^8.0.1",
"plugin-error": "^1.0.1",
"plur": "^4.0.0",
"pretty-bytes": "^5.6.0",
"through2-concurrent": "^2.0.0"
"plur": "^5.1.0",
"pretty-bytes": "^6.1.1"
},
"devDependencies": {
"ava": "^3.15.0",
"get-stream": "^6.0.1",
"ava": "^5.3.1",
"imagemin-pngquant": "^9.0.2",
"vinyl": "^2.2.1",
"xo": "^0.44.0"
"vinyl": "^3.0.0",
"xo": "^0.56.0"
},
"optionalDependencies": {
"imagemin-gifsicle": "^7.0.0",
"imagemin-mozjpeg": "^9.0.0",
"imagemin-mozjpeg": "^10.0.0",
"imagemin-optipng": "^8.0.0",
"imagemin-svgo": "^9.0.0"
"imagemin-svgo": "^10.0.1"
},
"peerDependencies": {
"gulp": ">=4"
Expand Down
33 changes: 22 additions & 11 deletions readme.md
Expand Up @@ -6,8 +6,8 @@

## Install

```
$ npm install --save-dev gulp-imagemin
```sh
npm install --save-dev gulp-imagemin
```

## Usage
Expand All @@ -28,15 +28,23 @@ export default () => (
### Custom plugin options

```js
import imagemin, {gifsicle, mozjpeg, optipng, svgo} from 'gulp-imagemin';

//
.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.mozjpeg({quality: 75, progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
gifsicle({interlaced: true}),
mozjpeg({quality: 75, progressive: true}),
optipng({optimizationLevel: 5}),
svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
{
name: 'removeViewBox',
active: true
},
{
name: 'cleanupIDs',
active: false
}
]
})
]))
Expand All @@ -46,12 +54,15 @@ export default () => (
### Custom plugin options and custom `gulp-imagemin` options

```js
import imagemin, {svgo} from 'gulp-imagemin';

//
.pipe(imagemin([
imagemin.svgo({
svgo({
plugins: [
{
removeViewBox: true
name: 'removeViewBox',
active: true
}
]
})
Expand Down Expand Up @@ -79,7 +90,7 @@ Unsupported files are ignored.
#### plugins

Type: `Array`\
Default: `[imagemin.gifsicle(), imagemin.mozjpeg(), imagemin.optipng(), imagemin.svgo()]`
Default: `[gifsicle(), mozjpeg(), optipng(), svgo()]`

[Plugins](https://www.npmjs.com/browse/keyword/imageminplugin) to use. This will completely overwrite all the default plugins. So, if you want to use custom plugins and you need some of defaults too, then you should pass default plugins as well. Note that the default plugins come with good defaults and should be sufficient in most cases. See the individual plugins for supported options.

Expand Down
19 changes: 9 additions & 10 deletions test.js
Expand Up @@ -3,7 +3,6 @@ import path from 'node:path';
import {fileURLToPath} from 'node:url';
import imageminPngquant from 'imagemin-pngquant';
import Vinyl from 'vinyl';
import getStream from 'get-stream';
import test from 'ava';
import gulpImagemin, {mozjpeg, svgo} from './index.js';

Expand All @@ -23,7 +22,7 @@ const createFixture = async (plugins, file = 'fixture.png') => {

test('minify images', async t => {
const {buffer, stream} = await createFixture();
const file = await getStream.array(stream);
const file = await stream.toArray();

t.true(file[0].contents.length < buffer.length);
});
Expand All @@ -35,16 +34,16 @@ test('minify JPEG with custom settings', async t => {
smooth: 45,
};
const {buffer, stream} = await createFixture([mozjpeg(mozjpegOptions)], 'fixture.jpg');
const file = await getStream.array(stream);
const file = await stream.toArray();

t.true(file[0].contents.length < buffer.length);
});

test('use custom plugins', async t => {
const {stream} = await createFixture([imageminPngquant()]);
const compareStream = (await createFixture()).stream;
const file = await getStream.array(stream);
const compareFile = await getStream.array(compareStream);
const {stream: compareStream} = await createFixture();
const file = await stream.toArray();
const compareFile = await compareStream.toArray();

t.true(file[0].contents.length < compareFile[0].contents.length);
});
Expand All @@ -57,17 +56,17 @@ test('use custom svgo settings', async t => {
},
};
const {stream} = await createFixture([svgo(svgoOptions)], 'fixture-svg-logo.svg');
const compareStream = (await createFixture(null, 'fixture-svg-logo.svg')).stream;
const file = await getStream.array(stream);
const compareFile = await getStream.array(compareStream);
const {stream: compareStream} = await createFixture(null, 'fixture-svg-logo.svg');
const file = await stream.toArray();
const compareFile = await compareStream.toArray();

t.true(file[0].contents.length > compareFile[0].contents.length);
});

test('skip unsupported images', async t => {
const stream = gulpImagemin();
stream.end(new Vinyl({path: path.join(__dirname, 'fixture.bmp')}));
const file = await getStream.array(stream);
const file = await stream.toArray();

t.is(file[0].contents, null);
});

0 comments on commit 70e07e8

Please sign in to comment.