---
# System prepended metadata

title: '[Vue] 利用 Webpack 使用 webp 來加速圖片載入速度'
tags: [chainWebpack, webp, Vue-cli]

---

# [Vue] 利用 Webpack 使用 webp 來加速圖片載入速度
###### tags: `webp` `chainWebpack` `Vue-cli`
#### webp 是 2010年 Google 釋出的圖片格式，針對 PNG 可減少 26%，JPEG 約可減少 25% ~ 34%，支援度的話目前僅 safari 尚不支援，但在 ios 14 以後開始支援。
#### 在此區分為三個階段，分別為建置 webp 圖片、將 html 上的圖片轉為 webp 格式、將 style 內的圖片轉為 webp 格式
### 1.建置 webp 圖片
#### 這邊使用 [webpack-plugin-image-transform-webp-and-mini](https://www.npmjs.com/package/webpack-plugin-image-transform-webp-and-mini) 來協助將原有圖片轉換成 webp 格式，在此寫在 WebPack 的 chain 中，程式碼如下：
```javascript=
    config.plugin("webP").use(ImageminWebpWebpackPlugin, [
      {
        name: "static/img/[name].[hash:8].[ext]",
        logger: false,
        paths: { //将此路径下的图片进行转换
          dir: path.resolve(__dirname, "./src/assets"),
          exclude: []
        },
        miniOptions: false //是否針對小圖做轉換
      }
    ])
```
### 2.將 html 上的圖片轉為 webp
#### 這邊使用 VueLazyload 原生載入 webp 方式，將圖片路徑改為 webp，故在 html 的 img 使用上均需要用 v-lazy 的方式才可以使用 webp。
```javascript=
    vue.use(VueLazyload, {
      filter: {
        webp(listener) {
          if (vue.prototype.$supportWebp && !~listener.src.indexOf(".webp")) {
            listener.src = listener.src.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp")
            listener.el.setAttribute("data-src", listener.src.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp"))
          }
        }
      }
    })
```
#### 偵測裝置是否支援 webp，這邊使用 canvas 來判斷
```javascript=
export async function isSupportWebp() {
  return new Promise(resolve => {
    let result = false
    const elem = document.createElement("canvas")
    if (elem.getContext && elem.getContext("2d")) {
      result = elem.toDataURL("image/webp").indexOf("data:image/webp") === 0
    }
    resolve(result)
  })
}
```
### 3.將 style 檔案內的圖片轉為 webp 格式
#### 因在 vue 中，若找不到圖片會導致程式無法正常執行，在 style 檔案內只能放現有的圖片路徑，無法放副檔名為 webp 的路徑，而原本的規則只讀一般的圖片，不會將圖片轉換為 webp 格式，故將原有的 images rule 清除後再自行生成規則，所以針對 webpack 的 chain 再做下列調整：
```javascript=
    let rule = config.module.rule("images")
    rule.uses.clear()
    rule
      .use("./webploader.js")
      .loader("./webploader.js")
      .end()
      .use("url-loader")
      .loader("url-loader")
      .options({
        limit: 4096,
        fallback: {
          loader: "file-loader",
          options: {
            name: "static/img/[name].[hash:8].[ext]"
          }
        }
      })
```
#### webploader.js 檔案內最主要用來將副檔名改為 webp 格式，內容如下：
```javascript=
const path = require("path")

module.exports = function(source, map) {
  let result = source

  if (this.resourceQuery && this.resourceQuery.includes("type=webp") && !this.resource.includes("data:image")) {
    let extname = path.extname(this.resourcePath)
    result = source.replace(extname, ".webp")
  }
  // return result
  this.callback(null, result, map)
}
```
#### 因 style 內的副檔名還是 png/jpeg，為了要在程式中做區隔，便在路徑後方加上 Query 來分辨，用來控制所要轉的圖片，並在程式進入點(main.js)判斷裝置支援 webp 之後寫上全域的 class，讓畫面轉讀取 webp 圖片。
#### main.js
```javascript=
filter(Vue)
;(async () => {
  Vue.prototype.$supportWebp = await isSupportWebp()

  if (Vue.prototype.$supportWebp) {
    document.documentElement.classList.add("webp")
  }
})()
```
#### less
```css=
div{
    background:url("圖片路徑") no-repeat;
    
    .webp & {
        background:url("圖片路徑?type=webp") no-repeat;
    }
}
```
### 補充：若有些畫面不想因為 lazyLoad 讀取圖片導致畫面閃爍，這邊擴充單純使用 webp 方式
#### 在 main.js 增加 directive
```javascript=
    Vue.directive("webp", {
      bind(el, binding) {
        el.src = Vue.prototype.$supportWebp ? binding.value.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp") : binding.value
      },
      update(el, binding) {
        el.src = Vue.prototype.$supportWebp ? binding.value.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp") : binding.value
      }
    })
```
#### 使用方式 (pug)
```pug=
img(v-webp="路徑" alt="")
```





