這系列的程式碼在 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