目次

ライブラリバージョン

  • nodenv: 1.3.1
  • node: v12.13.1
  • npm: 6.12.1

ディレクトリ構成

.
├── .eslintrc.json
├── .prettierrc
├── dist/
├── src/
├── node_modules/
├── package-lock.json
├── package.json
├── tsconfig.json
└── webpack.config.js

プロジェクトの生成

mkdir ${project}
cd ${project}
npm init -y

パッケージインストール

webpack

npm install --save-dev webpack webpack-cli webpack-dev-server webpack-merge

typescript

npm install --save-dev typescript ts-loader

react

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

eslint

npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-webpack
# airbnbのeslintルールを利用する場合, eslint-config-airbnbとそれに依存するパッケージをインストール
npm install --save-dev eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks

prettier

npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

各設定ファイルの例

package.json

/package.json

{
  "name": "ts_tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --mode=production --progress",
    "watch": "webpack-dev-server --mode=development --watch --progress",
    "lint": "eslint src -c .eslintrc.json --ext .js,.jsx,.ts,.tsx"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^16.9.23",
    "@types/react-dom": "^16.9.5",
    "@typescript-eslint/eslint-plugin": "^2.23.0",
    "@typescript-eslint/parser": "^2.23.0",
    "eslint": "^6.8.0",
    "eslint-config-airbnb": "^18.1.0",
    "eslint-config-prettier": "^6.10.0",
    "eslint-import-resolver-webpack": "^0.12.1",
    "eslint-plugin-import": "^2.20.1",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-prettier": "^3.1.2",
    "eslint-plugin-react": "^7.19.0",
    "eslint-plugin-react-hooks": "^2.5.0",
    "prettier": "^1.19.1",
    "ts-loader": "^6.2.1",
    "typescript": "^3.8.3",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  },
  "dependencies": {
    "react": "^16.13.0",
    "react-dom": "^16.13.0"
  }
}

tsconfig.json

/tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "moduleResolution": "node",
    "lib": [
      "es2019",
      "dom"
    ]
  },
  "include": [
    "src/**/*"
  ]
}

webpack.config.js

/webpack.config.js

module.exports = {
  devtool: 'source-map',
  // モード値をproductionに設定すると最適化される状態で、
  // developmentに設定するとソースマップ有効でJSファイルが出力される。
  mode: 'development',

  // メインとなるjavascriptファイル(エントリーポイント)
  entry: './src/main.tsx',
  // ファイルの出力設定
  output: {
    // 出力ファイルのディレクトリ名
    path: `${__dirname}/dist`,
    // 出力ファイル名
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        // 拡張子.tsもしくは.tsxの場合
        test: /\.tsx?$/,
        // TypeScriptをコンパイルする
        use: 'ts-loader'
      }
    ]
  },
  // import文で.tsや.tsxファイルを解決するため
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json']
  },
  devServer: {
    contentBase: './dist',
    historyApiFallback: true,
    inline: true,
    hot: true,
    port: 5000,
    open: true
  }
};

.eslintrc.json

/.eslintrc.json

{
  "extends": [
    "eslint:recommended",
    "airbnb",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "plugins": [
    "@typescript-eslint",
    "react-hooks",
    "prettier"
  ],
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2018,
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "rules": {
    "prettier/prettier": "error",
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ],
    "react/prefer-stateless-function": 0,
    "react/jsx-filename-extension": [
      1,
      {
        "extensions": [
          ".js",
          ".jsx",
          ".ts",
          ".tsx"
        ]
      }
    ],
    "no-underscore-dangle": [
      "error",
      {
        "allowAfterThis": true
      }
    ]
  },
  "settings": {
    "import/resolver": "webpack"
  }
}

.prettierrc

/.prettierrc

{
  "printWidth": 100,
  "singleQuote": true
}

動作確認のためのその他ファイル (html, tsx)

  • /dist/index.html

    <!DOCTYPE html>
    <html lang="ja">
      <head>
        <meta charset="UTF-8" />
        <title>TS_Tutorial</title>
        <style>
          body {
            background: #eee;
          }
          #app {
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
          }
        </style>
        <script defer src="main.js"></script>
      </head>
      <body>
        <div id="app"></div>
      </body>
    </html>
    
  • /src/main.tsx

    import * as React from 'react';
    import * as ReactDOM from 'react-dom';
    import SubComponent from './sub_component';
    
    const App: React.FC = () => (
      <div>
        <h1>Hello React!</h1>
        <SubComponent name="My counter for TypeScript" />
      </div>
    );
    
    ReactDOM.render(<App />, document.querySelector('#app'));
    
  • /src/sub_component.tsx

    import * as React from 'react';
    
    type Props = {
      name: string;
    };
    
    type State = {
      count: number;
    };
    
    class SubComponent extends React.Component<Props, State> {
      _handleOnClick: () => void;
    
      constructor(props: Props) {
        super(props);
        this.state = {
          count: 0
        };
        this._handleOnClick = this.handleOnClick.bind(this);
      }
    
      handleOnClick(): void {
        this.setState((prevState: State) => {
          const prevCount = prevState.count;
          return { count: prevCount + 1 };
        });
      }
    
      render(): JSX.Element {
        const { name } = this.props;
        const { count } = this.state;
        return (
          <div>
            <h2>{name}</h2>
            <div>{`count: ${count}`}</div>
            <button type="button" onClick={this._handleOnClick}>
              Add count
            </button>
          </div>
        );
      }
    }
    
    export default SubComponent;