本エントリーはAdvent Calendar 2021「初めてのアドベントカレンダー Advent Calendar 2021」の15日目のエントリーです!
合わせてこれまでの「初めてのアドベントカレンダー Advent Calendar 2021」のエントリーも是非読んでみてください!
はじめに
こんにちは。@hironomiuです。
今回は「Teamうどん」さん主催のアドベントカレンダー「初めてのアドベントカレンダー Advent Calendar 2021」の15日目にエントリーしたいと思います!
エントリーテーマ
Reactを書く際にcreate-react-appはとても便利ですよね!仕事柄1日1回はcreate-react-appするほど普段大変お世話になっておりリスペクトしているのですが、たまに自前でReact環境を用意したいなと思うことがあり、今回はタイトルの通り「脱 create-react-app」をゴールにReact(+ Redux Toolkit) + Tailwind + TypeScript + Webpack + Babel + ESLint + JEST の環境構築をエントリーしたいと思います。(とは言え今後もcreate-react-appはガシガシ使いますけどね!)
コード
今回構築したコード全体はGitHubに置いてあります。動作を確認したい方はgit clone
しyarn install
で環境構築,yarn start
で devServerの起動、yarn run build
でサンプルコードのカウンターアプリのビルドの確認ができます。
GitHub@hironomiu typescript-webpack-react-template
以降長いのでwebpack
を今回入門する方は先にgit clone
し動作のイメージを掴んでから進むのをオススメします。
またGitHubリポジトリ中のサンプルアプリ(React + Redux Toolkit)はVercelにデプロイしてあります。
一応裏はRedux Toolkitでカウンターを実装していますが、とは言え見た目はただのカウンターアプリなので上でgit clone
しローカルで動作確認するので十分ですが落穂拾いでデプロイについても軽く触れたいと思うので載せておきます。
利用パッケージ管理ツール、パッケージの紹介
今回利用するパッケージ管理ツール、パッケージは以下です。
パッケージ管理ツール
create-react-app
と同様にyarn(v1)でパッケージ管理し構築ました
React
特に説明の必要はないと思います。Reactです。
Redux Toolkit
Reactで状態の管理は基本Redux Toolkitに任せるのが良いんじゃないかな?と個人的には思ってるぐらい重宝しています。
Tailwind
CSSは最近使っていて自分的に使い勝手がよく感じるTailwindを選択しました。
ビルドの章で後述してますが、当初はv2を利用していましたがこのエントリーを書いているところv3に上がっていたためv2,v3の設定を今回は載せてあります。
webpack
モジュールバンドラーですね。バージョンはwebpack v5を選択しました。ESBuildも挑戦してみたいと思いましたが今回はwebpackを選択しました。
Babel
トランスパイラですね。
今回開発はTypeScriptなのでts-loaderの選択もありますがbabel-loaderを選択したかっためこちらも導入していきます。
ESLint
静的コード解析ツールですね。
今回このESlintとPrettireはエディタ(自分はVSCodeで開発しています)に任せるだけで良いかな?と考えたのですが最終的にESLintは導入、PrettireはVSCodeに任せるということにしました。
JEST
テストはcreate-react-app
でも採用されているJESTを選択しました。合わせて@testing-library/react
も導入します。
Install & Initialize
ここからは上記のJavaScriptパッケージ、各種ツールのインストール、初期化を行っていきます
Initialize
yarn
で初期化を行いpackage.json
を作成します。
yarn init -y
webpack Install
webpackで必要なパッケージのインストールを行います。
webpack-merge
は今回本番、開発、共通でconfigファイルを書き分けるためマージするために利用します。
html-webpack-plugin
でバンドルしたファイルを読み込むHTMLを出力させます。
terser-webpack-plugin
で本番のみconsole.log
を除去します。
clean-webpack-plugin
でbuild開始時にpublic
配下をクリアします。
webpack-dev-server
で開発サーバの起動(アプリ編集セーブ時にホットリロード)を行います。
yarn add --dev webpack webpack-cli webpack-merge clean-webpack-plugin html-webpack-plugin terser-webpack-plugin webpack-dev-server
またscripts
の実行で利用するnpm-run-all
もインストールします。
yarn add --dev npm-run-all
TypeScript Install & Initialize
TypeScriptで必要なパッケージのインストール、初期化(tsconfig.jsの作成)を行います。ts-node
はJESTで必要なためインストールします。
yarn add --dev typescript ts-node
npx tsc --init
Babel Install
ts-loader
とbabel-loader
で悩んだのですが babel-loader
を採用しました。
今回はReact、TypeScriptを使用するため@babel/preset-env
だけでなく@babel/preset-typescript
,@babel/preset-react
を追加でインストールします。
yarn add --dev babel-loader @babel/core @babel/preset-env @babel/preset-typescript @babel/preset-react
ESLint Install
ESLintで必要なパッケージのインストールを行います。
Babelと同様に今回はReact、TypeScriptを使用するため@typescript-eslint/eslint-plugin
,@typescript-eslint/parser
,eslint-plugin-react
を追加でインストールします。
yarn add --dev eslint eslint-webpack-plugin @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react
JEST Install & Initialize
JESTで必要なパッケージのインストール、初期化を行います。
npx jest --init
では
質問 | 回答 |
---|---|
? Would you like to use Jest when running "test" script in "package.json"? | n |
? Would you like to use Typescript for the configuration file? | y |
? Choose the test environment that will be used for testing | jsdom |
? Do you want Jest to add coverage reports? | n |
Which provider should be used to instrument code for coverage? | babel |
? Automatically clear mock calls, instances and results before every test? | n |
で進めます。Typoしても後ほどjest.config.js
を書き換えるので大丈夫です。
Which provider should be used to instrument code for coverage?
については
JEST公式:coverageProviderを参考にしました。
yarn add --dev jest jsdom eslint-plugin-jest @types/jest @types/jsdom ts-jest
npx jest --init
次に@testing-library/react
,@testing-library/jest-dom
をインストールします。
yarn add --dev @testing-library/react @testing-library/jest-dom
React + Redux Install
ここからはアプリケーション周りのインストールを行います。
まずはReactとRedux Toolkitで必要なパッケージのインストールを行います。
yarn add react react-dom @types/react @types/react-dom react-redux @types/react-redux @reduxjs/toolkit @types/node redux @types/redux
Redux Toolkitが不要な場合はyarn remove
でreact-redux @types/react-redux @reduxjs/toolkit @types/node redux @types/redux
を指定することで削除可能です。
Tailwind Install & Initialize
Tailwindで必要なパッケージ、webpackでCSSをバンドルする際に必要なパッケージをインストール、初期化を行いtailwind.config.js
,postcss.config.js
を作成します。
yarn add tailwindcss@latest @types/tailwindcss
yarn add --dev postcss-loader postcss autoprefixer css-loader mini-css-extract-plugin
npx tailwindcss init -p
SetUp
以降の設定ファイルはPathの記載がないものは全てプロジェクトディレクトリ直下に作成(もしくは初期化で作られたファイルを修正)します
Package.json
yarn
の初期化で作成されたpackage.json
に"devDependencies":
より上を追加&上書きで記述します。もしJESTの初期化でscripts
が追加された場合はそちらは削除します。name
は適時命名してください。
今回はstart
,test
,build
を使用します。build
での型チェックはtsc --noEmit
に任せています。
{
"name": "typescript-webpack-react-template",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"test": "jest",
"webpack:build": "webpack --config webpack.prod.js",
"build": "run-s no-emit webpack:build",
"dev": "webpack --config webpack.dev.js",
"tsc": "tsc",
"no-emit": "tsc --noEmit"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@types/jest": "^27.0.3",
"@types/jsdom": "^16.2.13",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"autoprefixer": "^10.4.0",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.5.1",
"eslint": "^8.3.0",
"eslint-plugin-jest": "^25.3.0",
"eslint-plugin-react": "^7.27.1",
"eslint-webpack-plugin": "^3.1.1",
"html-webpack-plugin": "^5.5.0",
"jest": "^27.3.1",
"jsdom": "^18.1.1",
"mini-css-extract-plugin": "^2.4.5",
"npm-run-all": "^4.1.5",
"postcss": "^8.3.11",
"postcss-loader": "^6.2.0",
"terser-webpack-plugin": "^5.2.5",
"ts-jest": "^27.0.7",
"ts-node": "^10.4.0",
"typescript": "^4.5.2",
"webpack": "^5.64.2",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.5.0",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@reduxjs/toolkit": "^1.6.2",
"@types/node": "^16.11.10",
"@types/react": "^17.0.36",
"@types/react-dom": "^17.0.11",
"@types/react-redux": "^7.1.20",
"@types/redux": "^3.6.0",
"@types/tailwindcss": "^2.2.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"redux": "^4.1.2",
"tailwindcss": "^3.0.1"
}
}
git diff
の結果(抜粋)
- "main": "index.js",
+ "scripts": {
+ "start": "webpack-dev-server --config webpack.dev.js",
+ "test": "jest",
+ "webpack:build": "webpack --config webpack.prod.js",
+ "build": "run-s no-emit webpack:build",
+ "dev": "webpack --config webpack.dev.js",
+ "tsc": "tsc",
+ "no-emit": "tsc --noEmit"
+ },
webpack
webpackの設定は本番prod
,開発dev
,共通common
の3つに分けて作成します。
webpack.common.js
共通の設定はこちらに記載します。プロジェクト直下にwebpack.common.js
を作成します。
entry: { app: './src/ts/app.tsx' },
がエントリーポイントになります。作ってから思ったのですがcreate-react-app
をリスペクトしているのでエントリーポイントはentry: { app: './src/ts/index.tsx' },
にしApp.tsx
をラップするべきだったかなと若干後悔したりしています。(今回はこのまま行きますがお好みで変更してください )
optimization: {
のくだりで今回はnode_module
で呼び出しているパッケージ、自身で作成したアプリを別々でバンドルするよう設定しています。GitHub@hironomiu typescript-webpack-react-templateではコメントで自身で作成したアプリを更に分けたい場合用の設定を記述してありますので興味ある方はそちらを見てみてください
バンドルするJS,CSSはcontenthash
を設定しコード更新時に対応します。
performance:
はデフォルトより少し緩めに設定をしています。
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: { app: './src/ts/app.tsx' },
output: {
filename: 'js/[name].[contenthash].js',
path: path.resolve(__dirname, 'public'),
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
minChunks: 1,
},
defaultVendors: {
filename: 'js/[name].[contenthash].js',
},
},
},
},
performance: {
maxEntrypointSize: 500000,
maxAssetSize: 500000,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
],
},
],
},
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx'],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/html/index.html',
chunks: ['app'],
}),
new ESLintPlugin({
extensions: ['.ts', '.js', '.tsx'],
exclude: 'node_modules',
}),
new MiniCssExtractPlugin({
filename: './css/[name].[contenthash].css',
}),
],
}
webpack.dev.js
開発の設定を記載します。プロジェクト直下にwebpack.dev.js
を作成します。今回はソースマップの指定、devServerの起動設定を記載します。
historyApiFallback: true,
はReactでルーティング(react-router-dom)を使用した際に直接ルーティングパスを指定した際にフォールバックすることで404を防ぐよう設定します。
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
const path = require('path')
module.exports = merge(commonConfig, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
open: true,
port: 'auto',
static: {
directory: path.join(__dirname, 'public'),
serveIndex: true,
},
historyApiFallback: true,
},
})
webpack.prod.js
本番の設定を記載します。プロジェクト直下にwebpack.prod.js
を作成します。今回はconsole.log
の除去の設定を記載します。
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = merge(commonConfig, {
mode: 'production',
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
},
})
babel.config.js
babelの設定を記載します。プロジェクト直下に babel.config.js
を作成します。
'@babel/preset-react'
のruntime: 'automatic'
についてはIntroducing the New JSX Transformを参考にしました。
module.exports = {
presets: [
[
'@babel/preset-env',
{ targets: { node: 'current' }, useBuiltIns: 'usage', corejs: 3 },
],
[
'@babel/preset-react',
{ targets: { node: 'current' }, runtime: 'automatic' },
],
'@babel/preset-typescript',
],
}
tsconfig.json
TypeScriptの設定を記載します。初期化で作成されたtsconfig.json
を以下に修正します。コメントが不要な方は適時削除してください。
tsc
で型チェックをする場合/* Type Checking */
以下は必要に応じて設定してください。
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react-jsx", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "esnext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": [
"jest",
"node",
"@testing-library/jest-dom"
], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*"
],
"exclude": [
"**/*.spec.ts",
"node_modules"
]
}
git diff
抜粋(空行をPrettireで除去含む)
-
-
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
+ "jsx": "react-jsx", /* Specify what JSX code is generated. */
-
- "module": "commonjs", /* Specify what module code is generated. */
+ "module": "esnext", /* Specify what module code is generated. */
- // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ "types": [
+ "jest",
+ "node",
+ "@testing-library/jest-dom"
+ ], /* Specify type package names to be included without being referenced in a source file. */
-
-
- // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ "sourceMap": true, /* Create source map files for emitted JavaScript files. */
-
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
-
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
- "strict": true, /* Enable all strict type-checking options. */
+ "strict": true, /* Enable all strict type-checking options. */
-
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
- }
-}
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ },
+ "include": [
+ "src/**/*"
+ ],
+ "exclude": [
+ "**/*.spec.ts",
+ "node_modules"
+ ]
+}
tsconfig.jest.json
プロジェクト直下にtsconfig.jest.json
を作成します。
名前の通りJestにtsconfig.json
をラップして渡しています。
{
"extends": "./tsconfig.json",
}
.eslintrc.js
ESLintの設定を記載します。プロジェクト直下に.eslintrc.js
を作成します。
JSX TransformのおかげでReactのImportが不要になりましたが、Importしない際に出るエラーはrules
でreact/react-in-jsx-scope: 'off'
で対応します。
extends
含め設定は調べると若干冗長な様ですが無いと無いでエラーで怒られるものもあるのでこの設定にしています。rules
に関してはお好みで追加してください。
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
'jest/globals': true,
},
plugins: ['jest', '@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
project: './tsconfig.json',
},
settings: {
react: {
version: 'detect',
},
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:jest/recommended',
],
rules: {
'prefer-const': 'error',
'react/react-in-jsx-scope': 'off',
'jest/no-disabled-tests': 'warn',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'warn',
'jest/valid-expect': 'error',
},
}
tailwind.config.js
初期化で作成したtailwind.config.js
にビルドする際にpurgeをする設定を追記します。ビルドの章で後述しますが下記の設定はv2用のためv3をインストールした場合は下記の設定でもワーニングが出るだけですがワーニングを消す場合はビルドの章にある設定に直してください。
module.exports = {
purge: {
enabled: true,
content: ['./src/**/*.tsx', './public/index.html'],
},
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
git diff
抜粋
- content: [],
+ purge: {
+ enabled: true,
+ content: ['./src/**/*.tsx', './public/index.html'],
+ },
+ darkMode: false, // or 'media' or 'class'
+ variants: {
+ extend: {},
+ },
postcss.config.js
初期化で作成したpostcss.config.js
をそのまま使用します。(加筆修正は行いません)
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
jest.config.js
初期化で作成したjest.config.js
を以下に書き換えます。コメントが不要な方は適時削除してください。
注意: JESTの初期化でjest.config.js
はECMAScriptでモジュールを宣言export default {
していますがCommonJSmodule.exports = {
に変更します。ECMAScriptでも動作するのですが手元の環境で動作しないケースが発生したためCommonJSに変更しました。
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/0f/v4tccdh150s7fpnmb_dthk8w0000gq/T/jest_dz",
// Automatically clear mock calls, instances and results before every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
globals: {
'ts-jest': {
tsconfig: 'tsconfig.jest.json',
},
},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "jsdom",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
Reactアプリの作成
ここまででバンドル、dev serverの起動、テストなどの設定はできたので動作確認用に実際にReactアプリを作成してみましょう。今回はtypescript-webpack-react-templateにあるサンプルコード(カウンターアプリ)を作成します。
./src
をルートとし以下の構成でアプリを作成します
__tests__
テストを作成
html
index.htmlを作成
ts
Reactアプリを作成
.
├── __tests__
├── html
│ └── index.html
└── ts
ディレクトリ作成
上のwebpack
から参照するディレクトリ、テスト用のディレクトリを作成します。
mkdir -p src/__tests__ src/html src/ts
./src/html/index.html
./src/html
配下にindex.html
を作成します。
このindex.html
をwebpackでバンドル対象のJS、CSSを組み込んでpublic
に出力されます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Counter App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
./src/ts/app.tsx
./src/ts
配下にapp.tsx
を作成します。
app.tsx
がエントリーポイントとなります。
import React from 'react'
import ReactDOM from 'react-dom'
import { store } from './app/store'
import { Provider } from 'react-redux'
import Home from './components/Home'
import './app.css'
const app = document.getElementById('app')
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<Home />
</Provider>
</React.StrictMode>,
app
)
./src/ts/app.css
./src/ts
配下にapp.css
を作成します。
Tailwindの設定を記述します。
@tailwind base;
@tailwind components;
@tailwind utilities;
./src/ts/components/Home.tsx
./src/ts/components
ディレクトリを作成します。
mkdir -p ./src/ts/components
./src/ts/components
配下にHome.tsx
を作成します。
次に作成する./src/ts/feature
配下に作成したCounter
コンポーネントを呼び出します。
import { VFC } from 'react'
import Counter from '../feature/counter/Counter'
const Home: VFC = () => {
return (
<div className="flex flex-col items-center">
<Counter />
</div>
)
}
export default Home
./src/ts/feature/counter/Counter.tsx
カウンターアプリのコンポーネントを作成します
./src/ts/feature/counter
ディレクトリを作成します。
mkdir -p ./src/ts/feature/counter
./src/ts/feature/counter
配下にCounter.tsx
を作成します。
このディレクトリ構成はcreate-react-app
のredux
テンプレートcra-template-reduxを参考にしました。
import { VFC } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment, selectCount } from './counterSlice'
const Counter: VFC = () => {
const count = useSelector(selectCount)
const dispatch = useDispatch()
return (
<div className="flex flex-col text-xl mt-10">
<div className="text-5xl mx-5 px-5">
<h1>Counter App</h1>
</div>
<div className="flex flex-row justify-center my-10">
<button
className="text-5xl mx-5 px-5"
onClick={() => dispatch(decrement())}
>
-
</button>
<p className="text-5xl">{count}</p>
<button
className="text-5xl mx-5 px-5"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
</div>
)
}
export default Counter
./src/ts/feature/counter/counterSlice.tsx
カウンターアプリのSliceを作成します。
./src/ts/feature/counter
配下に counterSlice.tsx
を作成します。
import { createSlice } from '@reduxjs/toolkit'
import { RootState } from '../../app/store'
type state = {
value: number
status: string
}
const initialState: state = {
value: 0,
status: 'idle',
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
decrement: (state) => {
state.value -= 1
},
increment: (state) => {
state.value += 1
},
},
})
export const { decrement, increment } = counterSlice.actions
export const selectCount = (state: RootState) => state.counter.value
export default counterSlice.reducer
./src/ts/app/store.ts
カウンターアプリのSliceを登録します。
./src/ts/app
ディレクトリを作成します。
mkdir -p ./src/ts/app
./src/ts/app
配下にstore.ts
を作成します。
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../feature/counter/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
動作確認
devServer
それではdevServer
を起動しアプリの動作を確認してみましょう。停止する場合はCTRL + Cでできます。
yarn start
devServerから自動でブラウザタブを開きアプリを起動してくれれば成功です。gifではデモしていませんがアプリはWatchされているのでコードを修正しセーブしたタイミングで反映されます。
ビルド
次にビルドの動作を確認してみましょう。
yarn run build
結果
$ yarn run build
yarn run v1.22.17
$ run-s no-emit webpack:build
$ tsc --noEmit
$ webpack --config webpack.prod.js
assets by path js/*.js 156 KiB
asset js/vendor.8695c69d62d0e2463e7a.js 154 KiB [emitted] [immutable] [minimized] (name: vendor) (id hint: vendor)
asset js/app.87a175b380041ad64933.js 2.03 KiB [emitted] [immutable] [minimized] (name: app)
asset ./css/app.5afb3b34d518ae0becf0.css 12 KiB [emitted] [immutable] (name: app)
asset index.html 456 bytes [emitted]
Entrypoint app 168 KiB = js/vendor.8695c69d62d0e2463e7a.js 154 KiB ./css/app.5afb3b34d518ae0becf0.css 12 KiB js/app.87a175b380041ad64933.js 2.03 KiB
orphan modules 125 KiB (javascript) 937 bytes (runtime) [orphan] 45 modules
runtime modules 2.76 KiB 4 modules
cacheable modules 295 KiB (javascript) 12 KiB (css/mini-extract)
modules by path ./node_modules/ 293 KiB
modules by path ./node_modules/react/ 7.63 KiB 4 modules
modules by path ./node_modules/react-redux/ 48.2 KiB 3 modules
modules by path ./node_modules/prop-types/ 2.58 KiB 3 modules
modules by path ./node_modules/react-dom/ 119 KiB 2 modules
modules by path ./node_modules/scheduler/ 4.91 KiB 2 modules
modules by path ./node_modules/react-is/ 2.69 KiB 2 modules
3 modules
modules by path ./src/ts/ 2.46 KiB (javascript) 12 KiB (css/mini-extract)
./src/ts/app.tsx + 4 modules 2.46 KiB [built] [code generated]
css ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./src/ts/app.css 12 KiB [built] [code generated]
webpack 5.64.2 compiled successfully in 7283 ms
✨ Done in 8.76s.
余談ですがTailwind.config.js
でpurgeの設定が漏れるとasset ./css/app.451002707eb5607fb8e3.css 3.82 MiB
と巨大なサイズになるので注意が必要です(purgeが効いてると上のようにasset ./css/app.5afb3b34d518ae0becf0.css 12 KiB
で収まりました)
yarn run v1.22.17
$ run-s no-emit webpack:build
$ tsc --noEmit
$ webpack --config webpack.prod.js
assets by chunk 3.82 MiB (name: app)
asset ./css/app.451002707eb5607fb8e3.css 3.82 MiB [emitted] [immutable] (name: app)
asset js/app.87a175b380041ad64933.js 2.03 KiB [emitted] [immutable] [minimized] (name: app)
asset js/vendor.8695c69d62d0e2463e7a.js 154 KiB [emitted] [immutable] [minimized] (name: vendor) (id hint: vendor)
asset index.html 456 bytes [emitted]
Entrypoint app 3.97 MiB = js/vendor.8695c69d62d0e2463e7a.js 154 KiB ./css/app.451002707eb5607fb8e3.css 3.82 MiB js/app.87a175b380041ad64933.js 2.03 KiB
orphan modules 4.17 MiB (javascript) 937 bytes (runtime) [orphan] 45 modules
runtime modules 2.76 KiB 4 modules
cacheable modules 295 KiB (javascript) 3.82 MiB (css/mini-extract)
modules by path ./node_modules/ 293 KiB
modules by path ./node_modules/react/ 7.63 KiB 4 modules
modules by path ./node_modules/react-redux/ 48.2 KiB 3 modules
modules by path ./node_modules/prop-types/ 2.58 KiB 3 modules
modules by path ./node_modules/react-dom/ 119 KiB 2 modules
modules by path ./node_modules/scheduler/ 4.91 KiB 2 modules
modules by path ./node_modules/react-is/ 2.69 KiB 2 modules
3 modules
modules by path ./src/ts/ 2.46 KiB (javascript) 3.82 MiB (css/mini-extract)
./src/ts/app.tsx + 4 modules 2.46 KiB [built] [code generated]
css ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./src/ts/app.css 3.82 MiB [built] [code generated]
webpack 5.64.2 compiled successfully in 7963 ms
✨ Done in 9.43s.
tree
でビルドで作成されるpublic
配下を確認しましょう。無事バンドルが成功しました。
$ tree ./public
./public
├── css
│ └── app.5afb3b34d518ae0becf0.css
├── index.html
└── js
├── app.87a175b380041ad64933.js
└── vendor.8695c69d62d0e2463e7a.js
2 directories, 4 files
Tailwind v3
自分の環境では当初Tailwindはv2を使用していたのですが、このエントリーを実際に検証していた際にv3を使用するようになりました。v3ですと今回のtailwind.config.js
の設定ですと以下のワーニングが出力されると思います。
warn - The `purge`/`content` options have changed in Tailwind CSS v3.0.
warn - Update your configuration file to eliminate this warning.
warn - The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.
warn - Change `darkMode` to `media` or remove it entirely.
build自体は問題なくされますが気持ち悪いと思うのでtailwind.config.js
を以下に書き直しましょう。darkMode
については必要な場合は適時設定してください。
module.exports = {
content: ['./src/**/*.tsx', './public/index.html'],
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
build時にワーニングが消えます。CSSのパージも問題なくされています。
$ yarn run build
yarn run v1.22.17
$ run-s no-emit webpack:build
$ tsc --noEmit
$ webpack --config webpack.prod.js
assets by path js/*.js 157 KiB
asset js/vendor.70fe727d6a1415ad5e9f.js 154 KiB [emitted] [immutable] [minimized] (name: vendor) (id hint: vendor)
asset js/app.63e074b899132527681a.js 2.1 KiB [emitted] [immutable] [minimized] (name: app)
asset ./css/app.6616dda496afbb6533cf.css 7.44 KiB [emitted] [immutable] (name: app)
asset index.html 456 bytes [emitted]
Entrypoint app 164 KiB = js/vendor.70fe727d6a1415ad5e9f.js 154 KiB ./css/app.6616dda496afbb6533cf.css 7.44 KiB js/app.63e074b899132527681a.js 2.1 KiB
orphan modules 120 KiB (javascript) 937 bytes (runtime) [orphan] 45 modules
runtime modules 2.82 KiB 4 modules
cacheable modules 297 KiB (javascript) 7.43 KiB (css/mini-extract)
modules by path ./node_modules/ 294 KiB
modules by path ./node_modules/react/ 7.63 KiB 4 modules
modules by path ./node_modules/react-redux/ 48.2 KiB 3 modules
modules by path ./node_modules/prop-types/ 2.58 KiB 3 modules
modules by path ./node_modules/react-dom/ 119 KiB 2 modules
modules by path ./node_modules/scheduler/ 4.91 KiB 2 modules
modules by path ./node_modules/react-is/ 2.69 KiB 2 modules
3 modules
modules by path ./src/ts/ 2.46 KiB (javascript) 7.43 KiB (css/mini-extract)
./src/ts/app.tsx + 4 modules 2.46 KiB [built] [code generated]
css ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./src/ts/app.css 7.43 KiB [built] [code generated]
webpack 5.65.0 compiled successfully in 5498 ms
Tips
Webサーバなどにデプロイ前にpublic
配下の動作を確認したい時があると思います。その際にserve、http-serverなど利用すると思いますが
react-router-domを使ってルーティング先を直接呼びだす(リロードなど)状況で404になってしまいます。SPAではなくルーティングをする際は
servorで起動するとよろしくしてくれるのでオススメです。
テスト
JESTを導入しているので簡単なテストコードを実行して動作確認をしましょう。
./src/setupTests.ts
@testing-library/jest-dom
用にsetupTests.ts
を作成します。
import '@testing-library/jest-dom/extend-expect'
./src/tests/counterSlice.spec.ts
初期値と加算のテストです
./src/__tests__
配下にcounterSlice.spec.ts
を作成します。
import counterReducer, { increment } from '../ts/feature/counter/counterSlice'
describe('counterReducer', () => {
const initialState = {
value: 3,
status: 'idle',
}
it('initial state', () => {
expect(counterReducer(undefined, { type: 'unknown' })).toEqual({
value: 0,
status: 'idle',
})
})
it('increment', () => {
const actual = counterReducer(initialState, increment())
expect(actual.value).toEqual(4)
})
})
テスト実行
全てpassしているので動作としては成功です。
$ yarn test
yarn run v1.22.17
$ jest
PASS src/__tests__/counterSlice.spec.ts
counterReducer
✓ initial state (3 ms)
✓ increment (2 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.695 s, estimated 3 s
Ran all test suites.
✨ Done in 4.10s.
./src/tests/Counter.spec.tsx
レンダリングの確認のテストでCounter.spec.tsx
を作成します。
import { render, screen } from '@testing-library/react'
import Counter from '../ts/feature/counter/Counter'
import { store } from '../ts/app/store'
import { Provider } from 'react-redux'
describe('feature/counter/Counter', () => {
it('renders Counter App text', () => {
render(
<Provider store={store}>
<Counter />
</Provider>
)
const Element = screen.getByText(/Counter App/i)
expect(Element).toBeInTheDocument()
})
})
テスト実行
先ほどのテストと合わせて全てpassしているので動作としては成功です。
$ yarn test
yarn run v1.22.17
$ jest
PASS src/__tests__/counterSlice.spec.ts
PASS src/__tests__/Counter.spec.tsx
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.754 s
Ran all test suites.
✨ Done in 8.26s.
落穂拾い
今回のテーマから少し逸れますがアプリを作ったらデプロイ、CI/CDを回したいと思うと思います。またGitHub@hironomiu typescript-webpack-react-templateをgit clone
した際にGitHub Actionsの設定があるので、そこについても軽く触れたいと思います。
デプロイ(Vercel)
インターネット上に公開し動作を確認したいことはあると思います。その際に自前でサーバを用意するのは少し手間(HTTPSにするのもちょっと手間)だなと思うことがあると思います。そういうカジュアルに公開したい時に普段自分はVercelを利用することが多いのですが、このエントリーでビルドしたアプリをデプロイする際以下を設定しました。
以下設定内容
設定項目 | 設定値 |
---|---|
FRAMEWORK PRESET | Other |
BUILD COMMAND | yarn run build |
OUTPUT DIRECTORY | public |
INSTALL COMMAND | yarn install |
Vercelの使い方には触れませんがとても簡単ですのでカジュアルに無料プランでシュッとアプリをデプロイするのには割とオススメです。(割とお世話になってます)
CI/CD(GitHub Actions)
CI/CDとありますがデプロイはしていません。(デプロイはActionsとは別に独立してVercelに任せています。)
冒頭で「たまに自前でReact環境を構築した方が何かと良いかなと思うことがあり」と書きましたが、仕事柄、普段学生さん向けに勉強会を開催するのですがReactをテーマにした時、Dockerコンテナをnode:latest
で立ち上げた環境でcreate-react-app
で開発をするとnodeのバージョンによって(主にv17)アプリの起動やビルドなどで怒られることがあり、そういう思いに至ったというのがあります。そこで今回作った環境もmainに取り込み時と定期的(1日1回にしています)にGitHub Actionsを使いnodeの各バージョンでビルドの検証を行なっています。Dockerのnode imageのlatestとGitHubで同期が取れているわけではないので現時点では気休め程度ですが一旦buildを通す(おまけでテストも回す)ところまでは行なっています。
git clone
した方で不要な場合は.github
ごと削除、デプロイなどする場合は適時編集してください。
main取り込み時(.github/workflows/build.yml)
name: biuld
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x, 17.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: SetUp
run: yarn install
- name: Test
run: yarn test
- name: Build
run: yarn build
定期、日時(.github/workflows/schedule-build)
name: schedule-build
on:
schedule:
- cron: '25 1 * * *'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x, 17.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: SetUp
run: yarn install
- name: Test
run: yarn test
- name: Build
run: yarn build
おわりに
【脱 create-react-app】React(+ Redux Toolkit) + Tailwind + TypeScript + webpack + Babel + ESLint + Jest の環境構築 でした。雰囲気で作っている節が多分にあるので細かいところでより良い設定があると思いますが今回は動作することができたので当初のゴールを達成できたと言うことにしたいと思います。またこのエントリーが少しでも参考になれば幸いです。
そして明日の「初めてのアドベントカレンダー Advent Calendar 2021」はy_a_m_aくんのエントリーです。お楽しみに!
合わせてこれまでの「初めてのアドベントカレンダー Advent Calendar 2021」のエントリーも是非読んでみてください!
それでは良いクリスマスを!!