Open14

Vite + React の環境構築

ピン留めされたアイテム
あしやひろあしやひろ

Next.jsには慣れてきたけど、素のReactで開発ってほとんどしたこと無いのと、Webpack触りたくないvueの本で知ったviteがReactとかでも使えるらしい&わりと評判良さそうなので触ってみつつ、GitHubにテンプレートリポジトリを用意するのを目標にしてみる。

以下参考資料(特に一番上の記事を大いに参考にさせてもらいました

あしやひろあしやひろ

とりあえず yarn create vite する

$ yarn create vite
yarn create v1.22.17
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Installed "create-vite@2.6.6" with binaries:
      - create-vite
      - cva
✔ Project name: … react-template
✔ Select a framework: › react
✔ Select a variant: › react-ts

Scaffolding project in /Users/kk6/Products/react-template...

Done. Now run:

  cd react-template
  yarn
  yarn dev

✨  Done in 218.24s.
あしやひろあしやひろ

Volta の pin コマンドで node と yarn のバージョンを固定

$ volta pin node
success: pinned node@16.13.0 (with npm@8.1.0) in package.json
$ volta pin yarn
success: pinned yarn@1.22.17 in package.json

すると、package.json に以下の内容が追記される。

  "volta": {
    "node": "16.13.0",
    "yarn": "1.22.17"
  }
あしやひろあしやひろ

eslint をセットアップする。npx に相当する yarn のコマンドは yarn2 から使えるようになるらしい。どうせまだ yarn install する前だし、とりあえず npx eslint --init でセットアップして、最後の選択肢 Would you like to install them now with npm? も Yes にしてインストールしてしまおう。

→ npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-airbnb@latest
Local ESLint installation not found.
The config that you've selected requires the following dependencies:

eslint-plugin-react@^7.21.5 @typescript-eslint/eslint-plugin@latest eslint-config-airbnb@latest eslint@^5.16.0 || ^6.8.0 || ^7.2.0 eslint-plugin-import@^2.22.1 eslint-plugin-jsx-a11y@^6.4.1 eslint-plugin-react-hooks@^4 || ^3 || ^2.3.0 || ^1.7.0 @typescript-eslint/parser@latest
✔ Would you like to install them now with npm? · No / Yes
Installing eslint-plugin-react@^7.21.5, @typescript-eslint/eslint-plugin@latest, eslint-config-airbnb@latest, eslint@^5.16.0 || ^6.8.0 || ^7.2.0, eslint-plugin-import@^2.22.1, eslint-plugin-jsx-a11y@^6.4.1, eslint-plugin-react-hooks@^4 || ^3 || ^2.3.0 || ^1.7.0, @typescript-eslint/parser@latest

added 307 packages, and audited 308 packages in 12s

69 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Successfully created .eslintrc.js file in /Users/kk6/ghq/github.com/kk6/react-template
ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy.

で、package-lock.json を削除してから yarn install して yarn.lock を生成する。

$ rm package-lock.json

$ yarn install
yarn install v1.22.17
warning package.json: No license field
info No lockfile found.
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning react-template@0.0.0: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 38.73s.
あしやひろあしやひろ

init時点の .eslintrc.js

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'plugin:react/recommended',
    'airbnb',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 13,
    sourceType: 'module',
  },
  plugins: [
    'react',
    '@typescript-eslint',
  ],
  rules: {
  },
};

package.jsonlint コマンドを追記

  "scripts": {
    ...
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
  },

現時点での実行結果(長いので重複していて削除してよさそうな行は省略)

$ yarn lint
yarn run v1.22.17
warning package.json: No license field
$ eslint . --ext .js,.jsx,.ts,.tsx

src/App.tsx
   1:33  error  Missing semicolon                                                      semi
   9:5   error  'React' must be in scope when using JSX                                react/react-in-jsx-scope
   9:5   error  JSX not allowed in files with extension '.tsx'                         react/jsx-filename-extension
  14:58  error  'count' is already declared in the upper scope on line 6 column 10     no-shadow
  15:23  error  `{count}` must be placed on a new line                                 react/jsx-one-expression-per-line
  19:16  error  `code` must be placed on a new line                                    react/jsx-one-expression-per-line
  19:36  error  ` and save to test HMR updates.        ` must be placed on a new line  react/jsx-one-expression-per-line

src/main.tsx
   1:8   error  'React' was used before it was defined          no-use-before-define
   1:26  error  Missing semicolon                               semi
   4:17  error  Unable to resolve path to module './App'        import/no-unresolved
   4:17  error  Missing file extension for "./App"              import/extensions
   7:3   error  JSX not allowed in files with extension '.tsx'  react/jsx-filename-extension
  10:34  error  Missing trailing comma                          comma-dangle

vite.config.ts
  1:1   error  'vite' should be listed in the project's dependencies, not devDependencies                  import/no-extraneous-dependencies
  1:36  error  Missing semicolon                                                                           semi
  2:1   error  '@vitejs/plugin-react' should be listed in the project's dependencies, not devDependencies  import/no-extraneous-dependencies
  6:21  error  Missing trailing comma                                                                      comma-dangle

✖ 38 problems (38 errors, 0 warnings)
  19 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
あしやひろあしやひろ

とりあえずこの時点で eslint の fix コマンドを追加して実行してみる

 "scripts": {
   ...
-   "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
+   "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
+   "lint:fix": "yarn lint --fix"
 },

結果(一部省略)

$ yarn lint:fix
...

src/App.tsx
   9:5   error  'React' must be in scope when using JSX                             react/react-in-jsx-scope
   9:5   error  JSX not allowed in files with extension '.tsx'                      react/jsx-filename-extension
  14:58  error  'count' is already declared in the upper scope on line 6 column 10  no-shadow

src/main.tsx
  1:8   error  'React' was used before it was defined          no-use-before-define
  4:17  error  Unable to resolve path to module './App'        import/no-unresolved
  4:17  error  Missing file extension for "./App"              import/extensions
  7:3   error  JSX not allowed in files with extension '.tsx'  react/jsx-filename-extension

vite.config.ts
  1:1  error  'vite' should be listed in the project's dependencies, not devDependencies                  import/no-extraneous-dependencies
  2:1  error  '@vitejs/plugin-react' should be listed in the project's dependencies, not devDependencies  import/no-extraneous-dependencies

✖ 19 problems (19 errors, 0 warnings)
あしやひろあしやひろ

error Missing file extension for "./App" import/extensions は以下を .eslintrc.js に追記すると解消される。(plugin:import/typescript のほうだけでもいいのかもだけど、セットで設定してる人が多かった)

  extends: [
    'plugin:react/recommended',
    'airbnb',
+    'plugin:import/recommended',
+    'plugin:import/typescript',
  ],
あしやひろあしやひろ

先にルールを追加したほうが良さそう。

まず、hooks関係のルールを追加してくれるairbnb/hooksを追加する。

eslintrc.js
  extends: [
    'plugin:react/recommended',
    'airbnb',
+   'airbnb/hooks',
    'plugin:import/recommended',
    'plugin:import/typescript',
  ]

追加前後で yarn eslint --print-config .eslintrc.js した結果同士でdiffをとったものが以下。

   },
   "plugins": [
     "jsx-a11y",
+    "react-hooks",
     "import",
     "@typescript-eslint",
     "react"

     ...

     "import/no-duplicates": [
       "warn"
     ],
+    "react-hooks/rules-of-hooks": [
+      "error"
+    ],
+    "react-hooks/exhaustive-deps": [
+      "error"
+    ],
     "jsx-a11y/anchor-has-content": [
       "error",
       {

ちなみに、ここからさらに plugin:react-hooks/recommended を追加してみたが、print-configの結果が変わらなかったのでこっちは必要無いらしい。

あしやひろあしやひろ

Typescript関連のルール追加。

.eslintrc.js
  extends: [
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'airbnb',
    'airbnb/hooks',
+   'plugin:@typescript-eslint/recommended',
+   'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'plugin:import/recommended',
    'plugin:import/typescript',
  ]

print-config の差分は長いので今回は省く。

ちなみに plugin:@typescript-eslint/recommended-requiring-type-checking を追加した時点で You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser. って怒られる。なので parserOptions.project を設定してやる。

.eslintrc.js
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 13,
    sourceType: 'module',
+   tsconfigRootDir: __dirname,
+   project: ['./tsconfig.json'],
  },

そして、この時点でeslintを実行するとvite.config.tsでlintエラーになりはじめる。

vite.config.ts
  0:0  error  Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: vite.config.ts.
The file must be included in at least one of the projects provided

これはとりあえずルールをどうにかするよりも .eslintignore で対処したほうが良さそう。

.eslintignore
build/
public/
**/node_modules/
*.config.js
*.config.ts
*.local
.*
あしやひろあしやひろ

ルールを追加。

.eslintrc.js
   rules: {
+    'import/extensions': [
+      'error',
+      {
+        js: 'never',
+        jsx: 'never',
+        ts: 'never',
+        tsx: 'never',
+      },
+    ],
+   'react/jsx-filename-extension': [
+     'error',
+     {
+       extensions: ['.jsx', '.tsx'],
+     }
+   ],
+   'react/react-in-jsx-scope': 'off',
+   'no-use-before-define': 'off',
+   '@typescript-eslint/no-use-before-define': ['error'],
+   'react/jsx-one-expression-per-line': 'off',
   },

import/extensions

これは js/jsx/ts/tsxなファイルをimportするときに拡張子が無くても良いようにするもの。これで Missing file extension "tsx" for "./App" import/extensions が解消される。

react/jsx-filename-extension

.tsxにJSXを書いても良いようにする。これで JSX not allowed in files with extension '.tsx' react/jsx-filename-extension が解消。

react/react-in-jsx-scope

React16まではJSXから変換後のjsで React を使用するので、JSXの時点では使わなくても import React from 'react' を書いておかないといけなかったらしい。しかしもはやimportの必要はないのでこのルールは無効化する。これで 'React' must be in scope when using JSX react/react-in-jsx-scope が解消。

no-use-before-define と @typescript-eslint/no-use-before-define

以下のように、Reactを使っていてもimport React from 'react';でエラーが出ている

error  'React' was used before it was defined  no-use-before-define
src/main.tsx
import React from 'react';
...

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root'),
);

こちらの記事 によると、no-use-before-defineをoffにし、@typescript-eslint/no-use-before-defineを設定してやるとのこと。

react/jsx-one-expression-per-line

例えば以下

src/main.tsx
count is: {count}

これをoffにしてなくて eslint --fix すると、このように修正される。

src/main.tsx
count is:
{' '}
{count}

流石にこれはoffでいいと思う。

ひとまずルールは以上。

あしやひろあしやひろ

最後に残ったlintエラー

src/App.tsx
  14:58  error  'count' is already declared in the upper scope on line 6 column 10  no-shadow

これは直してやればいいだけ。

main.tsx
-     <button type="button" onClick={() => setCount((count) => count + 1)}>
+     <button type="button" onClick={() => setCount(count + 1)}>

これでlintエラーは全て解消。

あしやひろあしやひろ

Prettierをインストール

$ yarn add -D prettier eslint-config-prettier

.eslintrc.js に以下追記。

.eslintrc.js
  extends: [
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "airbnb",
    "airbnb/hooks",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:import/recommended",
    "plugin:import/typescript",
+   "prettier",
  ],

.prettierrc.json を作成。拡張子はprettierの公式ドキュメントにならってjsonにしてみた。今はとりあえずこれだけでいいと思う(基本的にデフォルト値任せで良さそう)

.prettierrc.json
{
  "singleQuote": true
}

ignoreはとりあえず公式ドキュメントの通りで。

.prettierignore
# Ignore artifacts:
build
coverage

npm script 追加。

package.json
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "serve": "vite preview",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "lint:fix": "yarn lint --fix",
+   "lint:format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}'"
  },
あしやひろあしやひろ

いい加減 warning package.json: No license field 直さないとね

package.json
  "volta": {
    "node": "16.13.0",
    "yarn": "1.22.17"
  },
+ "license": "MIT"
}
あしやひろあしやひろ

yarn dev したときに以下のようなメッセージが出てる

[BABEL] Note: The code generator has deoptimised the styling of /Users/kk6/ghq/github.com/kk6/react-template/node_modules/.vite/react-dom.js?v=921532a8 as it exceeds the max of 500KB.
Sourcemap for "/Users/kk6/ghq/github.com/kk6/react-template/node_modules/.vite/react.js" points to missing source files
Sourcemap for "/Users/kk6/ghq/github.com/kk6/react-template/node_modules/.vite/react_jsx-dev-runtime.js" points to missing source files
Sourcemap for "/Users/kk6/ghq/github.com/kk6/react-template/node_modules/.vite/react-dom.js" points to missing source files

今の所、@vitejs/plugin-react のバージョンを1.0.5に固定して問題が修正されるのを待つしか無いらしい。

https://github.com/vitejs/vite/issues/5438