create-react-native-appで ReactNative + Expo + TypeScriptの環境を構築してやろう。ついでに絶対パスでimportできるようにしてやろう。

## はじめに
巷で人気のReactNativeを始めよう。
なに？Expoってやつを使うと実装しながら実機で確認できるって？
しかもcreate-react-native-appを使うと一発で環境ができるとな？
そいつぁすげぇや！:dolphin:
よし、いっそのことTypeScriptで作ってみよう。

さて

```typescript
import Hoge from '../../../hoge/hoge';
import Foo from '../../../../foo/foo';
```

こんなの書きたくないよ:weary:

```typescript
import Hoge from 'hoge/hoge';
import Foo from 'foo/foo';
```

イイネ！:+1:

こんな気持ちで始めてみました。

## やるやらない
### やること
* create-react-native-app(以下 **crna** )でReactNative + Expo + TypeScriptの環境を構築
* tsconfigとbabel-plugin-module-resolverを使って絶対パスでimportできるようにする

サンプルコード
https://github.com/tshackstream/expo-ts-example

こんな感じのコードでexpoを起動、正常に表示できるところまでやります。

### やらないこと
トランスパイルの細かい設定などはやりません。

## TL;DR
* crnaでプロジェクトを作って
* TypeScriptを導入して
* babel-plugin-module-resolverを入れて
* `tsconfig.json`と`.babelrc`を書き換えて

おわり

## 環境
|開発環境||
|---|---|
|macOS|HighSierra|
|node|v10.8.0|
|npm|v6.2.0|

|確認端末||
|---|---|
|端末|iPhoneX|
|iOS|v11.4|

## 事前準備
個人的な趣味でyarnを使います。npmでも問題ありません。

```shell-session
$ npm install -g yarn
```

create-react-native-app, typescriptをグローバルインストール

```shell-session
$ yarn add global reate-react-native-app typescript
```

typescriptをインストールすると`tsc`コマンドが使えるようになります。

## ReactNative + Expo + TypeScript環境構築
### プロジェクト作成、TypeScript導入

expo-ts-exampleという名前で作成します。

```shell-session
$ create-react-native-app expo-ts-example
```

プロジェクトにtypescriptをインストール

```shell-session
$ yarn add typescript -D
```

型定義もインストール

```shell-session
$ yarn add @types/react @types/react-native -D
```

`tsconfig.json`を作成

```shell-session
$ tsc --init
```

`tsconfig.json`の編集

```diff:tsconfig.json
@@ -2,17 +2,17 @@

-    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
+    "module": "es2015",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     // "lib": [],                             /* Specify library files to be included in the compilation. */
-    // "allowJs": true,                       /* Allow javascript files to be compiled. */
+    "allowJs": true,                       /* Allow javascript files to be compiled. */
     // "checkJs": true,                       /* Report errors in .js files. */
-    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
+    "jsx": "react-native",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
     // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
     // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
     // "sourceMap": true,                     /* Generates corresponding '.map' file. */
     // "outFile": "./",                       /* Concatenate and emit output to single file. */
-    // "outDir": "./",                        /* Redirect output structure to the directory. */
-    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
+    "outDir": "build",                        /* Redirect output structure to the directory. */
+    "rootDir": "src",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
     // "composite": true,                     /* Enable project compilation */
     // "removeComments": true,                /* Do not emit comments to output. */
     // "noEmit": true,                        /* Do not emit outputs. */
@@ -34,15 +34,15 @@
     // "noUnusedParameters": true,            /* Report errors on unused parameters. */
     // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
     // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
-
+    "skipLibCheck": true,
     /* Module Resolution Options */
-    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+    "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
     // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
     // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
     // "typeRoots": [],                       /* List of folders to include type definitions from. */
     // "types": [],                           /* Type declaration files to be included in compilation. */
-    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
+    "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
     "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
     // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
 
@@ -55,5 +55,7 @@
     /* Experimental Options */
     // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
     // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
-  }
+  },
+  "compileOnSave": false,
+  "exclude": ["node_modules", "App.js", "App.test.js", "build"]
 }
\ No newline at end of file

```


srcディレクトリを作成しその中に`App.js`を`Root.tsx`という名前でリネーム
ファイル名は当初`App.tsx`でしたがそれだとimportを絶対パスにした時に動きませんでした:thinking:

ただしcrnaでプロジェクトを作成した場合、mainファイルが

```json:package.json
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js"
```

となっており、その中ではプロジェクトルートの`App.js`をrequireしています。

```javascript:crna-entry.js
var _App = require('../../../../App');
```

このままではエラーになるのでプロジェクトルートに`App.js`を作ってあげましょう。この`App.js`はtsをトランスパイルしたjsを読み込ませます
トランスパイル先は`build`なので、以下のようなファイルにします。

```javascript:App.js
import App from "./build/Root";
export default App;
```

現在の構造はこんな感じ

```shell-session
expo-ts-example
├── App.test.js
├── README.md
├── app.json
├── node_modules
├── package.json
├── src
    └── Root.tsx
├── tsconfig.json
└── yarn.lock
```

この状態でトランスパイル後、expoを起動すると正常に起動するはずです。

```shell-session
$ tsc
$ yarn start
```

<img width="375" alt="IMG_0011.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/58841/c6d9f4d0-3b4a-2571-35ad-7992977866cf.jpeg">

:relaxed:

### ファイル変更検知⇒トランスパイルするようにする

そのままだとファイル変更後いちいち`tsc`しないと変更が反映されないため
`ファイル監視⇒ファイル変更検知⇒tsc⇒expo反映⇒(以下ループ)`
を自動でできるようにします。

concurrently,rimrafをプロジェクトにインストール

```shell-session
$ yarn add concurrently rimraf -D
```

*concurrently*
npmスクリプトを同時実行できるようにする。`npm-run-all`と似たようなもの

*rimraf*
`rm -rf`を実行する

package.jsonを編集

```diff:package.json
     "eject": "react-native-scripts eject",
     "android": "react-native-scripts android",
     "ios": "react-native-scripts ios",
-    "test": "jest"
+    "test": "jest",
+    "tsc": "tsc",
+    "clean": "rimraf build",
+    "build": "yarn run clean && yarn run tsc --",
+    "watch": "yarn run build -- -w",
+    "watchAndRunAndroid": "concurrently \"yarn run watch\" \"yarn run android\"",
+    "buildRunAndroid": "yarn run build && yarn run watchAndRunAndroid ",
+    "watchAndRunIOS": "concurrently \"yarn run watch\" \"yarn run ios\"",
+    "buildRunIOS": "yarn run build && yarn run watchAndRunIOS ",
+    "watchAndStart": "concurrently \"yarn run watch\" \"yarn run start\"",
+    "buildAndStart": "yarn run build && yarn run watchAndStart "
   },
   "jest": {
     "preset": "jest-expo"

```

## importを絶対パスにする

srcに`components`というディレクトリを作り、その中に`MyComponent.tsx`というファイルを作りました。

```typescript:src/components/MyComponent.tsx
import React from 'react';
import { Text, View } from 'react-native';

export default class MyComponent extends React.Component {
    render() {
        return (
            <View>
                <Text>It's my component!</Text>
            </View>
        );
    }
}

```

`Root.tsx`でimportしてやりましょう。もちろん相対パスは使わずに。

```diff:src/Root.tsx
@@ -1,11 +1,14 @@
 import React from 'react';
 import { StyleSheet, Text, View } from 'react-native';
+  import MyComponent from "components/MyComponent";
 
 export default class Root extends React.Component {
   render() {
     return (
       <View style={styles.container}>
          <Text>Open up App.js to start working on your app!</Text>
          <Text>Changes you make will automatically reload.</Text>
          <Text>Shake your phone to open the developer menu.</Text>
+         <MyComponent/>
       </View>
     );

```

このままではトランスパイラもexpoも
`import MyComponent from "components/MyComponent";`
を解決することができません。

トランスパイラは`tsconfig.json`で、expoは`.babelrc`でそれぞれパスの解決をさせます。
babelはプラグインが無いと解決できないのでインストールします。

babel-plugin-module-resolverをプロジェクトにインストール

```shell-session
$ yarn add babel-plugin-module-resolver -D
```

`.babelrc`,`tsconfig.json`に追記

```diff:tsconfig.json
@@ -37,8 +37,10 @@
     "skipLibCheck": true,
     /* Module Resolution Options */
     "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
-    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
-    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+    "baseUrl": "./src",                       /* Base directory to resolve non-absolute module names. */
+    "paths": {
+      "components*": ["components*"]
+    },                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
     // "typeRoots": [],                       /* List of folders to include type definitions from. */
     // "types": [],                           /* Type declaration files to be included in compilation. */

```

```diff:.babelrc
@@ -4,5 +4,10 @@
     "development": {
       "plugins": ["transform-react-jsx-source"]
     }
-  }
+  },
+  "plugins": [
+    ["module-resolver", {
+      "root": ["./build"]
+    }]
+  ]
 }

```

ここで注意したいのが`.babelrc`の設定で、expoが参照しているのは`App.js`経由の`build/Root.tsx`です。
つまりexpo視点のルートディレクトリは`build`なのでrootには`build`を指定してあげる必要があります。

最終的なディレクトリ構成はこのようになりました。

```shell-session
expo-ts-example
├── App.js
├── App.test.js
├── README.md
├── app.json
├── build
    ├── Root.js
    └── components
        └── MyComponent.js
├── expo-ts-example.iml
├── node_modules
├── package.json
├── src
    ├── Root.tsx
    └── components
        └── MyComponent.tsx
├── tsconfig.json
└── yarn.lock
```

さてお待ちかね実行タイム

```shell-session
$ yarn buildAndStart
```

<img width="375" alt="IMG_0012.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/58841/f93ee3e4-d35a-1d7b-06ea-b4d239328473.jpeg">

:+1:

## 所感

import周りで1日ほどハマりました。楽をしたかったはずなのに。

![572da61930da592cfc85108207146d46.png](https://qiita-image-store.s3.amazonaws.com/0/58841/14198779-a8dc-5daf-dbc7-98e8742bf073.png)


玄人だけでなく、エンジニアにとっても真理ではなかろうか。

## 参考
[TypeScript in CRNA](https://blog.geekyants.com/typescript-in-crna-8baab5f95db0)
[React-Native App using Expo CRNA and Typescript - Part 1](https://medium.com/@bharat.tiwari/react-native-app-using-expo-crna-and-typescript-part-1-6d34113cc816)
[babel-plugin-module-resolverでimportを快適にする(eslint-plugin-import + flow + path-autocomplete)](https://qiita.com/ymkz/items/8f09326382ed7ebcd061)
[哲也～雀聖と呼ばれた男～](https://www.amazon.co.jp/%E5%93%B2%E4%B9%9F%EF%BD%9E%E9%9B%80%E8%81%96%E3%81%A8%E5%91%BC%E3%81%B0%E3%82%8C%E3%81%9F%E7%94%B7%EF%BD%9E%EF%BC%88%EF%BC%93%EF%BC%89-%E9%80%B1%E5%88%8A%E5%B0%91%E5%B9%B4%E3%83%9E%E3%82%AC%E3%82%B8%E3%83%B3%E3%82%B3%E3%83%9F%E3%83%83%E3%82%AF%E3%82%B9-%E3%81%95%E3%81%84%E3%81%B5%E3%81%86%E3%82%81%E3%81%84-ebook/dp/B009KYAYQ2/ref=sr_1_11?s=books&ie=UTF8&qid=1534424527&sr=1-11&keywords=%E5%93%B2%E4%B9%9F)