【问题标题】:is it possible to use imagemin-cli and keep the same folder structure of compressing files?是否可以使用 imagemin-cli 并保持压缩文件的相同文件夹结构?
【发布时间】:2017-07-20 11:56:47
【问题描述】:

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

package.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/ 中。

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

【问题讨论】:

    标签: npm-scripts imagemin


    【解决方案1】:

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

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

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


    解决方案

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

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


    imagemin.js

    实用程序node脚本如下:

    #!/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);
    });
    

    注意: 上面的代码使用了imagemin API 的1.0.5 版本,而不是最新版本 - 为什么?请参阅下面“附加说明”部分下的第 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:prodcopy: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.0 的版本中,API 期望src 值是一个全局模式,如最新版本5.2.2 中所示。

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

    【讨论】:

      【解决方案2】:

      2020 年更新

      Gijs Rogé 的未合并(截至 2020 年 6 月中旬)pull request 可以在输出目录中保留目录结构。

      您可以通过直接从 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( ... )
              ]
      });
      

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

      使用destination: '.' 覆盖原文件。

      【讨论】:

        【解决方案3】:

        我也有同样的问题,但我在节点模块中更改了 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);
        };

        【讨论】:

          【解决方案4】:

          以下脚本为每个文件夹运行单独的 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];
          }
          
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多