【前端工程化】我开源了个高效的图片压缩工具 - fast-imagemin-cli

Hello,我是Xc,一位因antfu结缘开源的前端菜鸟,今天和大家分享最近开源的一个高效的图片压缩工具fast-imagemin-cli。

什么是图片压缩

顾名思义就是压缩图片大小,有效的图片压缩会优化网页的加载,我们常见的图片压缩处理主要有以下2种:

  • 借助tinypng等图片压缩网站或者工具提前压缩好再放入项目中
  • 借助脚手架插件在编译时进行压缩输出

为什么做这个工具

源于实际的需求吧,部分类型项目存在较多的图片资源,基于上面2种方案先说下在生产中存在的一些问题吧。

  • 借助压缩工具:由于项目是多人开发,可能存在有时候忘记压缩的情况,导致一个较大的图片资源直接给编译到生产中了,影响了网页的加载。
  • 借助脚手架插件:因为图片资源较多,每次编译时在图片压缩上都消耗大量时间去做重复的事情。

如何做这个工具

就在思考要如何解决这类问题时候,正好在 antfu 的 开源探店直播项目收集 中看到了 速冻鱼 大佬开源的 easy-tinypng-cli 插件,它可以自动帮忙压缩替换图片资源。

然后又结合了目前项目在用的vite-plugin-imagemin,整理了下这两个不满足我的需求的地方:

  • 使用 vite-plugin-imagemin 时,每次构建都必须重新压缩,我认为这是不必要的。
  • 使用easy-tinypng-cli时,需要联网,有500个限制,不能满足大量处理。

好奇心让我去看了这两个工具的源码,发现easy-tinypng-cli其中设计的recode.json来作为一个记录表,防止图片进行二次压缩,导致浪费每个月的次数。

vite-plugin-imagemin是一个基于imagemin系列插件开发一个组合压缩图片插件。

瞬间有个想法戛然而生,我把他们结合一下是不是就行了?

  • 利用vite-plugin-imagemin中的压缩功能解决easy-tinypng-cli的联网和数量限制问题。
  • 利用easy-tinypng-cli中的recode设计来解决vite-plugin-imagemin每次的重复压缩工作问题。

开发

工具类型定位

是打算将其做成cli工具还是一个编译插件,这里我选择将其作为类似easy-tinypng-cli的工具,做成插件开发成本较大,要需要处理多种脚手架的问题,虽然说可以用unplugin来开发兼容多种脚手加插件,但是还是觉得cli工具更方便,所以决定将其作为一个cli工具进行开发。

工具整体流程设计

graph TD
A[执行命令] --> B(读取配置)
B --> |未读取到配置文件| C(结束)
B --> |读取到配置文件| D(获取配置目录下的所有图片路径)
D --> E(加载imagemin插件)
E --> F(遍历文件路径)
F --> G(根据文件内容获取hash)
G --> R(比对记录表)
R --> |hash存在| K(跳过该路径)
R --> |hash不存在| L(压缩并替换源文件并写入记录)
L --> 结束 

实现配置加载

参考了easy-tinypng-cli中使用的unconfig进行配置文件的加载,可以加载执行命令目录下文件名与files配置匹配的文件,支持多种拓展名,拓展名匹配优先级按照extensions

import { loadConfig } from 'unconfig'

const { config } = await loadConfig({sources: [{files: 'fm.config',extensions: ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs', 'json', ''],},],}) 

遍历图片文件路径

首先需要一个遍历方法

export function walkSync(currentDirPath: string, callback: (filePath: string, dirent: fs.Dirent) => void) {fs.readdirSync(currentDirPath, { withFileTypes: true }).forEach((dirent: fs.Dirent) => {const filePath = path.join(currentDirPath, dirent.name)if (dirent.isFile())callback(filePath, dirent)else if (dirent.isDirectory())walkSync(filePath, callback)})
} 

根据配置文件中获取到的目录配置,进行遍历

const { include, options } = configconst includeArr = Array.isArray(include) ? include : [include]const filePaths: string[] = []includeArr.forEach((targetDir) => {const currentDirPath = path.resolve(process.cwd(), targetDir)walkSync(currentDirPath, (filePath: string, dirent: fs.Dirent) => {if (isImageExp(dirent.name)) { // 判断是否是图片文件const hash = getHashOfFile(filePath)// 获取文件hashif (force || !oldTinyMap.has(hash)) // 判断是否已经压缩过filePaths.push(filePath)}})}) 

压缩方法

const plugins = getImageminPlugins(options) // 复用vite-plugin-imagemin中的方法if (plugins.length === 0) {debug(chalk.redBright('Please configure at least one compression configuration parameter.'))return}const tasks = filePaths.map(filePath => Promise.resolve(processFile(plugins!, filePath, fs.readFileSync(filePath))))const spinner = ora({ text: chalk.bold.yellow('compressing...'), color: 'yellow' }).start() // 利用promise.all去执行压缩操作任务await Promise.all(tasks).then((results) => {spinner.stop()const total = results.lengthconst success = results.filter(result => result).lengthconst fail = total - successdebug(`compress results: total: ${chalk.bold.blue(total)} success: ${chalk.bold.green(success)} fail: ${chalk.bold.red(fail)}`)// eslint-disable-next-line no-consoleconsole.table(Array.from(tinyMap.values()), ['relativePath', 'oldSize', 'size', 'ratio', 'time(ms)'])Array.from(tinyMap.entries()).forEach(([key, value]) => {oldTinyMap.set(key, value)})// 写入日志fs.writeFileSync(RecordFilePath, JSON.stringify(Object.fromEntries(oldTinyMap.entries()), null, '\t'))}) 

主体功能就开发完成了,有兴趣的可以去fast-imagemin-cli查看源码

应用

安装

# pnpm
pnpm install vite-plugin-imagemin -D
 # npm
npm install vite-plugin-imagemin -D
 # yarn
yarn add vite-plugin-imagemin -D 

imagemin 安装问题

感谢大佬指出这个问题,这个问题我在vite-plugin-imagemin的README中有看到相应的解决方案,如下

由于 imagemin 在国内不好安装。现提供几个解决方案

1.使用 yarn 在 package.json 内配置(推荐)

"resolutions": {"bin-wrapper": "npm:bin-wrapper-china"}, 

2.使用 npm,在电脑 host 文件加上如下配置即可

199.232.4.133 raw.githubusercontent.com 

Config

fm.config.ts or fm.config.json

  • inclde(required): 需要压缩的图片目录会自动递归遍历.
  • options: imagemin 配置项
// fm.config.ts
import { defineFmConfig } from 'fast-imagemin-cli/support'

export default defineFmConfig({include: ['./src/assets'], // string | string[]options: {gifsicle: {optimizationLevel: 7,interlaced: false,},optipng: {optimizationLevel: 7,},mozjpeg: {quality: 20,},pngquant: {quality: [0.8, 0.9],speed: 4,},svgo: {plugins: [{name: 'removeViewBox',},{name: 'removeEmptyAttrs',active: false,},],},},
}) 

Options

params type default default
svgo object or false - See Options
gifsicle object or false - See Options
mozjpeg object or false - See Options
optipng object or false - See Options
pngquant object or false - See Options
webp object or false - See Options

添加 Script

// package.json
{"scripts": {"fm": "fm" // or "fm --force" 强制压缩,跳过记录// 如果担心会忘记压缩的情况,可以直接把命令配置到编译命令中"build": "fm && vue-tsc --noEmit && vite build" }
} 

Demo

xlegex

first

【前端工程化】我开源了个高效的图片压缩工具 - fast-imagemin-cli_第1张图片

你会发现其中一个 .png 文件压缩耗时 76930ms, 如果每次都花这么长时间,会大大影响工作效率。

second

【前端工程化】我开源了个高效的图片压缩工具 - fast-imagemin-cli_第2张图片

第二次就只需要5ms,节省了77000ms。

最后

感谢easy-tinypng-clivite-plugin-imagemin提供的灵感

项目地址

fast-imagemin-cli

使用中有任何问题可以提issue哦~~

如果这个插件能够帮助到你或者让你觉得有用可以给个star和关注~~

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

你可能感兴趣的:(前端,开源,javascript)