👏

NestJS + SWC + Vitest + TypeScript(Bundler) 構成

2024/07/31に公開

はじめに

  • GitHub 見ても、同構成をしている Repo が見つからなかったので投稿。
  • Claude 3.5 も全く解決できなかった。(というかだいぶ嘘つかれた)

発端

vitest に怒られちゃった...

The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.

CJS はやめとけよ、と。

前提

  • ESM 形式にするが、tsファイルの import の中に 拡張子を絶対に含めない。(美しさを著しく損ねるため)
  • ほんとダメ
import { Module } from '@nestjs/common';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';

構成

tsconfig.json

  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "target": "ESNext",
  • 神の Bundler 利用。

.swcrc

{
  "$schema": "https://json.schemastore.org/swcrc",
  "module": {
    "type": "es6",
    "resolveFully": true
  },
  "sourceMaps": true,
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": true
    },
    "baseUrl": "./"
  },
  "minify": false
}

ポイント

  • resolveFully: true を指定しないと絶対に動かない。ERR_MODULE_NOT_FOUND で 永久に苦しめられる。
> nest start --builder swc

>  SWC  Running...
Successfully compiled: 5 files with swc (56.88ms)
node:internal/modules/esm/resolve:265
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/root/.ghq_src/github.com/TakashiAihara/redd/dist/app.module' imported from /root/.ghq_src/github.com/TakashiAihara/redd/dist/main.js
    at finalizeResolution (node:internal/modules/esm/resolve:265:11)
    at moduleResolve (node:internal/modules/esm/resolve:933:10)
    at defaultResolve (node:internal/modules/esm/resolve:1157:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:227:38)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:87:39)
    at link (node:internal/modules/esm/module_job:86:36) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///root/.ghq_src/github.com/TakashiAihara/redd/dist/app.module'
}
  • resolveFully により、ビルド後に自動的に拡張子を含めてくれる。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

↓↓↓

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller.js";
import { AppService } from "./app.service.js";

nest-cli.json

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "tsConfigPath": "tsconfig.json",
    "builder": {
      "typeCheck": true,
      "type": "swc",
      "options": {
        "swcrcPath": ".swcrc"
      }
    }
  }
}

  • ありがちな感じで。
  • .swcrc を明示。

vitest.config.ts

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    root: './',
    watch: false,
  },
  plugins: [
    swc.vite({
      jsc: {
        parser: {
          syntax: 'typescript',
          tsx: true,
          dynamicImport: true,
          decorators: true,
        },
        transform: {
          legacyDecorator: true,
          decoratorMetadata: true,
          react: {
            runtime: 'automatic',
          },
        },
        target: 'esnext',
        loose: false,
      },
      module: { type: 'es6' },
    }),
  ],
});

おわりに

  • VSCode は tsconfig.json で解決
  • SWC は .swcrc で解決

みたいに、それぞれ見るものが違う感じになるので、成果物ベースで正しさを担保する必要があるのは辛いですね。

Discussion