图片转webp工程化

一、生成webp图片

1. husky中触发转化脚本的指令

// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
current_branch=`git rev-parse --abbrev-ref HEAD`
if [[ $current_branch != 'master' && $current_branch != 'release' ]]; then
  # 生成 webp 图片
  npm run webp -- commit
fi

2. 获取一切有改变的图片

const { execSync } = require('child_process');
function getChangedImgs() {  
   return String(execSync('git status -s'))  
    .split('n')  
      .filter(item => item.charAt(1) !== 'D') // 过滤掉首字母为 'D' 的项  
      .map(item => item.slice(2).trim()); // 获取空格后的文件途径
      .filter(path => path.match(/.(jpe?g)|(png)/))
}
coonst imgFiles = getChangedImgs()

3. 使用插件生成webp

const imagemin = require("imagemin");
const imageminWebp = require("imagemin-webp");
// 转化
function transformWebp(dir, filePath) {
  return imagemin([filePath ? filePath : `${dir}/*.{jpg,png}`], {
    destination: `${dir}`,
    plugins: [
      imageminWebp({
        quality: 75, // 图片质量
      })
    ]
  });
};

图片转webp工程化
经过上述后会主动在目标目录中生成对应的webp文件;

4. node同步履行函数主动提交新生成的webp图片

const addFile = imgFiles
    .map(path => {
      return path.replace(/.(png|jpe?g)$/, ".webp");
    })
    .filter(webpPath => {
      return fs.existsSync(webpPath);
    });
execSync(`git add ${addFile.join(" ")}`);

二、运用webp图片

1. 支撑webp的浏览器增加webpclass类

function supportsWebP() {
    const elem = document.createElement('canvas'); 
    if (!!(elem.getContext && elem.getContext('2d'))) { // 判别浏览器是否支撑 canvas 
        return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0; 
        // 创立一个 WebP 图片并检查其 data URL 是否以 'data:image/webp' 最初 
     } 
     return false; // 浏览器不支撑 canvas 
}
if (supportsWebP()) {
   // 在根元素html上增加class webp
   document.documentElement.classList.add('webp')
}

2. 核心功能-处理css

这儿主要要做两点:

    1. 增加webp父类名,让webp下款式权重更高
.foo {}
.webp .foo {} // 在支撑webp的浏览器中该款式权重更高
    1. 经过生成webp图片后,图片库中默许生成了全套的webp图;咱们需求将css中引入的png图悉数替换成webp图; 因为是同途径同文件名,不同后缀,只需求替换后缀即可。
.foo {
  background: url('./img/bg.png') center / contain no-repeat
}
.webp .foo {
   background: url('./img/bg.webp') center / contain no-repeat
} // 在支撑webp的浏览器中该款式权重更高
  • 3.上述第2点需求注意一个点,并非一切的图片转化成webp后都会更小,所以咱们在是否增加.webp类时需求做一个判别;

参考 咱们这儿用的是替换文件后缀,一起判别原文件和webp谁更小而决定是否运用webp

const postcss = require('postcss')
const path = require('path');
const fs = require('fs');
const propReg = /^background/
module.exports = postcss.plugin(
  'postcss-webp',
  ({
    webpClass = 'webp',
    pattern = /.(png|jpe?g)/,
    cssModules = false,
    ignoreComment = 'webp-ignore',
    includes = [],// 正则只处理部分模块
    excludes = [],// 排除模块
    // url-loader 的 limit 配置。base64 的已经内嵌的其他资源了,没有必要再用 webp
    limit = 4096,
  } = {}) => {
    return (root) => {
      const { file: filePath, css: content } = root.source.input;
      const webpReg = new RegExp(`.${webpClass}\s[a-z#\.\*]`)
      if (content && webpReg.test(content)) return;
      const isInclude = includes.length ? includes.some((reg) => reg.test(filePath)) : true;
      const isExclude = excludes.length ? excludes.some((reg) => reg.test(filePath)) : false;
      if (!isInclude || isExclude) return;
      root.walkRules((rule) => {
        const ruleIgnore = rule.parent.nodes.filter((el) => el.type === 'comment' && el.text === ignoreComment)
        if (ruleIgnore.length) return
        if (rule.selector.indexOf(`.${webpClass}`) !== -1) return
        const hasBackground = rule.nodes.filter((el) => {
          return el.type === 'decl' && el.prop.match(propReg)
        })
        if (hasBackground) {
          const webpRule = postcss.rule({
            selector: cssModules ? `:global(.${webpClass}) ${rule.selector}` : `.${webpClass} ${rule.selector}`
          })
          let useWebp = false;
          rule.walkDecls(propReg, (decl) => {
            const declIgnore = decl.next() && decl.next().type === 'comment' && decl.next().text === ignoreComment
            if (declIgnore) return;
            const hasUrl = decl.value.match(/url(([^)]*)?)/)
            if (hasUrl && pattern.test(decl.value)) {
              const imageUrl = hasUrl[1].replace(/'|"/gi, '').replace(/?.*$/, '');
              const imagePath = path.join(path.dirname(filePath), imageUrl);
              const webpPath = imagePath.replace(pattern, '.webp');
              const imgStat = fs.statSync(imagePath);
              const webpStat = fs.existsSync(webpPath) ? fs.statSync(webpPath) : null
              const imageFullUrl = hasUrl[1].replace(/'|"/gi, '')
              if (imgStat.size > limit && webpStat && webpStat.size < imgStat.size && !/?nowebp/.test(imageFullUrl)) {
                useWebp = true;
                webpRule.append({
                  prop: decl.prop,
                  value: decl.value.replace(pattern, '.webp')
                })
              }     
            } else {
              webpRule.append({
                prop: decl.prop,
                value: decl.value,
              });
            }
          });
          if (webpRule.nodes.length && useWebp) {
            rule.after(webpRule)
          }
        }
      })
    }
  }
)