⚛️

Create React Appを使わずにReactで新しくアプリを作る

2024/05/22に公開

自分がいつもReact.jsで新しくアプリを作るときに行う手順を記事にしました。
Create React Appは便利ですが、「個人開発としては使っても、サービス提供として使うのは一般的ではない」「設定が隠蔽されるため、後々調整が効かなくなる」といったデメリットがあります。
なので、私はCreate React Appを使用せずに、新しくアプリを作成します。

新しくリポジトリを作成する

$ mkdir foo
$ git init
$ cd foo

npm init

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (foo)
version: (1.0.0)
description:
entry point: (index.js)
test command: jest
git repository:
keywords:
author: skytomo
license: (ISC)
About to write to D:\skytomo\Documents\Programming\sitelen-nena\package.json:

{
  "name": "foo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "skytomo",
  "license": "MIT"
}


Is this OK? (yes)

TypeScriptとWebpackのインストール

npm install --save-dev typescript
npm install --save-dev webpack webpack-cli ts-loader
npm install --save-dev webpack-dev-server

Webpackの初期設定

$ npx webpack init
[webpack-cli] For using this command you need to install: '@webpack-cli/generators' package.
[webpack-cli] Would you like to install '@webpack-cli/generators' package? (That will run 'npm install -D @webpack-cli/generators') (Y/n) Y
…(省略)…
? Which of the following JS solutions do you want to use? Typescript
? Do you want to use webpack-dev-server? Yes
? Do you want to simplify the creation of HTML files for your bundle? Yes
? Do you want to add PWA support? Yes
? Which of the following CSS solutions do you want to use? SASS
? Will you be using CSS styles along with SASS in your project? Yes
? Will you be using PostCSS in your project? No
? Do you want to extract CSS for every file? No
? Do you like to install prettier to format generated configuration? Yes
? Pick a package manager: npm
[webpack-cli] ℹ INFO  Initialising project...
 conflict package.json
? Overwrite package.json? overwrite
    force package.json
   create src\index.ts
   create README.md
   create index.html
   create webpack.config.js
   create tsconfig.json
…(省略)…
[webpack-cli] ⚠ Generated configuration may not be properly formatted as prettier is not installed.
[webpack-cli] Project has been initialised with webpack!

ESLintのインストールと初期設定

Airbnbを使用したいので、ESLint の初期設定時、Airbnb のスタイルガイドが選択肢に表示されない – 手動インストールするという記事を参考に初期設定を済ませます。
2年前とは違う手順なので注意です。

$ npm install --save-dev eslint
$ npx eslint --init
You can also run this command directly using 'npm init @eslint/config@latest'.
Need to install the following packages:
@eslint/create-config@1.1.1
Ok to proceed? (y)
√ How would you like to use ESLint? · syntax
√ What type of modules does your project use? · esm
√ Which framework does your project use? · react
√ The React plugin doesn't officially support ESLint v9 yet. What would you like to do? · 8.x
√ Does your project use TypeScript? · typescript
√ Where does your code run? · browser
The config that you've selected requires the following dependencies:

eslint@8.x, globals, typescript-eslint, eslint-plugin-react
√ Would you like to install them now? · No / Yes
√ Which package manager do you want to use? · npm
☕️Installing...
…(省略)…
Successfully created foo/eslint.config.mjs file.

Airbnbスタイルガイドを手動でインストール

$ npm install --save-dev eslint-config-airbnb eslint-config-airbnb-typescript

.eslintrc.jsの設定

以下ように設定します。
.eslintrc.jsが存在せず、eslint.config.mjsが存在する場合は、もう一度npx eslint --initで初期設定すると作成されます(理由は不明)。
eslint.config.mjsはこっそり削除しておきましょう。

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: ["plugin:react/recommended", "airbnb", "airbnb-typescript"], // ここを編集する
  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["@typescript-eslint", "react"],
  rules: {},
};

Prettierのインストールと初期設定

Prettierは整形ツールですが、そのままだとESLintと競合してしまうのでそれを防ぐためのプラグイン(eslint-config-prettier)をともに入れます。

npm install --save-dev prettier eslint-config-prettier
node --eval "fs.writeFileSync('.prettierrc','{}\n')"

.prettierignoreの設定

.prettierignoreを作成し、整形を避けたいファイルを書きます。

.prettierignore
dist
coverage

eslint-config-prettierの設定

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: ["plugin:react/recommended", "airbnb", "airbnb-typescript", "prettier"], // ここを編集する
  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["@typescript-eslint", "react"],
  rules: {},
};

Reactのインストールと初期設定

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

以下のファイルを編集・追加します。

.eslintrc.js
 module.exports = {
   env: {
     browser: true,
     es2021: true,
   },
   extends: ["plugin:react/recommended", "airbnb", "airbnb-typescript"],
   overrides: [
     {
       env: {
         node: true,
       },
       files: [".eslintrc.{js,cjs}"],
       parserOptions: {
         sourceType: "script",
       },
     },
   ],
   parser: "@typescript-eslint/parser",
   parserOptions: {
     ecmaVersion: "latest",
     sourceType: "module",
   },
   plugins: ["@typescript-eslint", "react"],
-  rules: {},
+  rules: {
+   "import/extesnions": "off",
+   "no-return-assign": ["off"],
+  },
+  settings: {
+   "import/resolver": {
+     node: {
+       extensions: [".js", ".jsx", ".ts", ".tsx"],
+     },
+   },
+ },
 };
webpack.config.js
…(省略)…
 const config = {
-    entry: './src/index.ts',
+    entry: './src/index.tsx',
     output: {
         path: path.resolve(__dirname, 'dist'),
     },
     devServer: {
         open: true,
         host: 'localhost',
     },
     plugins: [
         new HtmlWebpackPlugin({
             template: 'index.html',
         }),
…(省略)…
tsconfig.json
 {
   "compilerOptions": {
     "allowSyntheticDefaultImports": true,
     "noImplicitAny": true,
     "module": "es6",
     "target": "es5",
-    "allowJs": true
+    "allowJs": true,
+    "jsx": "react",
+    "moduleResolution": "bundler"
   },
-  "files": ["src/index.ts"]
+  "files": ["src/index.tsx"]
 }
index.html
 <!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8" />
         <title>Webpack App</title>
     </head>
     <body>
-        <h1>Hello world!</h1>
-        <h2>Tip: Check your console</h2>
+        <div id="root"></div>
+      	<script src="bundle.js"></script>
     </body>
     
     <script>
         if ("serviceWorker" in navigator) {
             window.addEventListener("load", () => {
                 navigator.serviceWorker
                     .register("service-worker.js")
                     .then((registration) => {
                         console.log("Service Worker registered: ", registration);
                     })
                     .catch((registrationError) => {
                         console.error("Service Worker registration failed: ", registrationError);
                     });
             });
         }
     </script>
 </html>
src/index.tsx
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";

const App = () => <div>Hello, React!</div>;

const container = document.getElementById("root");
if (container === null)
  throw new Error("No root element found in the document");
const root = createRoot(container);
root.render(
  <StrictMode>
    <App />
  </StrictMode>,
);

npm run serveで動作を確認してみましょう。

Jestのインストールと初期設定

テストの設定も済ませておきましょう。
公式サイトを参考にします。

npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
npm install --save-dev ts-jest @types/jest
npm install --save-dev jest-environment-jsdom @testing-library/react

.eslintrc.jsを編集します。

.eslintrc.js
 module.exports = {
   env: {
     browser: true,
     es2021: true,
+    jest: true,
   },
   extends: ["plugin:react/recommended", "airbnb", "airbnb-typescript"],
   overrides: [
     {
       env: {
         node: true,
       },
       files: [".eslintrc.{js,cjs}"],
       parserOptions: {
         sourceType: "script",
       },
     },
   ],
   parser: "@typescript-eslint/parser",
   parserOptions: {
     ecmaVersion: "latest",
     sourceType: "module",
   },
   plugins: ["@typescript-eslint", "react"],
   rules: {
    "import/extesnions": "off",
    "no-return-assign": ["off"],
   },
   settings: {
    "import/resolver": {
      node: {
        extensions: [".js", ".jsx", ".ts", ".tsx"],
      },
    },
  },
 };

以下のファイルを作成し、以下のように設定します。

babel.config.js
module.exports = {
  presets: [
    "@babel/preset-env",
    ["@babel/preset-react", { runtime: "automatic" }],
  ],
};
jest.config.js
module.exports = {
  roots: ["<rootDir>/src"],
  transform: {
    "^.+\\.(ts|tsx)$": "ts-jest",
  },
};

以下のファイルを追加してnpm testが通るか試しましょう。

src/sample.ts
export default function sum(a: number, b: number) {
  return a + b;
}
test/sample.test.ts
import sum from "../src/index";

test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});

おわり

幸せなReactライフを!

Discussion