如何在 TypeScript remix 專案中把 .svg 引用成 React 元件

2024-08-03 19:23 Remix

搜尋 remix SVG as react component 會找到SVGR 官方提供的 remix 設定,但感覺太複雜了。其實可以直接用 vite-plugin-svgr 來處理就好。

步驟

先安裝套件:

npm i --D vite-plugin-svgr @svgr/plugin-jsx @svgr/plugin-prettier @svgr/plugin-svgo
yarn add --D vite-plugin-svgr @svgr/plugin-jsx @svgr/plugin-prettier @svgr/plugin-svgo

然後在專案根目錄新增 svgr.config.cjssvgo.config.cjs。使用 .cjs 是因為用 .js 設定檔沒辦法讓 npm run remix vite:dev 啟動專案。錯誤訊息如下:

[vite] Internal server error: require() of ES Module ()/svgo.config.js from ()/node_modules/cosmiconfig/dist/loaders.js not supported.
Instead change the require of svgo.config.js in (略)/node_modules/cosmiconfig/dist/loaders.js to a dynamic import() which is available in all CommonJS modules.

接著在 svgr.config.cjs 中設定要引用的外掛(plugin):

module.exports = {
  plugins: ["@svgr/plugin-svgo", "@svgr/plugin-jsx", "@svgr/plugin-prettier"],
};

不需要加上 typescript: true,因為轉成 .tsx 反而會讓 vite 無法順利解析。錯誤訊息大概長這樣:

7:34:05 PM [vite] Internal server error: Transform failed with 1 error:
()/app/assets/icons/arrow_back.svg?react:2:12: ERROR: Expected "from" but found "{"
  
  Expected "from" but found "{"
  1  |  import * as React from 'react';
  2  |  import type { SVGProps } from 'react';
     |              ^
  3  |  const SvgArrowBack = (props: SVGProps<SVGSVGElement>) => (
  4  |    <svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} viewBox="0 0 24 24" {...props}>

然後在 svgo.config.cjs 設定 .svg 的加工需求:

module.exports = {
  plugins: [
    {
      name: "removeAttrs",
      params: {
        attrs: "(fill|fill-rule|style)",
      },
    },
  ],
};

選擇移除 fillstyle 是因為我會透過 tailwind css 來控制 .svg (圖示)的顏色。

再把設定檔與 vite-plugin-svgr 加進 vite.config.js

import svgr from "vite-plugin-svgr";
import svgrOptions from './svgr.config.cjs';

export default {
  // ...
  plugins: [svgr({ svgrOptions }),],
};

快完成了。在 ./app 資料夾內建立 custom.d.ts。檔名中的 custom 可以換成其他名稱。在 custom.d.ts 加入以下內容:

declare module "*.svg" {
  const content: string;
  export default content;
}

declare module "*.svg?react" {
  import * as React from "react";
  export const ReactComponent: React.FunctionComponent<
    React.ComponentProps<"svg">
  >;
  export default ReactComponent;
}

這代表這個專案的 .svg 檔有兩種用法。一個是在路徑尾巴加上 ?react 把 .svg 當成 React 元件用。一個是不加上 ?react 來載入 .svg 的檔案路徑。參考以下範例:

import ArrowBack from "~/assets/icons/arrow_back.svg?react";
import arrowBackSrc from "~/assets/icons/arrow_back.svg";

function IconButton() {
  return (
    <button>
      <ArrowBack className="block h-6 w-6" />
      <img src={arrowBackSrc} alt="" />
    </button>
  );
}

最後,把 custom.d.ts 加進 tsconfig.jsoncompilerOptions.types 中:

{
  "compilerOptions": {
    "types": ["@remix-run/node", "vite/client", "./app/custom.d.ts"]
  }
}

完成 👏

參考文件