かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Vite React コンポーネントへのパスを変数にして Dynamic import したい

React.lazyimport() を使えばコンポーネントを Dynamic import することができます。
例えば何種類下のアイコンの中で必要なコンポーネントだけ読み込みたいような場合、コンポーネントへのパスを変数にできるとコードがシンプルになりそうだったので試してみました。

環境
  • react@18.1.0
  • vite@2.9.9
  • typescript@4.6.4

パスを変数にして import() する

const componentName = 'MyComponent';
const MyComponent = React.lazy(() => import(`./path/to/${componentName}.tsx`));

const MyApp: FC = () => {
  return (
    <Suspence fallback="loading...">
      <MyComponent />
    </Suspence>
  );
};

変数を使って import する場合のTips

  • パスは ./, ../ から始める必要がある。パスエイリアスは使えない
  • 拡張子が必要

動的に import するパスを変更する

先の例のままでは変数が固定値なので変数を使って import() している意味がありません。 props などを使って動的に import するコンポーネントを変更できます。

1. 変数を使った Dynamic import を返すコンポーネントを使う

import { FC, lazy, Suspence } from 'react';

type UnitProps {
  unit?: 'Soleil' | 'Luminus' | 'Tristar';
};
const Unit: FC<UnitProps> = ({ unit }) => {
  if (!unit) {
    return null;
  }
  return lazy(() => import(`./unit/${unit}.tsx`);
};

const MyApp:FC = () => {
  const unitName = getUnitName();
  return (
    <Suspence fallback="loading...">
      <Unit unit={unitName} />
    </Suspence>
  );
}

2. 高階関数を使って React.lazy を返す関数を使う

import { FC, lazy, Suspence } from 'react';

type UnitName = 'Soleil' | 'Luminus' | 'Tristar' | undefined;
const getUnitComponent = (unit: UnitName) => {
  if (!unit) {
    return null;
  }
  return lazy(() => import(`./unit/${unit}.tsx`));
};

const MyApp:FC = () => {
  const unitName = getUnitName();
  const Unit = getUnitComponent(unitName);
  return (
    <Suspence fallback="loading...">
      <Unit />
    </Suspence>
  );
}

Tips

パスエイリアスを使用することはできない

変数を使用しない場合はパスエイリアスを使っても問題がありませんでしたが、変数で import() する場合はパスエイリアスを使ったパスを指定するとパス解決ができないようです

🙆 固定値であれば Path alias も問題ない

const MyComponent = React.lazy(() => import('@/path/to/MyComponent.tsx'));

const MyApp: FC = () => {
  return (
    <Suspence fallback="loading...">
      <MyComponent />
    </Suspence>
  );
};

これは問題なく動作する

🙅 変数を使用する場合 Path alias は使えない

const componentName = 'MyComponent';
const MyComponent = React.lazy(() => import(`@/path/to/${componentName}.tsx`));

const MyApp: FC = () => {
  return (
    <Suspence fallback="loading...">
      <MyComponent />
    </Suspence>
  );
};

@/path/to/MyComponent.tsx という文字列のまま import しようとするので永遠に loading... が表示されてしまいます。
予測ですが、変数でない場合はコンパイル時にパスエイリアスのパス解決した文字列に置き換えられるので問題がないのだと思います。

相対パスで指定する必要がある

const componentName = 'MyComponent';
- const MyComponent = React.lazy(() => import(`@/path/to/${componentName}.tsx`));
+ const MyComponent = React.lazy(() => import(`./path/to/${componentName}.tsx`));

Vite が内部的に使用している Rollup で相対パスで指定しなければならないとされている

Imports must start with ./ or ../.
All imports must start relative to the importing file. The import should not start with a variable, an absolute path or a bare import:

// Not allowed
import(bar);
import(`${bar}.js`);
import(`/foo/${bar}.js`);
import(`some-library/${bar}.js`);

cf. plugins/packages/dynamic-import-vars at master · rollup/plugins · GitHub

拡張子が必要

import() で読み込む変数に拡張子がないと Vite で warning が発生する

const MyComponent = React.lazy(() => import(`./path/to/${componentName}`));

const MyApp: FC = () => {
  return (
    <Suspence fallback="loading...">
      <MyComponent />
    </Suspence>
  );
}

👇

The above dynamic import cannot be analyzed by vite. See <a href="https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations">https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations</a> for supported dynamic import formats. If this is intended to be left as-is, you can use the /* @vite-ignore */ comment inside the import() call to suppress this warning.

Vite が内部的に使用している Rollup にインポートするつもりのないファイルが含まれる可能性を排除するために拡張子が必要であると書かれていました。warning なので放置してもクリティカルな問題はないですが Rollup で説明されているのでちゃんと拡張子を指定するのが良さそうです。

Imports must end with a file extension
A folder may contain files you don't intend to import. We, therefore, require imports to end with a file extension in the static parts of the import.

// Not allowed
import(`./foo/${bar}`);
// allowed
import(`./foo/${bar}.js`);

cf. plugins/packages/dynamic-import-vars at master · rollup/plugins · GitHub


[参考]