🍨

Turborepo×Expo×Expressにeslint-config・typescript-configを設定

2024/11/18に公開

はじめに

私が今のプロジェクトで、Turborepo×Expo×Expressのリポジトリを0から作成しているので、その時に分からなかったeslint-config・typescript-configを設定について記していきたいと思います。

Turborepoは、モノレポを管理するためのツールです。リポジトリのルートでフロントもバックも立ち上げたりすることができます。
https://turbo.build/repo/docs

今回主に説明するファイルはこちらです。

root/
├── apps/
│   ├── api/ //Express格納
│   ├── mobile/ //Expo格納
├── packages/
│   ├──eslint-config/
│   ├──typescript-config/
├── turbo.json

それでは、まずはリポジトリを作成していきます。

リポジトリ作成

  1. Turborepoの雛形作成
  2. Expoの雛形作成
  3. Expressの雛形作成

Turborepoの雛形作成

$ npx create-turbo@latest

このような構成でリポジトリが完成します🎉

次はapps配下にExpoとExpressを入れていきます。

Expoの雛形作成

$ npx create-expo-app@latest mobile

このような構成でリポジトリが完成します🎉

Expressの雛形作成

雛形作成することもできますが、かなり雛形自体にたくさんのフォルダが入っており、消すのが大変なので、Expressは0から作ります。

$ mkdir api
$ cd api
$ npm install express

ここでできたapi配下にsrc/index.tsのフォルダを作成し、下記を記述してください

import express from 'express';

const app = express();
const port = 3000;

console.log('Hello from the server!');

app.get('/', (req, res) => {
  res.send('Hello from the server!');
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

$ npm install

こちらで完成です🎉

configを設定

eslint-configとtypescript-configを設定します。

  • eslint-configとは?
    モノレポ全体で共通のESLint設定を適用するために使用されます。
  • typescript-configとは?
    モノレポ内のすべてのTypeScriptプロジェクトで使う設定をしたものです。

どちらも、各プロジェクトが個別に設定を持つのではなく、共有設定をパッケージとして管理し、それを継承します。

eslint-configを設定

リポジトリの.eslintrc.jsにどのファイルを継承しているか設定します。
turborepoのpackages/eslint-configとは、モノレポ全体で共通のESLint設定を適用するために使用されるファイルです。
今回の私のプロジェクトでは、base.jsとexpo.jsを設定しました。

この二つのファイルは、下記の設定をするファイルです。

base.js: ベースとなるESlintを設定。api/.eslintrc.jsから継承されています。

expo.js: ExpoのみのESlintを設定。mobile/.eslintrc.jsから継承されています。

ExpressのESlint設定はbase.jsにしました。Express単体のESlintを設定したい時はexpress.jsというファイルを今後作りたいと思います。

下記の3ファイルを実装します。

.eslintrc.js

/** @type {import("eslint").Linter.Config} */
module.exports = {
  root: true,
  extends: ['@repo/eslint-config/base.js'],
};

base.js

module.exports = {
  extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
  env: {
    node: true,
    es6: true,
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: true,
  },
  overrides: [
    {
      files: ['**/__tests__/**/*', '**/.eslintrc.js'],
      env: {
        jest: true,
      },
      rules: {
        // ルールがあれば
      },
    },
  ],
  ignorePatterns: [
    // Ignore dotfiles
    '.*.js',
    'node_modules/',
  ],
  rules: {
    // 'no-console': 'warn', 
    '@typescript-eslint/no-unsafe-call': 'error', 
    '@typescript-eslint/no-unsafe-member-access': 'warn', 
    '@typescript-eslint/no-unsafe-return': 'error', 
    '@typescript-eslint/no-empty-function': 'warn',
    'object-shorthand': 'error', 
    'import/no-anonymous-default-export': 'off', 
    'require-jsdoc': 0,
    'valid-jsdoc': ['off'], 
    'no-restricted-imports': [
      'warn',
      {
        patterns: ['./*', '../*'], 
      },
    ],
    'import/order': [
      'warn',
      {
        groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
        'newlines-between': 'always',
        pathGroupsExcludedImportTypes: ['internal'],
        alphabetize: {
          order: 'asc',
          caseInsensitive: true,
        },
        pathGroups: [
          {
            pattern: '@/**',
            group: 'external',
            position: 'after',
          },
        ],
      },
    ],
  },
};

expo.js

const { resolve } = require('node:path');

const project = resolve(process.cwd(), 'tsconfig.json');

/** @type {import("eslint").Linter.Config} */
module.exports = {
  extends: ['expo', './base.js', 'turbo'],
  env: {
    node: true,
    browser: true,
    'react-native/react-native': true,
  },
  globals: {
    React: true,
    JSX: true,
  },
  plugins: ['only-warn', 'react-native'],
  settings: {
    'import/resolver': {
      typescript: {
        project,
      },
    },
  },
  overrides: [{ files: ['*.js?(x)', '*.ts?(x)', '**/.eslintrc.js'] }],
  rules: [
    {
      'no-console': 'warn', 
    },
  ],
};

typescript-configを設定

リポジトリのtsconfig.jsonにどのファイルを継承しているか設定します。
turborepoのpackages/typescript-configとは、モノレポ全体で共通のTypeScriptの設定を適用するために使用されるファイルです。
今回の私のプロジェクトでは、base.jsonとapi.jsonを設定しました。

この二つのファイルは、下記の設定をするファイルです。

base.json: ベースとなるTSconfigを設定

api.json: ExpressのみのTSconfigを設定

ExpressのTSconfig設定はbase.jsonにしました。Expo単体のTSconfigを設定したい時はexpo.jsonというファイルを今後作りたいと思います。

api/tsconfig.json

{
  "extends": "@repo/typescript-config/base.json",
  "compilerOptions": {
    "lib": ["ES2015"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "exclude": ["node_modules"],
  "include": ["src"]
}

mobile/tsconfig.json

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true,
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}

base.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "incremental": false,
    "isolatedModules": true,
    "lib": ["es2022", "DOM", "DOM.Iterable"],
    "module": "NodeNext",
    "moduleDetection": "force",
    "moduleResolution": "NodeNext",
    "noUncheckedIndexedAccess": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2022"
  }
}

api.json

{
  "extends": "./base.json",
  "compilerOptions": {
    "module": "NodeNext",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "../../apps/api",
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": "../../apps/api",
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["../../apps/api/src/**/*", "../../apps/mobile/tsconfig.json"],
  "exclude": ["node_modules", "../../**/node_modules"],
  "compileOnSave": false
}

さいごに

中身はご自身のPJに合わせてご変更ください!私のPJでは上記内容を実装しました!

Discussion