TypeScriptのReact-Webpack DevServer実行

TypeScriptのReactアプリを作るに際し、一番シンプルなのはWebpack DevServerで実行だけするというものかなと思います。NodeとYarnはすでに入ってること前提で、あとはテキストエディタ&WEBブラウザだけでOK。

$ mkdir TsReact
$ cd TsReact
$ yarn init
$ yarn add react react-dom
$ yarn add @types/react @types/react-dom -D
$ yarn add typescript webpack webpack-dev-server path awesome-typescript-loader -D

まずターミナルからプロジェクトの基盤を作ります。フォルダ作ってYarnでやるべきことをやる。reactとwebpack関係はES6のプロジェクトと一緒ですが、Babelの一群がtypescriptひとつで代替されるので必要なものが格段に少なくなります。

// tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "jsx": "react"
    }
}

TypeScriptのコンパイラ(=トランスパイラ)の設定をプロジェクトルートのtsconfig.jsonに書きます。まずは最低限で上記のとおり。ブラウザで実行するのでes5がターゲット。スキーマを見るとjsxはreact-domかreact-nativeか処理しないか(preserve)。例のFacebook OSSライセンスが揉めれば、いずれはpreserveで非Reactなものに対応させるようなことあるかもしれませんが、今はReact以外はありません。

このtsconfig.jsonの内容をpackage.jsonに格納する方法を探したけど見つからなく、どうもできないみたい。BabelやEslintみたいに格納させて欲しいなあ。

http://json.schemastore.org/tsconfig

// webpack.config.development.js
const path = require('path');

module.exports = {
    entry: './ts/index.tsx',
    output: {
        filename: 'bundle.js',
        publicPath: '/js/',
    },
    devtool: 'inline-source-map',
    module: {
        rules: [{
            test: /\.(ts|tsx)$/,
            use: 'awesome-typescript-loader',
        }],
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js'],
    },
    devServer: {
        host: 'localhost',
        port: 9000,
        contentBase: path.resolve('static'),
    },
};

Webpack DevServerの設定ですが、こちらはプロジェクトルートのwebpack.config.development.jsへ。あとでproduction環境も作りますから、はじめからdevelopmentをいれたファイル名にしておきました。/tsフォルダにソースコードを置き、/js/bundle.jsという名前で配信するようにしています。/static/index.htmlに静的なエントリHTMLをおいて、これはdevServer.contentBaseで静的ファイルサービスの設定をしました。bundle.jsの読み込みとアプリケーションホルダーのdivを持つだけのHTMLです。

注意としては、resolveに、TypeScriptのソース拡張子である.tsおよび.tsxに加えて、.jsも並べること。Webpack DevServerが背後でjsをステルスに提供しているのでそれも解決できるようにしないとトランスパイルエラーが出ます。TypeScriptは、awesome-typescript-loaderが処理します。同じ役割でts-loaderというのも世にありますがドキュメントを読んでみた結果、後に行うHMR環境を作るのはこちらが良いとのことなのでawesomeで。

// /components/Hello.tsx
import * as React from 'react';

export type Props = {
    compiler?: string;
    framework: string;
};

export const Hello = (props: Props) => (
    <h1>Hello from {props.compiler || 'JavaScript'} and {props.framework}!</h1>
);



// /components/Counter.tsx
import * as React from 'react';

export class Counter extends React.Component<{}, { counter: number }> {
    constructor(props) {
        super(props);
        this.state = { counter: 0 };
    }

    private interval: number;

    componentDidMount() {
        this.interval = window.setInterval(
            () => this.setState({ counter: this.state.counter + 1 }),
            1000,
        );
    }

    componentWillUnmount() {
        window.clearInterval(this.interval);
    }

    render() {
        return <h2>{this.state.counter}</h2>;
    }
}



// /index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import { Hello } from './components/Hello';
import { Counter } from './components/Counter';

ReactDOM.render(
    <div>
        <Hello compiler="TypeScript" framework="React" />
        <Counter />
    </div>,
    document.getElementById('application'),
);

簡単なReactアプリとして、二つのコンポーネント(Hello.tsxとCounter.tsx)をindex.tsxでまとめるものです。HelloがStateless Componentでプロパティを設定するもの、CounterがClass Componentでステートを持つものにしました。Flowを書いてみた後でTypeScriptを触ると、なんて一緒なのだろうと。探せば細かく違いはありながらも概ね違和感なく、TypeScript初手のはずなのに特に疑問なくすぐ慣れます。まさにES6+FlowでTypeScriptだわ。

// package.json
{
    "name": "TsReact",
    "version": "1.0.0",
    "private": true,
    "scripts": {
        "start": "webpack-dev-server --config webpack.config.development.js"
    },
    "dependencies": {
        "react": "^15.6.1",
        "react-dom": "^15.6.1"
    },
    "devDependencies": {
        "@types/react": "^16.0.5",
        "@types/react-dom": "^15.5.4",
        "awesome-typescript-loader": "^3.2.3",
        "path": "^0.12.7",
        "typescript": "^2.5.2",
        "webpack": "^3.5.5",
        "webpack-dev-server": "^2.7.1"
    }
}

package.jsonにscripts.startを足します。おまけでprivateをtrueにして、ライセンス等の情報は削りました。private=trueにしておくと削っても警告されません。そうでなければ折に触れてnpmやyarnより警告表示されます。

$ yarn start

yarn startでWebpack DevServerが起動します。yarn run startのショートハンドでyarn startが有効。

f:id:masataka_k:20170906061030p:plain

動かすとこんな感じ。今回、同時にAtomを入れてみました。AtomでTypeScript環境つくるのも簡単。結構成熟している。