Expo プロジェクトでソースコードの構成を変える
これまでのあらすじ…
前回の記事では、初期状態の Expo プロジェクトに Jest によるテストを追加した。TypeScript の場合は公式ガイドの手順に加えて、若干の修正が必要だった。そして、この時点でのファイル構成は以下のようになっている。
$ tree -L 1 .
.
├── App.test.tsx
├── App.tsx
├── app.json
├── assets
├── babel.config.js
├── jest.config.ts
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
2 directories, 8 files
今回はこのファイル構成を以下のように変更したいと思う。
- ソースコードを
src
ディレクトリとtest
ディレクトリに分ける - エントリーポイントを
src/App.tsx
に変更
なお、src
と test
を分けるこの構成は個人的に慣れてるから採用しただけで、たとえば、Jest - TypeScript Deep Dive では src
以下にすべての TypeScript ファイルを配置することが推奨されている。TypeScript Deep Dive では、この構成は、
for a clean project setup.
のためだと説明されているが、幸い、Expo プロジェクトではある程度の下地ができていることもあり、src
と test
を分けても複雑な設定にはならない。
src
ディレクトリと test
ディレクトリに分ける
では早速、src
ディレクトリと test
ディレクトリを作成して、ファイルを移動しよう。
$ mkdir src test
$ mv assets App.tsx ./src
$ mv App.test.tsx ./test
この時点でのファイル構成は次の通り。
$ tree -L 2 .
.
├── app.json
...
├── package-lock.json
├── package.json
├── src
│ ├── App.tsx
│ └── assets
├── test
│ └── App.test.tsx
└── tsconfig.json
当然、ファイルパスを変えたので、Relative import が動かなくなってる。
$ npx tsc
test/App.test.tsx:4:17 - error TS2307: Cannot find module './App' or its corresponding type declarations.
4 import App from "./App";
~~~~~~~
import App from "../src/App";
として修正することもできるが、どうせなら tsconfig.json の baseUrl
を設定して、Non-relative import でインポートできるようにしておこう。[1]
{
"compilerOptions": {
...
"baseUrl": "./src"
}
}
これで、App.test.tsx 側の import
を以下のようにできる。
import App from "App";
tsc でもエラーは見つからない。
$ npx tsc
テストを通す
さて、TypeScript の型検査は通ったので、次はテストを実行してみよう。
$ npm test
FAIL test/App.test.tsx
● Test suite failed to run
Cannot find module 'App' from 'test/App.test.tsx'
However, Jest was able to find:
'./App.test.tsx'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['ts', 'tsx', 'js', 'jsx'].
See https://jestjs.io/docs/en/configuration#modulefileextensions-arraystring
2 | import renderer from "react-test-renderer";
3 |
> 4 | import App from "App";
| ^
App
モジュールが見つからない、と怒られてしまった。TypeScript のコンパイラは通っているのに何故だろう?
実は、Expo (React Native) のプロジェクトでは、TypeScript の変換には TypeScript コンパイラ (tsc
) ではなく、Babel を使っている。そして、Babel の TypeScript 変換プラグインである @babel/plugin-transform-typescript は tsconfig.json を読まない。[2]
そのため、ここでは babel-plugin-module-resolver を使って、ルートを src
ディレクトリに設定しよう。[3]
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
[
"module-resolver",
{
root: ["./src/"],
extensions: [".ts", ".tsx", ".mjs", ".js", ".jsx"],
},
],
],
};
};
これでテストが通るようになった。
$ npm test
PASS test/App.test.tsx
<App />
✓ has 1 child (2568 ms)
Expo の設定を変更
では、expo start
で Expo の開発サーバを起動して、アプリを開いてみよう。
Failed to compile.
ENOENT: no such file or directory, open '/Users/takanori_is/Developer/Workspace/my-zenn-content/.work/my-expo-app/assets/favicon.png'
Unable to resolve asset "./assets/icon.png" from "icon" in your app.json or app.config.js
(node:73125) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
Error: Problem validating asset fields in app.json. See https://docs.expo.io/
• Field: splash.image - cannot access file at './assets/splash.png'.
• Field: android.adaptiveIcon.foregroundImage - cannot access file at './assets/adaptive-icon.png'.
• Field: icon - cannot access file at './assets/icon.png'.
Failed building JavaScript bundle.
Unable to resolve "../../App" from "node_modules/expo/AppEntry.js"
assets と App モジュールが見つからない、というエラーが出る。まずは、app.json で assets へのパスを修正しておく。
{
"expo": {
...
"icon": "./src/assets/icon.png",
"splash": {
"image": "./src/assets/splash.png",
...
},
...
"android": {
"adaptiveIcon": {
"foregroundImage": "./src/assets/adaptive-icon.png",
...
}
},
"web": {
"favicon": "./src/assets/favicon.png"
}
}
}
次は、App モジュールをエントリーポイントとして登録する必要がある。これは package.json での "main"
フィールドの変更と、App モジュール側で registerRootComponent()
を呼ぶ必要がある。[4]
{
"main": "src/App.tsx",
...
}
import { registerRootComponent } from "expo";
...
export default function App() {
...
}
...
registerRootComponent(App);
これで問題なくアプリが開くはず。
-
Relative と Non-relative imports について TypeScript: Documentation - Module Resolution ↩︎
-
Caveats の節を参照 @babel/plugin-transform-typescript · Babel ↩︎
-
このプラグインは babel-preset-expo に含まれている。また、React Native のガイドでも同様の方法が採られていた Using TypeScript with React Native · React Native ↩︎
Discussion