最近、React+TypeScriptの開発環境を作る場合、大抵はcreate-react-appを利用するケースが多いと思います。
実際、種々のベストプラクティスであったり、単純にアプリケーションを作る方にフォーカスする場合、create-react-appを利用するのがbetterです。ただ、色々な要因でejectせざるを得ない状況に追い込まれると、結構厳しいケースが多いです。実際公式でもejectは非推奨です。
そういうときに備えて(?)、0から環境を作ってみましょう。こういう経験をしておくと、ejectすること無くいろいろすることが出来ます。
今回作っていって見る環境は、次のような環境です。
- React + TypeScriptで書ける
- TypeScript内では、moduleは相対パスではなく、
@/hoge
のようにしてアクセスできる
- TypeScript内では、moduleは相対パスではなく、
- TSXで書ける
- ESLintでlintできる
- prettierでformatting
- Jest+enzymeでユニットテスト
- webpack-dev-serverでhot reloading
- Componentのreloadは色々問題もあるのでやりません
Reactの導入
まず新しいpackageを作りましょう。ここでは react-handmade-sample
という名前で作ります。
$ mkdir react-handmade-sample
$ cd react-handmade-sample
$ npm init
次に、React関連のパッケージをインストールします。styled-componentsは趣味なので、無くてもいいです。
$ npm install react react-dom styled-components
Webpackの導入
Webpackは、現時点でデファクトbundlerです。非常に設定量が多いと思われがちですが、必要最小限であれば、意外と量は少ないです。とりあえず必要なので入れます。webpack-dev-serverもついでにいれます。
$ npm install webpack webpack-dev-server
$ npm install html-webpack-plugin tsconfig-paths-webpack-plugin
Webpackは、developmentの場合に使う基本設定と、production時に利用する設定の二通りを用意しますが、とりあえずはdevelopmentの設定だけ記述します。
// webpack.config.js
const path = require('path');
module.exports = function(isProduction) {
return {
mode: "development",
entry: path.resolve(__dirname, "./src/index.tsx"),
// ファイルの出力設定
output: {
// 出力ファイルのディレクトリ名
path: path.resolve(__dirname, 'dist'),
// 出力ファイル名
filename: "index.js"
},
module: {
rules: [
]
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
plugins: [
]
},
plugins: [
]
};
};
空っぽに近いですが、とりあえず最小限です。
TypeScriptの導入
TypeScriptを書く以上、TypeScriptの導入は避けられません。ただ、webpackを利用するので、TypeScriptを直接入れると言うよりは、ts-loaderを導入します。BabelでもTypeScriptを利用できるのですが、Babelの場合、TypeScriptで得られる利点のいくつかを捨てることになります。なので、全体をTypeScriptで書く場合は素直にts-loaderを使うほうがよいかと。
$ npm install ts-loader tsconfig-paths-webpack-plugin @types/react @types/react-dom
tsconfig-paths-webpack-pluginは、TypeScriptのpathsオプションをwebpack内で利用する際に使います。これがないと、TypeScript→JavaScriptしたあとのbundleが動きません。
TypeScriptを使えるように、webpack.config.jsを書き換えます。
const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = function(isProduction) {
return {
mode: "development",
entry: path.resolve(__dirname, "./src/index.tsx"),
// ファイルの出力設定
output: {
// 出力ファイルのディレクトリ名
path: path.resolve(__dirname, 'dist'),
// 出力ファイル名
filename: "index.js"
},
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader',
options: {
configFile: "tsconfig.json"
}
}
}
]
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
plugins: [
new TsconfigPathsPlugin({ /*configFile: "./path/to/tsconfig.json" */ }),
]
},
plugins: [
]
};
};
TypeScriptの設定
@/hoge
のようにアクセスできる出来るようにするための設定を含めて、tsconfig.jsonを書きます。tsconfig.jsonは普通にpackage.jsonと同じディレクトリに置いておきます。
{
"compilerOptions": {
"sourceMap": true,
"target": "es2015",
"module": "es2015",
"jsx": "react",
"moduleResolution": "node",
"lib": [
"es2019",
"dom",
"dom.iterable",
"esnext"
],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"strictNullChecks": true,
"strict": true,
"incremental": true,
"isolatedModules": true,
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": [
"./src/**/*.ts",
"./src/**/*.tsx"
],
"exclude": [
"./node_modules",
"./src/**/*.spec.ts",
"./src/**/*.spec.tsx",
"./src/**/*.test.ts",
"./src/**/*.test.tsx"
]
}
今回はモダンブラウザだけを対象にするので、ES2015の形式にします。この辺りは各自の事情に依るので、あくまで一例です。
excludeでテストを抜いているのは、こうしないと色々問題があるのでこうしています。testは別ディレクトリに置く場合は、多分無くても大丈夫です。
webpack-dev-serverの設定
webpack.dev.js
として以下のような内容を作ります。
const config = require("./webpack.config.js");
module.exports = Object.assign(config(false), {
devtool: 'eval-source-map',
devServer: {
port: 3000,
contentBase: 'dist',
watchContentBase: true,
},
});
もともとのconfig.jsを再利用し、dev-serverの設定を追加します。
ESLintの設定
ESLintを導入します。TSLintを使わず、ESLintだけでやっていきます。
$ npm install eslint eslint_d eslint-config-prettier eslint-import-resolver-webpack eslint-plugin-import eslint-plugin-prettier eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser
他にもいろいろ導入しています。この辺りはプロジェクトとか好みとかに依るかと。
.eslintrc.js
を作ります。parserやpluginの設定、後はtsconfig.jsonやwebpack.config.jsの取り込みを行っています。
module.exports = {
plugins: ["@typescript-eslint"],
parser: '@typescript-eslint/parser',
extends: [
'plugin:prettier/recommended',
"plugin:react/recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
env: {
es6: true,
browser: true,
},
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
ecmaFeatures: {
jsx: true, // Allows for the parsing of JSX
},
project: "./tsconfig.json",
},
rules: {
"import/no-default-export": "error",
"react/jsx-uses-vars": ["warn"],
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"prettier/prettier": ['error'],
},
settings: {
react: {
version: 'detect',
},
"import/resolver": {
"webpack": {
"config": "webpack.config.js"
}
}
},
}
prettierの追加
フォーマットは、四の五の言わずprettierを使います。prettier_dを導入しているのは、Emacsで開発する際に、prettierをそのまま使っているとめっちゃ重いからです。
$ npm install prettier prettier_d
.prettierrc
はこんな感じで。semiは個人的な好みです。付けていかなくても、prettierが勝手に付けてくれるので、あんまり気にしなくていいです。
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 120,
"tabWidth": 2
}
Jestの導入
テストランナーは、だいたいデファクトになったJestを使います。
$ npm install jest ts-jest @types/jest
jestの設定は、個別の設定とかも出来るようですが、とりあえずpackage.jsonを使います。
"jest": {
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$"
],
"watchPathIgnorePatterns": [
"node_modules"
],
"moduleNameMapper": {
"^@/(.+)": "<rootDir>/src/$1"
},
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"setupFilesAfterEnv": [
"<rootDir>/scripts/setupTests.ts"
],
"moduleDirectories": [
"node_modules"
],
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
},
"globals": {
"ts-jest": {
"tsConfig": "tsconfig.json"
}
},
"testMatch": [
"**/__tests__/*.+(ts|tsx|js)",
"**/*\\.spec\\.+(ts|tsx|js)",
"**/*\\.test\\.+(ts|tsx|js)"
]
}
ts-jestとtsconfig.jsonを使うことで、pathsの問題とかも解消できます。 setupFilesAfterEnv
という部分に知らない設定がありますが、これはすぐあとで設定します。
Enzymeの設定
Componentのテストを効率よくやるために、enzymeを追加します。
$ npm install enzyme @types/enzyme enzyme-adapter-react-16
scripts/setupTests.js
を、以下のような内容で作成します。
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
export default undefined;
npm scriptsの設定
最後に、npm scriptsを追加します。
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"lint": "eslint 'src/**/*.ts[x]'",
"test:onetime": "jest --env=jsdom",
"test": "jest --env=jsdom --watch"
},
lint自体は、webpackの設定に組み込んで、webpack-dev-serverを実行している間にも実行することも出来ます。その辺りの設定は難しくないので、必要ならやってみるといいかと思います。
これは基本設定です
この辺りは、あくまで基本設定です。CSS moduleを使ったり、SVGをrequireしたりするようにしたり、babelを導入したり・・・と、色々やっていくことは出来ます。
ただ、設定が増えると後で変更しづらくなったり、設定の影響が把握しづらくなったりするので、程々にしておくのがおすすめです。実際、このくらいでも十分に開発していくことが出来ます。時間のあるときにでも、一度0から設定する、というのもいかがでしょうか。