はぜにっき

日記です。(毎日更新ではない)

create-react-app(version=react-scripts-ts) で生成されたコードを読む

追記: 2019-10-20

なんかGoogle検索からこのエントリにきてくれる人がそこそこいるっぽいんですが、かなり前のVersionの時に書いたエントリなのでそこまで参考にしないでください。
現在はcreate-react-app 自体でTypeScript対応がされていてそちらが一般的なため、この内容とはおそらく大幅に内容が違います。

github.com

Microsoftが create-react-app のTypeScriptバージョンを提供しているらしく、それを使って React + TypeScript でアプリケーションを作る練習をしようとした。
んだけど、どういう手順で作って行くのかがいまいちよくわからなかったので、とりあえず生成された中身を読んでみることした。

github.com

create-react-app の本体リポジトリにもTypeScriptのサポートが入るみたいで試してみたけど、上手く動かなかったのでやめた。
(型の確認まではできるようになったけど、 ソースコード.js のままで、全部書き換えないといけなかった。それはそれでTypeScriptの勉強になりそうだからそのうちやりたい。)

レベル感

自分は、Node.js を使った外のAPIを叩いたりJSONを投げるアプリケーションは作ったことはあるけど、フロントエンドに関しての実装はしたことがない状態。
jQueryも知らないし、画面描画に関わるJavaScriptのコードはほぼ全く読めないと言ってもいい感じ。

Reactは2回本の写経をしたけど、あんまりちゃんと理解していない。コンポーネントのpropsとstateの使い分けは本に書いてあったから雰囲気でわかる、と思っていたけど完全に忘れた。
TypeScriptは名前しか知らなくて、型付きのJavaScriptコンパイルしてくれる便利なやつ という認識。
本の写経したとき雰囲気だけはわかったけど、細かい仕様はわかってない。

ソースコードを読む前にやったこと

前提となるES2015〜のJavaScriptの文法を確認する

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発 を軽く読む。 jQueryの話、基本的なデータ構造周りの話は飛ばして、ちゃんと理解しているか怪しい部分だけさらった。
具体的には、 ジェネレータ( function * )、class、prototype周り。

TypeScriptの基本的な部分を理解する

Qiitaを読んだりした。
Typeのことがよくわからなかったが、Qiita: TypeScriptのInterfaceとType aliasの比較 で雰囲気は理解したと思う。

init-appをつくる

$ npx create-react-app init-app --scripts-version=react-scripts-ts
$ cd init-app
$ npm run eject

github.com

これができる。
npm run eject は、create-react-app側で良い感じにまとめられていたパッケージを分解する処理を走らせる。

eject前は、 package.jsonのscripts欄は

  "scripts": {
    "start": "react-scripts-ts start",
    "build": "react-scripts-ts build",
    "test": "react-scripts-ts test --env=jsdom",
    "eject": "react-scripts-ts eject"
  },

こうなっていて、ejectをすると

  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom"
  },

こうなる。
react-scripts-ts という便利コマンドが作られていて、それを解除するという感じ。

ちなみに、 start を実行をしたときに走る scripts/start.js は以下。

create-react-app-ts-init/start.js at master · haze-it/create-react-app-ts-init · GitHub

初期状態では混乱しないようにパッケージングされていて見えなくなっている、ということ。
(そもそもejectをする前は /scripts ディレクトリが存在しない)

中身

読んでいきましょう。

起動 - scripts/start.js

起動コマンドは npm run start か、 yarn start

f:id:hazeblog:20181029210648p:plain:w300

さっきも書いた通り、 start を実行すると、 scripts/start.js が実行される。
create-react-app-ts-init/start.js at master · haze-it/create-react-app-ts-init · GitHub

まず、14行目で config/env.js が読み込まれている。

f:id:hazeblog:20181029211757p:plain:w400

config/env.js はこれ。

create-react-app-ts-init/env.js at master · haze-it/create-react-app-ts-init · GitHub

env.js の中では、キャッシュを消したり、 NODE_ENV が存在するか確認したり、 .dotenv ファイルを読み込んだり、 NODE_PATH を設定したりしている。

その後は、色んなモジュールを読み込んで

f:id:hazeblog:20181029213333p:plain:w500

起動するためのホスト(通常は 0.0.0.0 )とポート(通常は 3000 )を指定している。
(この下に書かれているものは Cloud9内で起動した場合に実行される内容なので飛ばす)

f:id:hazeblog:20181029213824p:plain:w400

そして、63行目の choosePort でポートを取得する。
f:id:hazeblog:20181029214159p:plain:w300

この choosePort メソッドは react-dev-utils/WebpackDevServerUtils から持ってきているもので、「指定のポートが空いていたら獲得し、空いていなかったらユーザに次のポートを取りに行くかを確認する」機能を持つ。
試しに3000番ポートを埋めた(別Appを起動させた)状態で npm run start をすると、以下のようなエラーメッセージが出て、 y とかエンターを押すと 3001番ポートを取りに行ってくれる。

f:id:hazeblog:20181029214700p:plain:w400

npm - react-dev-utils: WebpackDevServerUtils
便利。

choosePort が成功したら、HTTP or HTTPS、Appの名前を確認して、URLを設定して、

f:id:hazeblog:20181029215103p:plain:w400

webpackで各モジュールをバンドル(複数のファイルをまとめる)して、

f:id:hazeblog:20181029215353p:plain:w500

proxyの設定を package.json から取ってきて

f:id:hazeblog:20181029215633p:plain:w500

起動!!+ブラウザを開きます。

f:id:hazeblog:20181029215754p:plain:w600

その下には Ctrl+C 押したりした時に終了する処理が書かれている。

f:id:hazeblog:20181029220650p:plain:w500

というのが start.js の処理。この辺を全部勝手に用意してくれる create-react-app便利だなーという感じ。

src/index.tsx

ここからReact。

最初に開かれるアプリケーションは src/index.tsx
これは paths.jsappIndexJs という定数部分に記述されていて、 webpack.config.[env].jsentry でそれを最初に実行するように設定されている。
(webpackの仕様)

path.js
f:id:hazeblog:20181029223008p:plain:w400

webpack.config.dev.js
f:id:hazeblog:20181029223132p:plain:w600

src/index.tsx
create-react-app-ts-init/index.tsx at master · haze-it/create-react-app-ts-init · GitHub

短め。

react , react-dom モジュールをimportして、
./App.tsx , ./index.css , ./registerServiceWorker.tsx を取ってくる。

App が実際のアプリケーションで、 index.css は普通のライブラリで、 registerServiceWorker.tsx は名の通りServiceWorkerの部分。

行なっているメインの処理はこれだけ。
ReactのDOMに App.tsx の中身 と index.html 内にある <div id="root"></div> を投げている。 f:id:hazeblog:20181029225225p:plain:w500

div rootHTMLElement にキャストされている。 HTMLElement は任意のHTML要素を表すものらしい。

HTMLElement - Web API | MDN

document.getElementById() は普通のJSのやつ。

Document.getElementById() - Web API | MDN

そのあとにServiceWorkerを走らせているけど、これは後述。

App.tsx

create-react-app-ts-init/App.tsx at master · haze-it/create-react-app-ts-init · GitHub

メインの処理部分。
react モジュールをimportして、cssとlogoを取る。

f:id:hazeblog:20181029231203p:plain:w600

Reactはコンポーネントという単位でUIを分けていて、その定義は、 React.Component を継承することで実現する。( extends React.Component

初期状態だとコンポーネントは一つしかないので、この App コンポーネントが画面全てを描画している。

f:id:hazeblog:20181029232224p:plain:w500

一般的なReactアプリケーションだと、entryにルーティング用のtsxをかませて、それでコンポーネントを出し分けたりするっぽい。

training-react-app/webpack.config.js at master · haze-it/training-react-app · GitHub

training-react-app/Routes.tsx at master · haze-it/training-react-app · GitHub

くるくる回るロゴは普通にCSS animationだった。(なんかのReactの機能を使ってると思ったら全然そんなことなかった)

f:id:hazeblog:20181029233132p:plain:w500

メイン処理は以上。

registerServiceWorker.ts

create-react-app-ts-init/registerServiceWorker.ts at master · haze-it/create-react-app-ts-init · GitHub

Service Workerってなんだ。雰囲気しか知らない。

developers.google.com

ブラウザ上の描画とは別に、スクリプトが実行できる環境が用意されていて、そこでプッシュ通知の処理をしたり、バックで処理走らせたりができるもの。
Webページ側の処理とは全く別のものなので、バックグラウンドで同期処理を走らせたりしても描画に影響はないみたい。便利そう。

registerServiceWorker.ts を読んでいく。

前半の事前処理で多用されている window.location について 。表示しているURLの情報とかを取ってこれるっぽい。
Window.location - Web API | MDN

envがproductionじゃないと登録処理がそもそも行われないようになっている。
f:id:hazeblog:20181029235801p:plain:w600

後ろの 'serviceWorker' in navigator は ブラウザが対応しているかを確認する処理らしい。
Navigator.serviceWorker - Web API | MDN

これは、動くworkerが別ドメインのものでないかを確認している。セキュリティ対策かな?
CDNを使ったりする場合はそのURLを入れたりする処理も必要になるっぽい。
f:id:hazeblog:20181030001128p:plain:w600

で、メインの登録処理と実行処理。
f:id:hazeblog:20181030001509p:plain:w700

swURL は、 実行するスクリプトのURLを入れている。
初期状態だとローカルホストでしか動かないようになっていて、 checkValidServiceWorker(swUrl: string) で、別のアプリのservice workerが登録されていないかを確認している。
checkValidServiceWorker は下の方で定義されている)

navigator.serviceWorker.ready.then(() => { .... } でserviceworkerを登録する。
直接DOMを編集できないので、 postMessage という機能を使って送り込む処理が必要らしい。その辺は別の記事を読んでください。

App.test.tsx

create-react-app-ts-init/App.test.tsx at master · haze-it/create-react-app-ts-init · GitHub

create-react-app-ts-init/README.md at master · haze-it/create-react-app-ts-init · GitHub

Testing-Components というものがあるらしい。
テストは追々で。


というわけで、一部雑に飛ばしたけど、メインの機能周りはこれでおしまい。
package.jsonとかtsconfigとか、configのpolyfill(ブラウザで対応していない機能を独自で実装するやつ)とかjest(テスト)辺りは後々深く見ていこうと思う。

とりあえず、次はイメージする画面を作る練習をいくつかする予定。