国庆无聊开发了个vite本地图片压缩插件。vite-plugin-imagemin-vv

1,117 阅读5分钟

前言

之前工作中遇到过图片需要压缩这个问题,每次都是手动压缩后覆盖原图,觉得挺麻烦的。虽然我是个菜鸡,总觉得不应该这么处理,手动总会有出错的时候,全自动的才好啊。

寻找解决方案

第一时间去搜索目前市面上已有的解决方案,并没有找到直接的解决方案,但也颇有收获,了解到了imagemin这个库,于是开始了更多搜索……具体过程就不说了,直接进入主题吧

开发思路

首先既然是要做vite的插件肯定先去vite官网看文档,看看提供哪些配置或者钩子, vite官网文档。好家伙不愧是我,一进门就看到常威在打来福。文档里赫然写着buildStart钩子,一看名字就知道是在构建开始的时候执行。

image.png

搭建开发环境

已经迫不及待了,想赶紧加到代码里试试看。不过呢,还是得有个开发环境。先搞个社区模板吧。

yarn create vite vite-project --template vue

创建好项目之后再在根目录新建一个文件夹vite-plugin,文件夹里面放入index.js,把下面的代码写进去,再来vite.config.ts里面引入调用一下。

const viteImagemin = () => ({
  name: 'vite-plugin-imagemin',
  buildStart: (options) => {
    console.log('--------options')
  }
})

export default viteImagemin

控制台打印如下

PS C:\Users\Administrator\Desktop\mono\vite-project> yarn build
yarn run v1.22.19
$ vue-tsc --noEmit && vite build
vite v3.1.6 building for production...
--------options
✓ 17 modules transformed.
dist/index.html                  0.44 KiB
dist/assets/index.bd68a93b.css   1.26 KiB / gzip: 0.65 KiB
dist/assets/index.34b8f462.js    52.72 KiB / gzip: 21.29 KiB
Done in 5.60s.

诶嘿嘿,先打印了我们的代码 再执行代码的压缩,这不正是我们想要的吗。很好我们的开局不错,感觉离开发成功就只差一点点了(bushi)。接下来就继续完善吧。

正式开发开发功能

既然是压缩本地图片,我的想法是按照下面几步走,先确定好思路是比较重要的一件事。

  1. 获取到图片绝对路径
  2. 根据绝对路径读取这个图片
  3. 调用imagemin这个库帮助压缩
  4. 覆盖源文件 既然思路清晰了就开始敲代码。

获取图片绝对路径

这部分就涉及到node的一些知识了,其实我也不是很懂,但只要你愿意去看文档就好了项目中引入path模块,再用resolve方法获取图片的绝对路径。注意,这边不能用__dirname参数,需要用process.cwd(),区别如下。

  1. process.cwd() 是当前Node.js进程执行时的文件夹地址——工作目录,保证了文件在不同的目录下执行时,路径始终不变
  2. __dirname 是被执行的js 文件的地址 ——文件所在目录
import path from 'path'

// 获取文件绝对路径
const getAbsPath = (relativePath) => {
  const cwd = process.cwd()
  const absPath = path.resolve(cwd, relativePath)
  return absPath
}

上面的方法是获取单个文件的路径,但是我们肯定是要指定一个文件夹,然后对这个文件夹进行处理。所以还要再写个方法。先写死一个文件夹吧,就是你了,src/assets ! 我们用下面这个字符串表示这个路径下的4大类型图片。

const dir = 'src/assets/**/*.{jpg,png,jpeg,gif}'

再调用globby获取到unixpath

import { globby } from 'globby'

const unixPaths = await globby(dir, { onlyFiles: true })

打印出来如下:

[ 'src/assets/jpeg.jpeg', 'src/assets/jpg.jpg', 'src/assets/png.png' ]

再获取一下绝对路径

const absPaths = unixPaths.map(v => getAbsPath(v))

打印出来如下,也就是每个图片的绝对路径了(平铺出来,没有层级嵌套)。

absPaths:[  'C:\\Users\\Administrator\\Desktop\\mono\\vite-project\\src\\assets\\jpeg.jpeg',  'C:\\Users\\Administrator\\Desktop\\mono\\vite-project\\src\\assets\\jpg.jpg',  'C:\\Users\\Administrator\\Desktop\\mono\\vite-project\\src\\assets\\png.png']

压缩单个文件,主要是调用imagemin.buffer

const compressSingleFile = async (singleAbsFilePath) => {
  return new Promise(async (resolve) => {
    let buffer = fs.readFileSync(singleAbsFilePath)
    let content = await imagemin.buffer(buffer, {
      plugins: [
        imageminGifsicle(),
        imageminOptpng(),
        imageminSvgo(),
        imageminMozjpeg(),
        imageminPngquant()
      ]
    })
    resolve(content)
  })
}

因为是多文件,需要批量处理

const compressFiles = async (comressFilePaths) => {
  let resolvedFileMap = []
  for (let i = 0; i < comressFilePaths.length; i++) {
    let filePath = comressFilePaths[i]
    let content = await compressSingleFile(filePath)
    resolvedFileMap.push({ filePath, content })
  }
  return resolvedFileMap
}

压缩完成后,覆盖源文件

const writeFiles = async (resolvedFileMap) => {
  if (resolvedFileMap.length) {
    resolvedFileMap.map(async (item) => {
      const { filePath, content } = item
      if (content) {
          fs.writeFileSync(filePath, content)
      }
    })
  }
}

测试

压缩前

image.png

压缩后

image.png

nice,程序跑起来了,但是压缩率好像不是很高。没关系,这些都是可以配置的,具体的去看看官网文档就好了。

优化

目前只是实现了最核心的压缩功能,但是呢还有很多需要优化的点。

  1. 做缓存处理,已经压缩过的图片无需再次压缩
  2. 文件路径可配置,并不是所有人图片都放在assets文件夹目录下的
  3. 文件可忽略,有可能会出现某些文件不希望压缩的情况
  4. 文件类型可忽略,有可能会出现某些文件类型不希望压缩的情况
  5. 使用ts重构一份
  6. 设置是否启用此插件的开关
  7. 控制台打印输出压缩结果,有一些图片可能会压缩不成功,此时用户可以单独特殊处理。(这段代码是在CSDN上看到的,直接CV下来了)
  8. 开局说的buildStart其实还不是最早的,后续改用为config钩子(这个才是build阶段第一个执行的钩子)。
  9. 只希望在build时启用,所以添加apply:'build',属性。(vite文档上有写明)

做好这些以后项目基本上算是比较完善了,想看源码的小伙伴可以去gayhub上看看,传送门

gitee,传送门

目前项目已发布到npm,可以开箱即用。常见的问题在gayhub上也有说明,可以参照着看看。

结语

第一次写东西发出来,就当是给自己做记录吧