WebpackとReact(TypeScript)を使ったフロント開発の環境構築
目次
ライブラリバージョン
- 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;