# [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="")
```