TypeScript,WebPackを利用したReactJS開発環境セットアップまとめ

TypeScriptの公式チュートリアル React & Webpack · TypeScriptをベースに、TypeScriptで記述したReactJSアプリをWebpackでビルドする開発環境の構築手順についてまとめています。

プロジェクトのディレクトリ構成を準備

プロジェクトの構成は、以下のようにsrcディレクトリにReactJSのコンポーネントを置き、distディレクトリをWebpackのビルドターゲットとします。

以下コマンドで、プロジェクトのディレクトリ構成を作成します

$ mkdir reactjs-ts-webpack
$ cd reactjs-ts-webpack/
$ mkdir dist
$ mkdir -p src/components
$ tree
.
├── dist
└── src
    └── components

3 directories, 0 files

npmプロジェクトの初期化とパッケージのインストール

npm initコマンドで、package.jsonを生成します。

$ npm init -y

まずは、Webpackをインストールします。

ここではローカルにインストールしてみます。

$ npm install --save-dev webpack

Webpack公式ドキュメント: Local Installation

ReactJS関連のパッケージ reactreact-dom、また、それらをTypeScriptで利用するための型定義ファイル(declaration files)を含むパッケージをインストールします

$ npm install --save react react-dom @types/react @types/react-dom

開発用のパッケージとして、typescriptと、Webpackでtypescriptをビルそするためのawesome-typescript-loader、ソースマップを生成するsource-map-loaderをインストールします

$ npm install --save-dev typescript awesome-typescript-loader source-map-loader

package.jsonは以下のようになりました。

// package.json
{{
  "name": "reactjs-ts-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.tsx",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "awesome-typescript-loader": "^3.2.3",
    "source-map-loader": "^0.2.1",
    "typescript": "^2.5.2",
    "webpack": "^3.5.6"
  },
  "dependencies": {
    "@types/react": "^16.0.5",
    "@types/react-dom": "^15.5.4",
    "react": "^15.6.1",
    "react-dom": "^15.6.1"
  }
}

TypeScriptの設定

TSをJSにコンパイルするawesome-typescript-loaderは、TypeScriptの設定ファイルtsconfig.jsonを参照します

tsconfig.jsonは、以下のように記述します

// tsconfig.json
{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es5",
        "jsx": "react"
    },
    "include": [
        "./src/**/*"
    ]
}

tsconfig.jsonの詳細はこちら
(日本語訳: tsconfig.json | TypeScript 日本語ハンドブック | js STUDIO )

上記に設定されているもののみ説明します。

compilerOptions

compilerOptionsは、TypeScriptのコンパイラtscの実行に指定するオプションです。
各オプションについては、Compiler Options · TypeScriptで参照できます。
参照: TypeScript1.6系のコンパイラのオプション一覧 – Qiita

オプション 説明
outDir コンパイルして生成されたjsファイルの出力先ディレクトリ。後に説明するwebpack.config.jsoutput.pathでも指定しているのでここでは省略しても良い。
sourceMap ソースマップを作成するかどうか。ソースマップはoutDirに指定したディレクトリに出力されます。こちらもwebpack.config.jsdevtoolにてソースマップの出力が設定されているので省略可。
noImplicitAny trueに設定すると暗黙のAny型の宣言がエラーになります ※1
module モジュール管理方法を指定します。commonjs, es6, amdなどを指定できます ※2
target 出力されるjsのECMAScript バージョン
jsx TypeScriptのコンパイルプロセスで、JSXもコンパイルする場合はreactを指定します。その他のオプションなど詳細は、JSX · TypeScriptを参照ください。

※1 暗黙のAny型について

例えば、以下のコードをコンパイルすると、エラーになります。

// greeting.js
function greeter(person) {
    return "Hello, " + person;
}

コンパイルの実行結果

$ tsc greeting.ts --noImplicitAny true
greeting.ts(1,18): error TS7006: Parameter 'person' implicitly has an 'any' type.

※2 モジュールについて参考情報

includes

対象ファイルをglobe風のファイルパターンで指定します。

サンプルコード

Reactコンポーネント

TypeScriptで、Reactコンポーネントを記述します。
JSXを含むファイルの拡張子はtsxを使用します。拡張子tsxのファイルが、jsxオプションにreactを指定した場合にJSXのコンパイル対象となるようです。

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

// propsの型定義を指定します
export interface HelloProps { compiler: string; framework: string; }

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

propsの型定義は、ES6でprop-typesを使うより簡潔に記述できる気がします

components/Hello.tsxをimportして表示するエントリーファイル

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

import { Hello } from "./components/Hello";

ReactDOM.render(
    <Hello compiler="TypeScript" framework="React" />,
    document.getElementById("example")
);

HTML

// index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello React!</title>
    </head>
    <body>
        <div id="example"></div>

        <!-- Dependencies -->
        <script src="./node_modules/react/dist/react.js"></script>
        <script src="./node_modules/react-dom/dist/react-dom.js"></script>

        <!-- Main -->
        <script src="./dist/bundle.js"></script>
    </body>
</html>

react.jsreact-dom.jsはバンドルに含めずに、node_modulesから直接読み込んでいます。
これは、次に説明するwebpack.config.jsexternalsで設定していますが、実際にはCDNからソースを読み込む場合などに利用します。

Webpackの設定

// webpack.config.js
module.exports = {
    entry: "./src/index.tsx",
    output: {
        filename: "bundle.js",
        path: __dirname + "/dist"
    },

    // Enable sourcemaps for debugging webpack's output.
    devtool: "source-map",

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".ts", ".tsx", ".js", ".json"]
    },

    module: {
        rules: [
            // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
            { test: /\.tsx?$/, loader: "awesome-typescript-loader" },

            // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
            { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
        ]
    },

    // When importing a module whose path matches one of the following, just
    // assume a corresponding global variable exists and use that instead.
    // This is important because it allows us to avoid bundling all of our
    // dependencies, which allows browsers to cache those libraries between builds.
    externals: {
        "react": "React",
        "react-dom": "ReactDOM"
    },
};

entry

Webpackが、バンドルするモジュールの解決を開始するファイル(またはファイルのリスト)を指定します。

Core Concept: Entry
設定リファレンス: entry

output

Webpackが、バンドルした結果を出力するファイルを指定します。

Core Concept: Output
設定リファレンス: Output

modules

ローダーは、testにマッチするソースファイルを、Webpackが依存性を解決可能なモジュールに変換する処理を実行するものです。

ここではTypeScriptをJSにコンパイルするローダーawesome-typescript-loaderを指定しています。

Core Concept: Loaders

resolve

resolveは、モジュールの依存性を解決方法に関するオプションを指定します。

ここではextensionsを設定していますが、
extentionsに指定された拡張子のファイルは、モジュールの読み込みに拡張子を省略することができるようになります。

例えば、src/index.tsxでは、./components/Hello.tsximportで拡張子を省略しています。

// src/index.tsx

import { Hello } from "./components/Hello";

devtool

devtoolは、開発用にソースマップを生成するかどうかを指定します。

ここでは、source-mapを指定しているので、元のTypeScriptをデバッグで参照できるようになります。

webpackでsource-mapを表示できるようにする – Qiita

externals

externalsは、依存性の解決から除外するモジュールを指定します。

ここでは、react.jsreact-dom.jsを指定しているため、src/index.htmlでは、2つのファイルを直接scriptタグで読み込んでいます。

ビルドの実行

$ ./node_modules/.bin/webpack 

[at-loader] Using typescript@2.4.2 from typescript and "tsconfig.json" from /Users/hrendoh/workspace/reactjs-ts-webpack/tsconfig.json.


[at-loader] Checking started in a separate process...

[at-loader] Ok, 0.227 sec.
Hash: 9ecf7f5e1ec6d5984958
Version: webpack 2.2.1
Time: 1373ms
        Asset     Size  Chunks             Chunk Names
    bundle.js  3.57 kB       0  [emitted]  main
bundle.js.map  3.95 kB       0  [emitted]  main
   [3] ./src/index.tsx 332 bytes {0} [built]
    + 3 hidden modules

ソース変更の度にビルドするようにウォッチするには--watchオプションを付けて実行します

$ ./node_modules/.bin/webpack --watch

ローカルインストールしていると特にwebpackのコマンドは長くなるので、以下のようにpackage.jsonにスクリプトを定義しておくと楽です

// package.json
{
  // ...
  "scripts": {
    "watch": "./node_modules/.bin/webpack --watch"
  },
  // ...
}

以下のコマンドでウォッチできるようになります

$ npm run watch

参考: NPM Scripts

実行確認

$ open index.html

以下のように表示されました

参考

TypeScriptの型定義ファイルについて