普通文組 2.5

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

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

步驟

先安裝套件:

Terminal window
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 啟動專案。錯誤訊息如下:

Terminal window
[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 無法順利解析。錯誤訊息大概長這樣:

Terminal window
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 arrowBackSrc from "~/assets/icons/arrow_back.svg";
import ArrowBack from "~/assets/icons/arrow_back.svg?react";
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"]
}
}

完成 👏

參考文件