TypeScript環境の構築手順とハマった点についてまとめる

TypeScript環境の構築手順について簡単にまとめる。
また構築時にハマった点について対応手順を残しておく。

## 環境
最終的な環境は以下の通り。
色々試しながらやったため不要なものが含まれている可能性あり。
```ts
  "dependencies": {
    "@chakra-ui/react": "^3.1.1",
    "@emotion/react": "^11.13.3",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-icons": "^5.3.0"
  },
  "devDependencies": {
    "@babel/preset-env": "^7.26.0",
    "@babel/preset-react": "^7.25.9",
    "@babel/preset-typescript": "^7.26.0",
    "@jest/globals": "^29.7.0",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.0.1",
    "@testing-library/user-event": "^14.5.2",
    "@types/jest": "^29.5.14",
    "@types/node": "^22.9.0",
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "babel": "^6.23.0",
    "babel-jest": "^29.7.0",
    "globals": "^15.9.0",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "jest-fixed-jsdom": "^0.0.8",
    "ts-jest": "^29.2.5",
    "ts-node": "^10.9.2",
    "typescript": "^5.6.3",
    "typescript-eslint": "^8.7.0",
    "vite": "^5.4.8",
    "vite-tsconfig-paths": "^5.1.1"
```

## テストに使用したコード
下記テストが正常終了することを目指した。
```tsx: App.tsx
import { Provider } from "@/components/ui/provider";
import { Button } from "@/components/ui/button";

function App() {
  return (
    <Provider>
      <div>Test</div>
      <Button colorPalette="teal">TestButton</Button>
    </Provider>
  );
}

export default App;
```

```tsx: App.test.tsx
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import App from "../App";

describe("てすと", () => {
  test("てすと", () => {
    render(<App />);
    expect(screen.getByText("Test")).toBeInTheDocument();
  });
});
```

## TypeScript環境の構築

### Jestの導入
```
npm install --save-dev jest
npm install --save-dev @types/jest @jest/globals 
npm init jest@latest
```

`package.json`に以下を追加
```json: package.json
{
  "scripts": {
    "test": "jest"
  }
}
```

### Testing-Libraryの導入
```
npm install --save-dev @testing-library/react @testing-library/dom @types/react @types/react-dom @testing-library/user-event
```

### Babel導入
```
npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/preset-typescript
```

`babel.config.jsonを以下の内容で作成。
```json: babel.config.json
{
  "presets": [
    ["@babel/preset-react", { "runtime": "automatic" }],
    ["@babel/preset-env", { "targets": { "node": "current" } }],
    "@babel/preset-typescript"
  ]
}
```

### Chakra UI v3の導入

公式ドキュメント通り下記を実行。
```
npm i @chakra-ui/react @emotion/react
npx @chakra-ui/cli snippet add
```

### パスエイリアスの設定
TypeScript、Vite、Jestそれぞれに設定が必要。

#### TypeScript
`import`時に`@/`を`src`フォルダ以下に読み替えるパスエイリアスの設定を`tsconfig.json`に追加する。
```ts:tsconfig.json
"paths": {
  "@/*": ["./src/*"]
}
```

#### Vite
`vite-tsconfig-paths`プラグインを導入することで、tsconfigのパス設定を同期できる。
```
npm i -D vite-tsconfig-paths
```

`vite.config.ts`に以下の記述を追加。
```ts: vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import tsconfigPaths from 'vite-tsconfig-paths' //追加

export default defineConfig({
  plugins: [react(), tsconfigPaths()], //tsconfigPaths()を追加
});
```

#### Jest
Jestに以下の設定を追加する。
```ts: jest.config.ts
module.exports = {
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1"
  }
}
```

## ハマった点

### Jest実行時に`Cannot find module`エラーが出る
Jest実行時にパスエイリアスを解決可能とするためには、上述の通り、`jest.config.ts`に設定を追加する必要がある。
>```ts: jest.config.ts
>module.exports = {
>  moduleNameMapper: {
>    "^@/(.*)$": "<rootDir>/src/$1"
>  }
>}
>```


### `Jest: Failed to parse the TypeScript config file`エラーが出る
typescriptとts-nodeを最新版にすることで解消した。
```
$ npm install --legacy-peer-deps --save-dev typescript@latest ts-node@latest
```

### `structuredClone is not defined`エラーが出る
パスエイリアスのエラー解消後、Jestのテスト実行時に発生した。
調べた所、node.jsのバージョンが古いと発生するようなので、node.jsを最新版にしたが解消しなかった。

`testEnvironment`の`jsdom`に起因するようだがよくわからなかった。
以下でとりあえず解決した。

`jest-fixed-jsdom`を導入
```
npm i jest-fixed-jsdom --save-dev
```

`testEnvironment`の値を変更する。
```js:jest.config.ts
module.exports = {
  testEnvironment: 'jest-fixed-jsdom',
}
```

### `TypeError: window.matchMedia is not a function`エラーが出る
`describe`の前に以下のコードを挿入することで解消した。
```tsx: App.test.tsx
window.matchMedia = window.matchMedia || function() {
    return {
        matches: false,
        addListener: function() {},
        removeListener: function() {}
    };
};
```

後に調べたら公式ドキュメントで言及されていたので、こちらの方が良さそう。

https://jestjs.io/ja/docs/manual-mocks#jsdom-%E3%81%AB%E5%AE%9F%E8%A3%85%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E3%83%A2%E3%83%83%E3%82%AF

## 参考
https://qiita.com/takagimeow/items/5b2be892247ec66e2b71

https://chaika.hatenablog.com/entry/2022/05/14/083000

https://chaika.hatenablog.com/entry/2023/07/27/083000

https://zenn.dev/lambta/articles/182d1eafc3e0a9