6

我正在尝试imagemin使用 npm 脚本创建脚本并使用imagemin-cli它。首先,我将文件复制到dist(或.tmp用于开发)文件夹,然后使用以下脚本压缩图像:

包.json

...
scripts {
  "copy:dev": "cpx app/src/**/*.{html,png,jpg,mp4,webm} .tmp/",
  "copy:prod": "cpx app/src/**/*.{html,png,jpg,mp4,webm} dist/",
  "imagemin:dev": "imagemin app/src/images/**/* -o .tmp/images/",
  "imagemin:prod": "imagemin  app/src/images/**/* -o dist/images/",
  ...
},

因此,当我运行这些脚本时,压缩后所有图像都放在文件夹images/中。

有没有办法压缩图像并保持文件夹结构?也许与另一个插件或其他东西。

4

4 回答 4

8

这是一种在保持文件夹结构的情况下压缩图像的方法吗?

简短的回答是不,不是imagemin-cli

imageminAPI imagemin-cli是基于此构建的)不提供保留文件夹结构的机制。请参阅项目 github 存储库中的open issue/feature-request #191 。


解决方案

实现您的要求的跨平台方法是编写一个直接利用imagemin API的自定义node.js实用程序脚本。如此有效...构建您自己的 CLI 工具,可以通过.npm-scripts

以下要点显示了如何实现这一点......


imagemin.js

实用程序节点脚本如下:

#!/usr/bin/env node

'use strict';

var path = require('path');
var readline = require('readline');
var Imagemin = require('imagemin');

var outdir = process.env.PWD; // Default output folder.
var verbose = false; // Default no logging.

// The folder name specified MUST exist in the `glob` pattern of the npm-script.
var DEST_SUBROOT_FOLDER = 'images';

// Nice ticks for logging aren't supported via cmd.exe
var ticksymbol = process.env.npm_config_shell.indexOf('bash') !== -1 ? '✔' : '√';

var rl = readline.createInterface({
    input: process.stdin,
    output: null,
    terminal: false
});

// Handle the optional `-o` argument for the destination folder.
if (process.argv.indexOf('-o') !== -1) {
    outdir = process.argv[process.argv.indexOf('-o') + 1];
}

// Handle the optional `-v` argument for verbose logging.
if (process.argv.indexOf('-v') !== -1) {
    verbose = true;
}

/**
 * Utilizes the Imagemin API to create a new instance for optimizing each image.
 * @param {String} srcpath - The filepath of the source image to optimize.
 * @param {String} destpath - The destination path to save the resultant file.
 * @param {Function} - The relevent `use` plugin (jpegtran|optipng|gifsicle).
 */
function imagemin(srcpath, destpath, plugin) {
    var im = new Imagemin()
        .src(srcpath)
        .dest(destpath)
        .use(plugin);

    im.optimize(function (err, file) {
        if (err) {
            console.error('Error: ' + err);
            process.exit(1);
        }
        if (file && verbose) {
            console.log('\x1b[32m%s\x1b[0m', ticksymbol, destpath);
        }
    });
}

/**
 * Obtains the destination path and file suffix from the original source path.
 * @param {String} srcpath - The filepath for the image to optimize.
 * @return {{dest: String, type: String}} dest path and ext (.jpg|.png|.gif).
 */
function getPathInfo(srcpath) {
    var ext = path.extname(srcpath),
        parts = srcpath.split(path.sep),
        subpath = parts.slice(parts.indexOf(DEST_SUBROOT_FOLDER), parts.length);

    subpath.unshift(outdir);

    return {
        dest: path.normalize(subpath.join(path.sep)),
        ext: ext
    };
}

/**
 * Triggers the relevent imagemin process according to file suffix (jpg|png|gif).
 * @param {String} srcpath - The filepath of the image to optimize.
 */
function optimizeImage(srcpath) {
    var p = getPathInfo(srcpath);

    switch (p.ext) {
    case '.jpg':
        imagemin(srcpath, p.dest, Imagemin.jpegtran({ progressive: true }));
        break;
    case '.png':
        imagemin(srcpath, p.dest, Imagemin.optipng({ optimizationLevel: 5 }));
        break;
    case '.gif':
        imagemin(srcpath, p.dest, Imagemin.gifsicle({ interlaced: true }));
        break;
    }
}

// Read each line from process.stdin (i.e. the filepath)
rl.on('line', function(srcpath) {
    optimizeImage(srcpath);
});

注意: 上面的代码使用 API 的版本1.0.5imagemin而不是最新版本 - 为什么?请参阅下面“附加说明”部分下的第 1 点。)


卸载和安装新软件包

  1. 首先卸载imagemin-cli,因为它不再需要:

$ npm un -D imagemin-cli

  1. 下一个安装imagemin版本(这是一个较旧的软件包,因此可能需要比平时更长的时间安装)1.0.5 npm

$ npm i -D imagemin@1.0.5

  1. 然后安装cli-glob。这将用于指定 glob 模式以匹配图像以进行优化。

$ npm i -D cli-glob


npm 脚本

更新您npm-scripts的如下:

...
"scripts": {
    "imagemin:prod": "glob \"app/src/images/**/*.{png,jpg,gif}\" | node bin/imagemin -v -o dist",
    "imagemin:dev": "glob \"app/src/images/**/*.{png,jpg,gif}\" | node bin/imagemin -v -o .tmp",
    ...
},
...

注意: 要使用上面显示的要点优化图像,不必使用原始帖子/问题中命名copy:prod和显示的两个脚本)copy:dev

  1. 上面的glob \"app/src/...脚本部分使用cli-glob来匹配必要的图像源文件。

  2. 然后将路径通过管道传送到imagemin.js实用程序节点脚本。

  3. 当包含-v(详细)参数/标志时,每个处理的图像都会记录到控制台。要省略日志记录,只需删除-v标志。

  4. -o输出)参数/标志用于指定目标文件夹名称。例如dist.tmp。当-o省略值时,生成的图像将输出到项目根目录。


补充说明:

  1. 使用imagemin版本1.0.5的原因是因为此 API 允许将src值指定为单个文件路径。在大于2.0.0API 的版本中,该src值应为 glob 模式,如最新版本5.2.2中所示。

  2. 上面假设的要点imagemin.js被保存到一个名为的文件夹bin中,该文件夹与package.json. 可以将其更改为首选名称,或通过在其前面加上点 [.] 例如.scripts.bin. 无论您选择什么,都需要相应地更新脚本的路径npm-scripts

于 2017-03-02T17:02:12.067 回答
6

2020 年更新

Gijs Rogé有一个未合并的(截至 2020 年 6 月中旬)拉取请求,可以在输出目录中保留目录结构。

您可以通过直接从 Github 安装、引用 repo 甚至特定提交来安装注册表中尚未列出的 npm 模块:
npm install https://github.com/<username>/<repository>#<commit> --save-dev

要使用 Gijs Rogé 的修复程序安装 imagemin,请运行...
npm install https://github.com/imagemin/imagemin#bfd7c547045f68ed92243c6a772f6265a08a687f --save-dev

...并通过设置启用脚本中的新选项preserveDirectories: true

// Note: imports and plugin configs have been omitted for brevity

const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');
...

(async () => {
    const files = await imagemin(['input_dir/**/*.{jpg,jpeg,png,svg}'], {
    destination: 'output_dir/',
    ✨preserveDirectories: true,
        plugins: [
            imageminMozjpeg( ... ),
            imageminPngquant( ... ),
            imageminSvgo( ... )
        ]
});

现在将处理.jpg找到的 ininput_dir/some/sub/dir/image.jpg并将其写入output_dir/input_dir/some/sub/dir/image.jpg.

用于destination: '.'在原地覆盖原始文件。

于 2020-06-17T10:28:36.880 回答
0

我也有同样的问题,但我在节点模块中更改了 imagemin 的 index.js 文件。请将代码复制粘贴到节点模块中

'use strict';
const fs = require('fs');
const path = require('path');
const fileType = require('file-type');
const globby = require('globby');
const makeDir = require('make-dir');
const pify = require('pify');
const pPipe = require('p-pipe');
const replaceExt = require('replace-ext');

const fsP = pify(fs);

const handleFile = (input, output, options) => fsP.readFile(input).then(data => {
	const dest = output ? output : null;

	if (options.plugins && !Array.isArray(options.plugins)) {
		throw new TypeError('The `plugins` option should be an `Array`');
	}

	const pipe = options.plugins.length > 0 ? pPipe(options.plugins)(data) : Promise.resolve(data);

	return pipe
		.then(buffer => {
			const ret = {
				data: buffer,
				path: (fileType(buffer) && fileType(buffer).ext === 'webp') ? replaceExt(dest, '.webp') : dest
			};

			if (!dest) {
				return ret;
			}

			return fsP.writeFile(ret.path, ret.data)
				.then(() => ret)
				.then(function(result) {})
		})
		.catch(error => {
			error.message = `Error in file: ${input}\n\n${error.message}`;
			throw error;
		});
});

module.exports = (input, output, options) => {
	if (!Array.isArray(input)) {
		return Promise.reject(new TypeError(`Expected an \`Array\`, got \`${typeof input}\``));
	}

	if (typeof output === 'object') {
		options = output;
		output = null;
	}

	options = Object.assign({plugins: []}, options);
	options.plugins = options.use || options.plugins;

	return globby(input, {onlyFiles: true}).then(paths => Promise.all(paths.map(x => handleFile(x, output, options))));
};

module.exports.buffer = (input, options) => {
	if (!Buffer.isBuffer(input)) {
		return Promise.reject(new TypeError(`Expected a \`Buffer\`, got \`${typeof input}\``));
	}

	options = Object.assign({plugins: []}, options);
	options.plugins = options.use || options.plugins;

	if (options.plugins.length === 0) {
		return Promise.resolve(input);
	}

	return pPipe(options.plugins)(input);
};

于 2019-03-07T19:28:45.190 回答
0

以下脚本imagemin为每个文件夹运行单独的作业。

它解决了同样的问题。

const path = require('path');
const fs = require('fs');
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');

const COMPRESSED_FOLDER = '__compressed';
const TIMER_NAME = 'compressed';

(async () => {
  console.time(TIMER_NAME);
  const publicPath = path.resolve(__dirname, '../public');
  const compressedFolderRegExp = new RegExp(COMPRESSED_FOLDER);
  const publicPathRegExp = new RegExp(publicPath);
  const folders = getAllDirectories(publicPath).filter(
    (directoryName) => !directoryName.match(compressedFolderRegExp)
  );

  await Promise.all(
    folders.map(async (folderPath) => {
      const destination = folderPath.replace(
        publicPathRegExp,
        `${publicPath}/${COMPRESSED_FOLDER}`
      );

      console.log('compressing...', destination);

      return imagemin([`${folderPath}/*.{jpg,png}`], {
        destination,
        plugins: [imageminWebp({ quality: 50 })],
      });
    })
  );

  console.timeEnd(TIMER_NAME);

  process.exit();
})();

function getAllDirectories(filepath) {
  const directoryPaths = fs
    .readdirSync(filepath, { withFileTypes: true })
    .filter((d) => d.isDirectory())
    .map(({ name }) => `${filepath}/${name}`);
  const childDirectories = directoryPaths.reduce(
    (acc, directoryPath) => acc.concat(getAllDirectories(directoryPath)),
    []
  );

  return [filepath, ...childDirectories];
}

于 2021-01-18T15:55:11.077 回答