捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:webpack 5 與圖片資源

今天會先分享個人慣用的靜態資源設定,接著會介紹我滿喜歡的 svg loader @svgr/webpack。這個套件能讓 .svg 直接以「元件」的形式被引用:

import EditIcon from './edit_icon.svg';

function EditButton() {
  return (
    <button>
      <EditIcon /> edit profile
    </button>
  );
}

那麽開始吧 (`・ω・´)

webpack 設定

以路徑形式使用圖片

首先在 webpack 設定中加上以下內容:

const webpackDevelopmentConfig: WebpackConfiguration = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg)$/i,
        type: 'asset/resource',
        resourceQuery: /url/,
      },
    ],
  },
};

沒錯,在 webpack 5 的時代裡,取用靜態資源不強制要求安裝套件 raw-loader / url-loader / file-loader 了。

webpack: Asset Modules types replace all of these loaders by adding 4 new module types.

以上這段規則在做的事情是:當偵測到檔案路徑符合 .png?url / .svg?url / .jpg?url 時,webpack 會提供該檔案在瀏覽器環境中可以使用的路徑。

webpack: asset/resource emits a separate file and exports the URL. Previously achievable by using file-loader.

所以現在你可以在專案內使用以下方式運用 ./src/asset 資料夾中的圖片檔案了:

import React from 'react';
import foxImageUrl from '@Asset/alexander-andrews-unsplash.jpg?url';

function Image() {
  return <img src={foxImageUrl} alt="fox image" />;
}

export default Image;

記得 import 路徑結尾要加上我們在 webpack 規則中設定好的 resourceQuery url

以元件形式使用 svg

先安裝 @svgr/webpack,然後追加以下設定:

const webpackDevelopmentConfig: WebpackConfiguration = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg)$/i,
        type: 'asset/resource',
        resourceQuery: /url/,
      },
      {
        test: /\.svg$/i,
        issuer: /\.tsx?$/,
        resourceQuery: { not: [/url/] },
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              svgo: false,
            },
          },
        ],
      },
    ],
  },
};

透過 test: /\.svg$/iresourceQuery: { not: [/url/] }issuer: /\.tsx?$/ 來處理「所有 .tsx 中結尾不為 .svg?url 的內容」。遇到符合前述條件的檔案時,使用 @svgr/webpack 來處理之。

這邊比較弔詭的地方是設定 svgo: false 來讓 css 針對 svg 的尺寸設定可以順利被套用上去。只寫成 use: ['@svgr/webpack'] 或是使用 svgo 官方建議的解決方式在個人的實驗結果中都是失敗的(svg 無法透過 css 調整尺寸)。

🤷

更新 src/env.d.ts

請將以下內容加到 ./src/env.d.ts 中。這能讓 TypeScript 知道「路徑結尾為 .(jpg|png|svg)?url 的檔案會是文字型別」,而「路徑結尾為 .svg 的檔案會是一個 React 元件且擁有 html svg element 的特性(props)」。

declare module '*.jpg?url' {
  const src: string;
  export default src;
}

declare module '*.png?url' {
  const src: string;
  export default src;
}

declare module '*.svg?url' {
  const src: string;
  export default src;
}

declare module '*.svg' {
  import React from 'react';
  const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}

完成!現在你的專案能夠正常顯示圖片內容了 (`・∀・)b

補充:如何選擇圖片格式

綜合平常的開發經驗以及參考 web.dev: Choose the right image format 一文,個人通常會以以下流程來判斷最終要使用哪一種格式來顯示圖片:

  1. 需要支援透明度:沒什麼選擇,基本上就是 png
  2. 照片、不是色塊分明的圖面:使用 jpg
  3. 色塊分明的圖片(如網站 logo 等):使用 svgpng

確定格式後,我會再根據圖片實際的展示位置決定是否調整檔案尺寸。除了 landing page 這類大面積區塊外,基本上圖片都可以縮小成「畫面上實際展示的大小」即可。

雖然現在是不需要特別斤斤計較流量的時代,但無意義地使用大圖片也會造成 lighthouse 評分低落。開發時若還有餘裕的話,上圖時稍微注意一下也沒有損失 😈