如何在 TypeScript remix 專案中把 .svg 引用成 React 元件
搜尋 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.cjs 與 svgo.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)",
      },
    },
  ],
};選擇移除 fill 和 style 是因為我會透過 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.json 的 compilerOptions.types 中:
{
  "compilerOptions": {
    "types": ["@remix-run/node", "vite/client", "./app/custom.d.ts"]
  }
}完成 👏