這系列的程式碼在 https://github.com/DanSnow/ironman-2020/tree/master/static-site-generator
這篇用到的圖片來源,授權是 CC0 ,作者是 Snapwire
圖片是網頁上一個很重要的元素, Gatsby 也提供了個跟它的 GraphQL 查詢搭配的圖片最佳化,不過老實說那個有點複雜,我並沒有打算要做一個完全一樣的,這邊就來做一個比較簡單的實做,我們提供圖片的最佳化與 lazy loading 就好
這部份會使用到 imagemin 這個 Node.js 下常用的圖片最佳化的套件,不過是以 webpack loader 的形式來使用的,所以就安裝:
$ yarn add --dev image-webpack-loader
然後我們要把它再包裝成自己的 webpack loader ,這樣我們比較好加自己額外的資訊進去,不過這邊其實只有網址而已
這邊寫一個很簡單的 webpack loader
module.exports = function () {
return `
import url from '!!${require.resolve('file-loader')}!${require.resolve('image-webpack-loader')}!${this.resource}'
export default { url }`
}
module.exports.raw = true
再來我們要解決在 Server 端載入圖片的問題,我們寫個程式去 hook Node.js 的 require
import Module from 'module'
export function patchRequire() {
// 保存原本的 require
const originalRequire = Module.prototype.require
// 覆寫 require
Module.prototype.require = function (id) {
// 圖片的地方我是用 webpack alias 來載入的,所以判斷是不是 ~ 開頭,不過正常應該是要判斷副檔名
if (id.startsWith('~')) {
return {}
}
return originalRequire.call(this, id)
}
}
這樣在 Server 端載入使用者的程式才不會出錯
這邊用的是很簡單的實作:
import React, { useEffect, useState } from 'react'
export function Image({ img }) {
// server 端就直接回傳
if (!img.url) {
return <div>Loading...</div>
}
const { url } = img
const [ready, setReady] = useState(false)
useEffect(() => {
// 等待圖片載完
const img = document.createElement('img')
img.addEventListener('load', () => setReady(true))
img.src = url
}, [url])
// 圖片還沒好也顯示 Loading ,這樣才跟 Server Render 的內容一樣
if (!ready) {
return <div>Loading...</div>
}
// 顯示圖片
return <img src={img.url} />
}
其實這篇原本還想用 sharp 提供圖片縮放的,只是我在安裝上出了點問題,另外範例程式中還有被我註解掉的用 blurhash 當 placeholder 的實作,因為 Node.js 的圖片處理速度實在是太慢了,導致重 build 非常的花時間
下一篇我想來講 incremental build