備忘録:TypeScriptにおける環境構築 Linter編

最近、Next.js * TypeScriptに@swc-node/jestを入れたり、適当にやっていたeslintの設定を40行くらいのちょっとしたものに直す機会があった(執筆中に見直して10行ぐらいへった)。

Linter周りは作るたびに同じことを調べている気がするので、多少まとめることにする。

Eslint

module.exports = {
    env: {
        browser: true,
        es2021: true,
        mocha: true,
        node: true,
        'jest/globals': true,
    },
    extends: [
        'eslint:recommended',
        'plugin:@typescript-eslint/recommended',
        'plugin:jest/recommended',
        'plugin:jest/style',
        'next/core-web-vitals',
        'prettier',
    ],
    plugins: ['testing-library'],

    parser: '@typescript-eslint/parser',
    parserOptions: {
        ecmaFeatures: {
            jsx: true,
        },
        ecmaVersion: 12,
        sourceType: 'module',
    },
};

env

envにmochaやjest/globalsを設定すると、jestでテストコードを書くときitdescribeが未定義だと怒られなくなる。nodeは覚えていない。コミットログをみると同タイミングだから@swc-node/jest関連か。

plugin と extends

pluginはルールセットを追加しextendsはルールセットのオンオフなどを行う、これが基本。

しかしextendsでpluginの役割を兼ねることができるため、実際には各configごとに設定方法が違うため、ライブラリごとに公式サイトを見ることになる。 ESLint の Plugin と Extend の違い | blog.ojisan.io

next lint

Next.jsはv11からlintがついてくる。この構成は既存のlintルールの共存が難しい。

自分は主にairbnbとの共存方法を調べていた。自分の結論から言うと、airbnbを捨て、eslint-config-nextに乗り換えることにした。理由は、airbnbの非合理なルールと、next lintによる高速化だった。

まず、airbnbはprefer default export や use before define など時代に合わないルールを採用している。これらを修正するのは、開発効率のためにならない。

次に、next lintの高速化により、2xから3xの高速化ができた。自分は型情報が必要なルールセットをオンにしているため、lintの処理が少し重い。実はeslint-config-nextはルールセット以外にパーサーなども用意していて、これを使うことでかなり高速化された。

パーサなどを変更せずnext.js用のルールセットだけ既存の構成に付け加えることも可能。このためには、extends: ['plugin:@next/next/recommended']を使うとnext.jsの設定だけを追加する。extends:'next'で入る設定は以下で確認できる。

github.com

.eslintignore

**/build/
**/public/
**/coverage/
**/node_modules/
**/*.min.js
# **/*.config.js
**/.*rc.json
**/dist/

重いと感じるときは、不要なファイルを見ていることを疑う。eslint は--verboseオプションがなく、DEBUG=eslint:* yarn eslintでログを出力する。

eslintrc自体のlinterがあればとよく思う。

prettierrc.json

{
    "semi": true,
    "singleQuote": true,
    "trailingComma": "es5",
    "tabWidth": 4
}

自動セミコロン挿入の弊害を知ってから、セミコロンは入れている。

.prettierignore

build
coverage
.next
.vscode
public
node_modules
yarn.lock

jest.config.js

今回はじめて書いた。特徴は@swc-node/jestを使うことで、かなり高速化されていること。最近chakra-uiのtest環境がts-jestからこれになり、テストが1/4の時間で終わるようになった。、個人的には、ts-jest時代はWSL環境で動かすとOSが落ちる状態だったので、かなり助かっている。

Next.js + Jest のテスト環境を @swc/jest を使って高速化する を参考にして書いた部分が多い。uttkさんの記事は@swc/jest、この記事は@swc-node/jestで異なるライブラリを採用しているため注意が必要。tsc-nodeはtscの非公式版なのだが、最近next.jsにも採用されている期待株。

module.exports = {
    testEnvironment: 'jsdom',

    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
    modulePathIgnorePatterns: [
        '<rootDir>/website/.cache',
        '<rootDir>/examples',
        '<rootDir>/tooling/cra-template*',
    ],

    transform: {
        '^.+\\.(ts|tsx)?$': [
            '@swc-node/jest',
            {
                sourceMaps: true, // エラーを見やすくする( 有効じゃないと内容がズレて表示されます )

                module: {
                    type: 'commonjs', // 出力するファイルをcommonjsとする
                },

                jsc: {
                    parser: {
                        syntax: 'typescript', // ソースコードをtypescriptとしてパースする
                        tsx: true, // jsx記法を許可する
                    },

                    transform: {
                        react: {
                            // 必須。省略すると "ReferenceError: React is not defined" が発生します
                            runtime: 'automatic',
                        },
                    },
                },
            },
        ],
    },

    testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
    transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
    setupFilesAfterEnv: [
        '@testing-library/jest-dom/extend-expect',
        '<rootDir>/jest.setup.ts',
    ],

    watchPlugins: [
        'jest-watch-typeahead/filename',
        'jest-watch-typeahead/testname',
    ],

    collectCoverageFrom: [
        'pages/**/*.{js,ts,tsx}',
        'components/**/*.{js,tsx,ts}',
        'theme/**/*.{js,tsx,ts}',
    ],
};

tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "react-jsx"
    },
    "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
    "exclude": ["node_modules"]
}

Next.jsのjsxはpreserveになっているが、vscodeのauto-importができなくなる問題がある。そのため、開発中はjsxをreact-jsxに変更している。つい10日前ほどにVsCodeに修正PRが出ていたのでいずれ改善されるだろう。

package.json > scripts

    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint",
        "lint-v": "DEBUG=eslint:* next lint",
        "test": "jest --coverage",
        "test:watch": "jest --watch",
    },

テストのカバレッジ・ウォッチと、eslintのverboseを登録した。

ふりかえって

Next.js用の環境をつくるのをいい機会にLinter周辺の知識を学んだ。Next.jsのlinterやテストまわりの体験はかなり優れていたので、今後は積極的にNext.jsを使うだろうと思う。Vercelロックインの批判も多いが、的を射た批判はあまり多くないので、気にせず使っていきたい。

今後husky / lint-stagedを追加するかもしれない。