React Advent Calendar 2016 に空きがあったのでねじ込みました。
仕事ではないけど普段はAngularでフロントエンドを書いています。ちきさんです。
関数型ReactでビジュアルFizzBuzz があまりにも読みやすいソースコードで、読んでるうちに僕もReactが書ける気がしてきたので挑戦してみました。
ところでAngularにはangular-cliという環境構築ツールがあります。
オレオレ環境構築は過去に散々やってきたので、またReactでオレオレは嫌だなと思っていたらツイッターで facebookincubator/create-react-app の存在を教えていただきまして、使ってみたらあっさり環境が出来上がったのでワオと思ったのですがコレ当然なんですけどJavaScript用の環境なんですね。
そのままではTypeScript脳の僕には使えないので仕方なくタイトルのようなことをした次第。そして備忘録も兼ねて記事に残しておこうというわけです。
ちなみに僕が初めて書いたReactアプリは ovrmrw/my-first-react-typescript です。オンラインデモは こちら です。宜しければどうぞ。
ではここから本題です。
create-react-appをインストール
create-react-app
という長い名前のCLIをインストールします。もうちょっと名前なんとかならなかったんですかね。
プロジェクト作成時にyarn
が使われるので一応yarn
も加えておきます。
後々必要になるのでついでにtypescript
とtslint
もインストールしましょう。
$ npm i -g create-react-app yarn typescript tslint
Reactプロジェクトを作る
適当なフォルダに移動して下記のコマンドを実行します。
$ create-react-app my-app
my-app
というフォルダが作成されて、そこに必要なものがダウンロードされるでしょう。
結構時間かかります。
npm run ejectで環境を一旦壊す
デフォルトではJavaScript + Babelで開発するようになっていますし、Webpackの設定も含めて諸々の設定ファイルがnode_modules
の中に隠蔽されてしまっています。
TypeScriptで書きたいのでこれでは困ってしまいますね。実はそういう人のためのコマンドが用意されています。
$ npm run eject
これは不可逆な操作なので取扱注意です。ドキュメントにも以下のように書いてあります。
Note: this is a one-way operation. Once you
eject
, you can’t go back!
しかし我々はこれをやらなければ先には進めません。I'll never go back. 遠慮せずにやっちまいましょう。
するとAre you sure you want to eject? This action is permanent. [y/N]
と聞かれるのでもちろん y
と入力してエンターを叩きます。
僕のWindows環境では最後にバッチ ファイルが見つかりません。
と表示されてエラーになりますが気にしなくてOKです。
TypeScriptをインストール
TypeScriptとか必要そうなものをインストールします。とりあえず最低限これぐらいは入れておいた方がいいでしょう。
$ yarn add --dev typescript tslint awesome-typescript-loader @types/react @types/react-dom @types/node
(VSCodeのみ)
僕はVSCodeしか使わないので他のエディタの場合はどうするのか知りませんが、VSCodeの場合はプロジェクトのワークスペース設定に下記のように書く必要があります。
{
"typescript.tsdk": "./node_modules/typescript/lib"
}
これによりTypeScriptのエンジンとしてプロジェクトのnode_modules
フォルダにインストールされたものが使用されるようになります。
tsconfig.jsonを作成する
プロジェクトのルートフォルダで下記のコマンドを実行します。
$ tsc --init
するとこういうファイルが作成されますね。
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false
}
}
それを僕ならこんな感じに書き直します。
{
"compilerOptions": {
"outDir": ".dest-tsc",
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"jsx": "react",
"lib": [
"es2017",
"dom"
],
"typeRoots": [
"node_modules/@types"
]
},
"include": [
"src/**/*"
]
}
ついでにsrc
フォルダにもtsconfig.json
を作成します。
TypeScript 2.1からはextends
を使って別のconfigを継承できるので試してみましょう。
{
"extends": "../tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"strictNullChecks": true,
"skipLibCheck": true
}
}
これでルートのtsconfig.json
を継承して新しいconfigが作れました。実はこの2つのファイルは目的が違います。
-
/tsconfig.json
... Webpackでバンドルするときに読み込まれるバンドル用の設定。というのもあるけどルートにtsconfig.jsonがないと何かと躓くことが多い。 -
/src/tsconfig.json
... エディタやtslintが読み込んでリアルタイムに型チェックするエディタ用の設定。
僕は主に前者はnullチェックを入れずに、後者にはnullチェックを入れます。
tslint.jsonを作成する
プロジェクトのルートフォルダで下記のコマンドを実行します。
$ tslint --init
するとtslint.json
が作成されます。
そのままだと一部使い勝手が良くないので少々変更しましょう。
下記の部分を...
"quotemark": [
true,
"double"
],
"semicolon": [
true,
"always"
],
下記のように書き換えます。
"quotemark": [
true,
"single",
"jsx-double"
],
"semicolon": [
false,
"always"
],
jsRules
とrules
の2セクションに分かれているので両方とも変更します。
その結果こうなります。
- 文字列はシングルクォーテーションで囲み、JSXの中だけはダブルクォーテーションで囲む。
- セミコロンは省略できる。
このへんは個人の好みがあるので各自好きなように設定しましょう。
動作確認してみる
TypeScript環境化する前に一度アプリを動かしてみましょう。
$ npm start
これでブラウザが起動して「Welcome to React」が表示されればOKです。
さて今はJSで動いていますが、これからTS化の作業をしていきます。
Webpackの設定を書き換える
まずconfig/paths.js
の下記の行を
appIndexJs: resolveApp('src/index.js'),
下記のように変更します。
appIndexJs: resolveApp('src/index.tsx'),
次にconfig/webpack.config.dev.js
です。変更箇所が多いのでテンポ良くいきましょう。
Before
extensions: ['.js', '.json', '.jsx', ''],
After
extensions: ['.ts', '.tsx', '.js', '.json', '.jsx', ''],
Before
{
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.css$/,
/\.json$/,
/\.svg$/
],
loader: 'url',
query: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
After
{
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.css$/,
/\.json$/,
/\.svg$/,
/\.tsx?$/,
],
loader: 'url',
query: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
Before
{
test: /\.svg$/,
loader: 'file',
query: {
name: 'static/media/[name].[hash:8].[ext]'
}
}
After
{
test: /\.svg$/,
loader: 'file',
query: {
name: 'static/media/[name].[hash:8].[ext]'
}
},
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader',
}
ここまでです。config/webpack.config.prod.js
も同じように変更しておくと良いですね。ここでは省略します。
index.js --> index.tsx
いよいよ大詰めです。
srcフォルダのindex.js
をindex.tsx
に変えましょう。拡張子を変えるだけです。
少しソースコードも書き換えましょう。
Before
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<App />,
document.getElementById('root')
);
After
import * as React from 'react'; // 変更
import * as ReactDOM from 'react-dom'; // 変更
import App from './App';
import './index.css';
ReactDOM.render(
<App />,
document.getElementById('root')
);
App.js --> App.tsx
App.js
もApp.tsx
のように拡張子を変更します。
ソースコードも一部変更します。
Before
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
After
import * as React from 'react'; // 変更
const logo = require('./logo.svg'); // 変更
import './App.css';
class App extends React.Component<{}, {}> { // 変更
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
動かしてみよう
さあ、これでcreate-react-app
で作った環境のTypeScript化が完了しました。
動かしてみましょう。
$ npm start
「Welcome to React」が表示されましたね。You did it!
GitHub Pagesにデプロイする
ここからはGitHub Pagesにデプロイする方法を書きます。デプロイしたい方だけ読んでください。
まずgh-pages
というライブラリをインストールします。
$ yarn add --dev gh-pages
次にpackage.json
を変更します。
Before
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom"
},
After
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom",
"tsc": "tsc",
"lint": "tslint --type-check \"src/**/*.ts\" \"src/**/*.tsx\" ",
"deploy": "npm run build && gh-pages -d build"
},
"homepage": "https://<your-account-name>.github.io/<your-project-name>",
"tsc"
と"lint"
の行は無くても構いません。
"homepage"
の行は重要です。account-nameとproject-nameのところをご自身の環境に合わせて変更してください。
これでデプロイできるようになりました。下記のコマンドを実行しましょう。
$ npm run deploy
デプロイが完了したら先程の"homepage": "https://<your-account-name>.github.io/<your-project-name>"
で指定したURLをブラウザで開きます。
ちなみに僕がデプロイしたものは こちら です。
お疲れさまでした。