React NativeをTypeScript + ESLint + Prettier + Storybookで環境構築
はじめに
React Nativeの環境構築をする機会があったので、まとめて紹介します。
やることは以下になります。
- Expo CLIを使ってReact NativeをTypeScriptで構築する
- ESLint / Prettier / husky / lint-stagedを導入する
- Storybookを導入し、Netlifyを利用してPull RequestにコンポーネントカタログのプレビューURLを載せる
完成版: https://github.com/RikutoYamaguchi/react-native-with-linter-and-storybook-sample
PRのサンプル: https://github.com/RikutoYamaguchi/react-native-with-linter-and-storybook-sample/pull/1
Expo CLIでReact NativeをTypeScriptで構築する
ここは公式の手順と全く同じです。
Setting up the development environment · React Native
Expo CLIのインストール
$ yarn global add expo-cli
プロジェクトの作成
$ expo init AwesomeProject
? Choose a template: › - Use arrow-keys. Return to submit.
----- Managed workflow -----
blank a minimal app as clean as an empty canvas
> blank (TypeScript) same as blank but with TypeScript configuration
tabs (TypeScript) several example screens and tabs using react-navigation and TypeScript
----- Bare workflow -----
minimal bare and minimal, just the essentials to get you started
minimal (TypeScript) same as minimal but with TypeScript configuration
(TypeScript)
になっているものを選択すれば、TypeScript環境ができあがります。めちゃくちゃ簡単ですね。
blank
or tabs
or minimal
ですが、tabs
を選択すると react-navigation
が導入され、
かつサンプルとしてタブスクリーンが2つ実装された状態になっています。
実装されたものは結構参考になるなと思ったので、実際に開発をするときは tabs
がいいかもしれません。
今回は環境構築のみに絞るので、blank (TypeScript)
を選択しました。
以下で、プロジェクトディレクトリに移動しておきます。
cd AwesomeProject
ESLintの導入
初期設定&インストール
以下のコマンドで初期設定&インストールします。
$ npx eslint --init
対話形式で色々聞かれるので、React Native & TypeScriptに適した選択をしていきます。
? How would you like to use ESLint? …
To check syntax only
To check syntax and find problems
❯ To check syntax, find problems, and enforce code style
シンタックスチェックと、ファイルの修正も行いたいので一番下の、
To check syntax, find problems, and enforce code style
を選択します。
? What type of modules does your project use? …
❯ JavaScript modules (import/export)
CommonJS (require/exports)
None of these
JavaScript modules (import/export)
を選択します。
? Which framework does your project use? …
❯ React
Vue.js
None of these
React
を選択します。
? Does your project use TypeScript? › No / Yes
TypeScriptなので、 Yes
を選択します。
? Where does your code run? … (Press <space> to select, <a> to toggle all, <i> to invert selection)
Browser
✔ Node
Node
を選択します。
? How would you like to define a style for your project? …
Use a popular style guide
❯ Answer questions about your style
Inspect your JavaScript file(s)
Answer questions about your style
を選択します。
? What format do you want your config file to be in? …
❯ JavaScript
YAML
JSON
JavaScript
を選択します。
? What style of indentation do you use? …
Tabs
❯ Spaces
Spaces
を選択します。
参考: スタイルガイド(コーディング規約) - TypeScript Deep Dive 日本語版#スペース
? What quotes do you use for strings? …
Double
❯ Single
Single
を選択します。
参考: スタイルガイド(コーディング規約) - TypeScript Deep Dive 日本語版#引用符
? Do you require semicolons? › No / Yes
Yes
を選択します。
参考: スタイルガイド(コーディング規約) - TypeScript Deep Dive 日本語版#セミコロン
react-hookプラグイン追加
このままだと、フックのルールが適用されないようなので、プラグインを追加します。
フックのルール – React
$ yarn add -D eslint-plugin-react-hooks
.eslintrc.js
を修正
以下の修正を行います。
- react-hook追加(ルールも)
- indentが4になっているので2に変更
- react versionの指定
参考: https://github.com/yannickcr/eslint-plugin-react/issues/1955#issuecomment-415172572
module.exports = {
'env': {
'es2021': true,
'node': true
},
'extends': [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended'
],
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaFeatures': {
'jsx': true
},
'ecmaVersion': 12,
'sourceType': 'module'
},
'plugins': [
'react',
- '@typescript-eslint',
+ '@typescript-eslint',
+ 'react-hooks'
],
'rules': {
'indent': [
'error',
- 4
+ 2
],
'linebreak-style': [
'error',
'unix'
],
'quotes': [
'error',
'single'
],
'semi': [
'error',
'always'
- ]
+ ],
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'warn'
- }
+ },
+ 'settings': {
+ 'react': {
+ 'version': 'detect'
+ }
+ }
};
Prettierの導入
インストール
$ yarn add -D prettier eslint-plugin-prettier
.prettierrc.js
を追加
$ touch .prettierrc.js
module.exports = {
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
};
.eslintrc.js
を修正
- pluginsにprettierを追加
...
'plugins': [
'react',
'@typescript-eslint',
- 'react-hooks'
+ 'react-hooks',
+ 'prettier'
],
...
};
ESLintとPrettierの動作確認
package.json
を修正
ESLintとPrettierを実行する npm scripts
を追加します。
...
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
- "eject": "expo eject"
+ "eject": "expo eject",
+ "lint": "eslint './src/**/*.{js,ts,tsx}'",
+ "lint:fix": "eslint --fix './src/**/*.{js,ts,tsx}'",
+ "format": "prettier --check ./src",
+ "format:fix": "prettier --write ./src"
},
...
./src/components/AppButton.tsx
を追加
環境構築のテスト用にシンプルなコンポーネントを作ります。
$ mkdir -p src/components
$ touch src/components/AppButton.tsx
./src/components/AppButton.tsx
import React from 'react';
import { Button } from 'react-native';
export type AppButtonPropsType = {
title: string,
onPress: () => void
}
export const AppButton: React.FunctionComponent<AppButtonPropsType> = (props: AppButtonPropsType) => <Button title={props.title} onPress={props.onPress} />
ESLintを動かしてみる
$ yarn lint
yarn run v1.22.4
$ eslint './src/**/*.{js,ts,tsx}'
/path/to/your/AwesomeProject/directory/src/components/AppButton.tsx
9:156 error Missing semicolon semi
✖ 1 problem (1 error, 0 warnings)
1 error and 0 warnings potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
セミコンロを書いていないのでerrorが発生します。
lint:fix
を使ってルールを適用します。
$ yarn lint:fix
yarn run v1.22.4
$ eslint --fix './src/**/*.{js,ts,tsx}'
✨ Done in 0.90s.
import React from 'react';
import { Button } from 'react-native';
export type AppButtonPropsType = {
title: string,
onPress: () => void
}
-export const AppButton: React.FunctionComponent<AppButtonPropsType> = (props: AppButtonPropsType) => <Button title={props.title} onPress={props.onPress} />
+export const AppButton: React.FunctionComponent<AppButtonPropsType> = (props: AppButtonPropsType) => <Button title={props.title} onPress={props.onPress} />;
Prettierを動かしてみる
$ yarn format
yarn run v1.22.4
$ prettier --check ./src
Checking formatting...
[warn] src/components/AppButton.tsx
[warn] Code style issues found in the above file(s). Forgot to run Prettier?
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
対象ファイルでwarnが発生しているのでルールを適用します。
$ yarn format:fix
yarn run v1.22.4
$ prettier --write ./src
src/components/AppButton.tsx 164ms
✨ Done in 0.62s.
import React from 'react';
import { Button } from 'react-native';
export type AppButtonPropsType = {
title: string;
onPress: () => void;
};
-export const AppButton: React.FunctionComponent<AppButtonPropsType> = (props: AppButtonPropsType) => <Button title={props.title} onPress={props.onPress} />;
+export const AppButton: React.FunctionComponent<AppButtonPropsType> = (
+ props: AppButtonPropsType,
+) => <Button title={props.title} onPress={props.onPress} />;
Prettierのエラー予防
./src
ディレクトリ以下に画像ファイルなどを設置するとエラーが発生してしまうので、予防として .prettierignore
ファイルを作成します。
$ touch .prettierignore
.prettierignore
*.jpg
*.png
*.gif
*.ttf
必要そうな拡張子を適宜追加しましょう。
huskyとlint-stageを導入する
インストール
$ yarn add -D husky lint-staged
設定
package.json
の修正
...
- "private": true
+ "private": true,
+ "lint-staged": {
+ "./src/**/*.{js,ts,tsx}": [
+ "eslint --fix './src/**/*.{js,ts,tsx}'",
+ "prettier --write ."
+ ]
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ }
}
動作確認
一度 ./src/components/AppButton.tsx
をルール適用前に戻して、動作するか確認します。
import React from 'react';
import { Button } from 'react-native';
export type AppButtonPropsType = {
title: string;
onPress: () => void;
};
export const AppButton: React.FunctionComponent<AppButtonPropsType> = (props: AppButtonPropsType) => <Button title={props.title} onPress={props.onPress} />
$ git add .
$ git commit -m "comment"
husky > pre-commit (node v14.15.1)
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
[master a095dc0] comment
9 files changed, 5199 insertions(+), 16 deletions(-)
create mode 100644 .eslintrc.js
create mode 100644 .prettierrc.js
create mode 100644 package-lock.json
create mode 100644 src/components/AppButton.tsx
コミット後以下のようにルールが適用されます。
import React from 'react';
import { Button } from 'react-native';
type AppButtonPropsType = {
title: string;
onPress: () => void;
};
export const AppButton: React.FunctionComponent<AppButtonPropsType> = (
props: AppButtonPropsType,
) => <Button title={props.title} onPress={props.onPress} />;
Storybookの導入
Storybookは公式にReact Native向けのセットアップがありますが、
- react-native server
- storybook server
両方を立ち上げないとStorybookでコンポーネントを確認できないようです。
今回は react-native-web
のみ確認できればよいと割り切って導入します。
インストール
$ yarn add -D @storybook/react @storybook/addon-essentials babel-loader
※ babel-loaderはStorybookが使用するため必要でした。
設定
$ mkdir .storybook
$ touch .storybook/main.js
$ touch .storybook/webpack.config.js
.storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.tsx'],
addons: ['@storybook/addon-essentials'],
};
.storybook/webpack.config.js
module.exports = async ({ config }) => {
config.resolve.alias = {
'react-native$': 'react-native-web'
}
return config
};
'react-native$': 'react-native-web'
で react-native
を react-native-web
に解決させています。
npm scripts追加
package.json
...
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"lint": "eslint './src/**/*.{js,ts,tsx}'",
"lint:fix": "eslint --fix './src/**/*.{js,ts,tsx}'",
"format": "prettier --check ./src",
- "format:fix": "prettier --write ./src"
+ "format:fix": "prettier --write ./src",
+ "storybook": "start-storybook -p 7007",
+ "storybook:build": "build-storybook -o ./storybookPublic"
},
...
storybook:build
はNetlify上で実行しますがローカルで実行するとコミットにのってしまうので、
.gitignore
にアウトプットを追加しておきます。
.gitignore
node_modules/**/*
.expo/*
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store
+storybookPublic/
Storyを追加
Storybookの確認用に簡単なStoryを追加します。
$ touch ./src/components/AppButton.stories.tsx
./src/components/AppButton.stories.tsx
import React from 'react';
import { AppButton, AppButtonPropsType } from './AppButton';
import { Meta, Story } from '@storybook/react';
export default {
title: 'AppButton',
component: AppButton,
argTypes: {
onPress: {
action: 'pressed',
},
},
} as Meta;
const Template: Story<AppButtonPropsType> = (args) => <AppButton {...args} />;
export const Primary = Template.bind({});
Primary.args = {
title: 'Button',
};
Storybookを起動
$ yarn storybook
以下の画面がブラウザで表示されます。
StorybookをPull RequestのコンポーネントカタログのプレビューURLを載せる
事前準備
- Netlifyと連携できるクラウドリポジトリサービスへpush
- GitHub
- GitLab
- Bitbucket
- Netlifyのユーザー登録をする
Netlifyと連携
1. ログイン後のTeam overview画面の「New site from Git」ボタンをクリックします。
2. ご自身が連携したいサービスを選択します。
※ サービス選択後、はじめてそのサービスをNetlifyで選択した場合、認証が出てくると思います。
3. Pushしたリポジトリを選択します。
4. 以下内容を設定します。
- Build command:
yarn storybook:build
- Publish directory:
./storybookPublic
5. 設定後「Deploy Site」をクリック
Pull Requestを作ってみる
※ Production Branchに対してのPRしかプレビューができません。
コマンドは GitHubCLI を利用します。
$ git checkout -b feature/preview
$ git commit --allow-empty -m "Preview test"
$ gh pr create --base main
$ gh pr view -w
途中対話型でいろいろ聞かれますがすべてエンターでもPR作成は可能です。
最後のコマンドでPRの画面がWEBブラウザで表示されます。
一番下の「Details」をクリックすることで、Storybookを確認できます。
まとめ
StorybookをNetlifyで配信して、PRから飛べるのは非常に便利で、デザイナー確認などが捗ります。
React Native以外にもこの部分は使い回せるので、ぜひ試してみてほしいです。
Discussion
いい記事でした〜
prettierのインストール時、yarnがyarnnになっているのでよければ修正お願いします🙏