🦨

Next.jsとPythonの開発環境を爆速で構築する

2023/09/26に公開

はじめに

今回はフロントエンドにNext.js、バックエンドにPythonを採用して、Webサイトの環境構築を行いたいと思います。元々、Whisper APIとVOICEVOXによる合成音声を使用した会話アプリを開発したかったのですがですが、環境構築の段階でかなりのボリュームになった為、一旦記事として共有します。



モノレポについて

モノレポとはWebアプリの全てのコード(WebバックエンドやWebフロントエンド)を単一のリポジトリに保存するパターンを指します。 リポジトリを単一化することのメリットは、複数のチームで開発を行う際に、バックエンドとフロントエンドでコードを使い回すことができたり、コードの調査、変更の追跡が容易になる点です。

https://monorepo.tools



アプリケーションの概要

今回実装したコードは以下のリポジトリに格納しています。アプリの実装も入ってくるので少し見にくいかもしれませんが、ご容赦ください。

https://github.com/MASAKi-cell/Whisper-chatGTP-Api

フロントエンドのfrontend/ディレクトリではNext.jsのプロジェクトとそれに関連するeslint、prettier、stylelintrcの設定を行います。バックエンドのbackend/ディレクトリにおいてはPythonのコードとPython用のlintツール(flake8 や black)の設定を配置します。huskyを、ルートディレクトリに配置して、フロントエンドとバックエンドの両方のlintが実行されるように設定します。GitHub Actionsの設定も行い、CI/CDタスクが自動的に実行されるように設定します。



フロントエンド(Next.js)の環境構築

フロントエンド(Next.js)の環境構築を行います。


styleLint

スタイルシートを整形してくれるstyleLintの設定を行います。
styleLintはチーム全体で使うCSSエラーを回避し、コードの一貫性を高めるために役立つlinterです。

https://stylelint.io/

.stylelintrc.json
{
  "extends": ["stylelint-config-recess-order", "stylelint-config-standard"],
  "customSyntax": "postcss-scss",
  "overrides": [
    {
      "files": ["**/*.{ts,jsx,tsx}"],
      "customSyntax": "@stylelint/postcss-css-in-js"
    }
  ],
  "rules": {
    "string-no-newline": true,
    "property-no-unknown": true,
    "declaration-block-no-duplicate-properties": true,
    "block-no-empty": true,
    "no-duplicate-selectors": true,
    "declaration-empty-line-before": "never"
  }
}
  • string-no-newline: contentなどの文字列を途中で改行を禁止します。
  • property-no-unknown: 使用できないプロパティを禁止します。
  • declaration-block-no-duplicate-properties: プロパティが重複していたらエラーとなります。
  • block-no-empty: 空ブロックを禁止します。
  • no-duplicate-selectors: セレクタが重複していた場合エラーとなります。
  • declaration-empty-line-before: 宣言前の空行を禁止します。

stylelintのorderモジュール選定については以下の理由からstylelint-config-recess-orderを採用します。

  • 視覚的な関連性に基づいた順序を設定してくれる(CSSプロパティの書き順についてアルファベット順よりも視覚順の方が好みのため)
  • 定義しているpropertiesが多い

https://zenn.dev/web_tips/articles/f1167f4314dcb3

https://qiita.com/nabepon/items/4168eae542861cfd69f7



保存時の自動整形

保存時に自動で視覚順に整形してくれるようにVisual Studio Codeの拡張機能の「stylelint-plus」をインストールしておきます。

https://marketplace.visualstudio.com/items?itemName=hex-ci.stylelint-plus

Visual Studio CodeのSettingにおける、「Stylelint: Auto Fix On Save」の項目にチェックを入れます。


setting.jsoneditor.codeActionsOnSaveで保存時に実行されるコードアクションを指定することができます。source.fixAll.stylelintをtrueにして、 stylelintによる問題を保存時に自動修正するようにしておきます。

setting.json
  "editor.codeActionsOnSave": {
+  "source.fixAll.stylelint": true,
  }



ESLint

次に構文チェックツールである、ESLintの設定を行います。

https://eslint.org/

.eslintrc.json
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "plugins": ["unused-imports"],
  "extends": ["next/core-web-vitals", "airbnb-typescript", "prettier"],
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "rules": {
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/no-throw-literal": "off",
    "@typescript-eslint/explicit-module-boundary-types": "error",
    "no-unused-vars": "error",
    "unused-imports/no-unused-imports": "error",
    "unused-imports/no-unused-vars": [
      "warn",
      {
        "vars": "all",
        "varsIgnorePattern": "^_",
        "args": "after-used",
        "argsIgnorePattern": "^_"
      }
    ],
    ・
    ・
    ・
  }
}
  • @typescript-eslint/no-non-null-assertion": 非nullアサーション演算子を許可しない。
  • @typescript-eslint/no-throw-literal: Errorオブジェクトの代わりとしてリテラルを投げることを禁止。
  • @typescript-eslint/explicit-module-boundary-types": 戻り値に明示的に型をつけない関数の禁止。
  • no-unused-vars: 実際にコード内で使用されていない変数の禁止。


LintルールはAirbnbのairbnb-typescriptを適用します。本来であれば、eslint-config-airbnbを適用したかったのですが、eslint-config-nextが要求するeslint-plugin-react-hooksのバージョンと、eslint-config-airbnbが要求するeslint-plugin-react-hooksのバージョンが競合してエラーが検出される為、今回はairbnb-typescriptのみ適用します。

eslint-config-airbnbについては以下Zennの記事で詳しく記載されています。

https://zenn.dev/shuuuuuun/articles/127728961f89a0


eslint --fixを実施した際に、未使用のimportを自動で削除してくれるプラグインno-unused-importsをインストールしてeslintに適用してます。

https://www.npmjs.com/package/eslint-plugin-unused-imports


    "import/order": [
      "error",
      {
        "groups": ["builtin", "external", "internal", ["parent", "sibling"], "object", "type", "index"],
        "newlines-between": "never",
        "pathGroupsExcludedImportTypes": ["builtin"],
        "alphabetize": { "order": "asc", "caseInsensitive": true },
        "pathGroups": [
          /** importの順序を設定 **/
          {
            "pattern": "app/types/**",
            "group": "internal",
            "position": "before"
          },
          /** 最後尾にStyleを設定 **/
          {
            "pattern": "./**.module.css",
            "group": "index",
            "position": "before"
          }
        ]
      }
    ]

importの自動整列に関する設定も追加しておきます。

https://zenn.dev/yoshiko/articles/0994f518015c04#importの自動整列(import%2Forder)


stylelintと同様にeslintでもsetting.jsonに自動修正するように設定を追加します。

setting.json
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": true,
+   "source.fixAll.eslint": true,
  }



Prettier

コードフォーマッターで、ソースコードを整形してくれるPrettierの設定を行います。

https://prettier.io/

.eslintrc.json
{
  "plugins": ["strict-dependencies", "unused-imports", "unused-imports"],
  "extends": [
    "next/core-web-vitals",
    "plugin:import/recommended",
    "plugin:import/warnings",
+   "prettier"
  ],
  "rules": {
  .
  .
  .

ESLintとprettierを併用する為に、.eslintrc.jsonに設定を追加します。
その後、prettierrc.jsonを作成して任意のルールを追加します。


.prettierrc.json
{
  "tabWidth": 2,
  "printWidth": 110,
  "semi": false,
  "singleQuote": true,
  "jsxSingleQuote": true
}
  • tabWidth: インデントのスペースの数を2行で指定。
  • printWidth: 1行のコードは最大幅を110文字にまで設定。
  • semi: 文の終わりにセミコロン(;)をつけない。
  • singleQuote: 文字列を囲むときにシングルクォート(')を使用する。
  • jsxSingleQuote: JSXの属性を囲む際もシングルクォート(')を使用する。


settings.jsonに、保存時に自動でフォーマットしてくれるように以下の項目を設定しておきます。

setting.json
  "editor.formatOnSave": true, // ファイル保存時の自動フォーマット有効
  "editor.formatOnPaste": true, // ペーストした文字の自動フォーマット有効
  "editor.formatOnType": true, // 文字入力行の自動フォーマット有効
  "editor.defaultFormatter": "esbenp.prettier-vscode", // デフォルトフォーマッターをPrettierに指定

https://zuma-lab.com/posts/eslint-prettier-settings



バックエンド(Python)の環境構築

次にバックエンド(Python)の環境構築を行います。


仮想環境の設定

Python の仮想環境を構築するために、venvを使用します。仮想環境を構築せず、pipパッケージを利用する場合は、グローバルインストールされてしまい、パッケージのコンフリクトが発生してしまう可能性があります。そのため、venvを使用して、プロジェクト毎にPythonの環境を分けるように設定します。

python3 -m venv venv         # 仮想環境を作成
source venv/bin/activate # 仮想環境を有効化
deactivate                   # 仮想環境を終了



パッケージ管理

パッケージ管理については、公式が提供するpipを使用します。

https://book.st-hakky.com/docs/about-openai/

ただ使用し始めて分かったのですが、pipを使用する場合、pip install <パッケージ> でインストール後、pip freeze > requirements.txtを実行してrequirements.txt に書き込まない限り、一覧化されず、どのバージョンをインストールすれば良いか分かりにくくなってしまう問題が発生します。

https://zenn.dev/tanny/articles/cdb555d6124a2a


Pipenv・Poetry・Ryeのどれかを使えばこの問題は解決できるようですが、ひとまずrequirements.txtに自動更新されるようにシェルスクリプトファイルを作成します。

touch pip_install.sh
backend/pip_install.sh
#!/bin/bash

# パッケージインストール時にrequirements.txtを自動更新する
pip install "$@"
pip freeze > requirements.txt


作成したスクリプトを実行可能にします。

chmod +x pip_install.sh


これでパッケージをインストールするたびに、requirements.txt が自動的に更新されるようになります。

./pip_install.sh <package_name>



Flake8

Flake8は、Pythonコードの論理エラーやスタイルをチェックするリンターです。

https://flake8.pycqa.org/en/latest/

Flake8自体は以下ライブラリのラッパーであり、Flake8を入れることで簡単にそれぞれのライブラリを適用することができます。

ライブラリ 概要
PyFlakes コードのエラーチェック
pycodestyle PEP8に準拠しているかチェック
mccabe 循環的複雑度のチェック

PEP8はPythonのコード規約の一種で、空白に関する内容や行の長さ、インデントなどコーディング規約に則ってチェックを行ってくれます。

https://pep8-ja.readthedocs.io/ja/latest/#section-12


flake8の設定を.flake8に追加します。

.flake8
[flake8]
exclude = venv/*
max-line-length = 95
extend-ignore = E203
max-complexity = 10
  • 仮想環境venvはFlake8のチェックから外しておきます。
  • max-line-length:行の長さを設定します。PEP8の標準では最大行長が79文字なので、少し長めに拡張してます。
  • extend-ignore: E203のエラーコードを無視します。後述するBlackなどのコードフォーマッタと競合するために設定します。
  • max-complexity: コードの複雑さがどこまで許容できるかを指定します。



Black

Flake8はPEP8の規約違反をチェックしてくれますが、修正まで行ってくれません。これを自動的に修正するように行ってくれるツールがBlackです。

https://black.readthedocs.io/en/stable/index.html

Blackのインストール後、Black Formatterのインストールしてsetting.jsonに項目を追加します。
https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter

setting.json
  "[python]": {
    "editor.defaultFormatter": "ms-python.black-formatter",
    "editor.formatOnSave": true
  }

[python]を記載することで、Pythonファイルの場合、フォーマッタとして "ms-python.black-formatter"(Black)が使用され、それ以外のファイルでは、デフォルトとして "esbenp.prettier-vscode"(Prettier)が使用されます。



mypy

最後にmypyをインストールします。mypyは型ヒントに基づいた型のチェックを行うことが可能です。

https://mypy.readthedocs.io/en/stable/getting_started.html

mypyをインストール後コマンドを実行してエラーが出力されてた場合、正常に動作しています。

(base) ➜  backend git:(main) ✗ mypy app
app/api/config.py:2: error: Cannot find implementation or library stub for module named "dotenv"  [import]
app/api/config.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
app/api/whisper.py:3: error: Cannot find implementation or library stub for module named "config"  [import]
Found 2 errors in 2 files (checked 7 source files)

VScodeのMypyの拡張機能もインストールしておきましょう。
https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker



Github Actionsの設定

CI/CDタスクを自動的に実行させる為に、Github Actionsの設定を行います。

name: ci
on:
  push:
    branches:
      - "main"

jobs:
  ci:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [16]

    steps:
      - name: Checkout 🛎
        uses: actions/checkout@v3

      - name: Setup node env 🏗
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node }}
          check-latest: true

      - name: Install dependencies 💾
        run: npm ci

      - name: Cache node_modules 📦
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-build-

  • matrix はGitHub Actions のワークフローで実行されるjobsにおいて、複数のOSやNode.jsのバージョンでのテストやビルドを行いたい場合に使用される機能です。matrix.osmatrix.nodeに複数のバージョンをリストとして指定すれば、それぞれのバージョンでジョブが並行して実行されます。
  • actions/checkout@v3でリポジトリのコードをGitHub Actionsランナー上にチェックアウトして、actions/setup-node@v3でNode.jsのセットアップを行い、その後の依存関係のインストール(npm ciコマンドによる、node_modulesの再構築)が実行される準備を整えます。
  • path: ~/.npmでャッシュするディレクトリのパスを.npmに指定しています。.npmはnpmが利用するキャッシュディレクトリとして利用されており、外部から取得したパッケージのバージョン情報や実際のパッケージコンテンツがキャッシュとして保存されています。

https://zenn.dev/sasakiki/articles/5b9974ce2a72b3



Husky

最後のHuskyの設定を行います。
以下のコマンドを実行してHuskyをインストールします。

npx husky-init && npm install


.git と package.json が同一ディレクトリでない場合は以下のようなエラーが発生するため、package.jsonにscriptを追加して、ルートディレクトリから、必要ファイルは frontend/.huskyに追加されるように追記します。

husky - .git can't be found (see https://typicode.github.io/husky/#/?id=custom-directory)
package.json
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
+   "prepare": "cd .. && husky install frontend/.husky"
  },

https://qiita.com/les-r-pan/items/c03f12bc1693983daa70


Huskyを設定して、pre-commit や pre-push などのGitフックを使用して、commitやpushの前にESLintを実行することができるので、不適切なコードがリポジトリにpushされるのを防ぐことが可能です。

pre-commitでコードをコミットする前に、ESLintやPrettier、flake8などのツールを使ってコードを整形・検証することが可能となります。

pre-commits
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"


# frontend
cd frontend

echo "\n 🚧🏗️  frontend: Checking format, lint, stylelint in your project before committing"

# Check Prettier ESLint stylelint standards
npm run lint-fix ||
(
    echo "\n ❌🟨 Check Failed$";
    false;
)


# backend
cd ../
cd backend

echo "\n 🚧🏗️  backend: Checking format, lint, stylelint in your project before committing"

flake8 # リンター
black app # コードフォーマッター
mypy app # 型チェック

lint-staged/huskyの組み合わせでも良かったのですが、今回は導入しませんでした。



HuskyとGithub Actionsの両方を組み合わせるメリット

GitHub Actionsでもlintの設定が可能ですが、今回はHuskyで実施してます。
Huskyはローカル環境でのコードの品質を確保するためのツールであり、開発者が記述したコードをcommitやpushする前に、自動的にlintを行い、問題点を早期に発見することができます。一方、Github Actionsはリモートリポジトリでのコードの品質と互換性を確保するための役割を果たしていて、プルリクエスト作成時や特定のブランチがプッシュされた際に、自動的にテスト、Lint、ビルドなどのタスクを実行します。HuskyとGitHub Actionsは排他的ではなく、Huskyは開発者がpushした際に、問題点の早期解決を行うことができ、GitHub Actionsはチーム全体のコード品質と互換性を保証し、デプロイプロセスの自動化などのより広範囲なタスクを担当することになります。



さいごに

今回は、linterやFormatterの設定を中心に記事にしました。本来であれば、Next.jsとPythonといった異なる環境下においては、Dockerを導入した方が良いかと思いますので、アプリの作成もしつつその点も踏まえて次回紹介できればと思います。



参考文献

https://zenn.dev/katsumanarisawa/articles/b98ba340a218af

https://zenn.dev/tmknom/scraps/f05911dad51689

https://iwb.jp/css-stylelint-vscode-settings-rules/

https://zenn.dev/tanny/articles/cdb555d6124a2a

Arsaga Developers Blog

Discussion