Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScript の class を使い倒す

TypeScript の class を使い倒す

Takuya Eguchi (Akagire)

July 22, 2024
Tweet

More Decks by Takuya Eguchi (Akagire)

Other Decks in Technology

Transcript

  1. 😢 こんなことありませんか? 1. API の response 型を バックエンドとフロントエンド 両方で定義するのが辛い 2.

    フロントエンドでもバックエンドでも 値のバリデーション処理を書くのが辛い
  2. パッケージ module moduleResolution ESNext Bundler CommonJS NodeNext (target: ES2021から) @sick-project/dto

    ? ? このアイデアの問題点 型の提供だけならそれでよいが、 NestJS 側で swagger や class-validator 等を使うとき DTO は class として定義が必要 → tsconfig が異なるためどうビルドすればいいのか...
  3. package.json と tsconfig.json を環境ごとに用意... @sick-project/dto src es dist package.json tsconfig.json

    tsconfig.es.json { "exports": { ".": { "browser": "./es/index.js", "default": "./dist/index.js" } }, "types": "./dist/index.d.ts", "scripts": { "build:backend": "tsc -p tsconfig.json", "build:frontend": "tsc -p tsconfig.es.json" } } { "compilerOptions": { “module”: “CommonJS”, “target”: “ES2021” } } { "compilerOptions": { “module”: “ESNext”, “moduleResolution”: “NodeNext” } } ※tsconfigはだいぶ端折ってます
  4. 🎉 import できた! @sick-project/next/fetchFoo.ts import type { GetFoo } from

    ‘@sick-project/dto’; // 略 const res = await fetch(‘/foo’); const body: GetFoo = await res.body(); @sick-project/nest/foo-controller.ts import type { GetFoo } from ‘@sick-project/dto’; // 略 res.status(200).json<GetFoo>({ message: ‘foo’ });
  5. es 向けビルド結果に decorator が混入 var __decorate = // 定義は略 import

    { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty } from 'class-validator'; var GetFoo = /** @class */ (function () { function GetFoo () { } __decorate([ ApiProperty({ // description 等... }) IsNotEmpty() ], GetFoo.prototype, “type”, void 0); return GetFoo; }()); export { GetFoo }; この影響でフロントエンド環境に @nestjs/swagger, class-validator を入れないとビルドでエラー(ノ∀` ) →使いもしないのにインスコが必要で バンドルサイズ爆増
  6. ビルド結果 import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty

    } from 'class-validator'; var GetFoo = /** @class */ (function () { function GetFoo () { } return GetFoo; }()); export { GetFoo }; 😢 decorator は消えたが、 import 文は消えてない dto の package.json へ “sideEffect”: false を指定で tree shaking されるが、 フロント側に @nestjs/swagger を install してないとビルドが通らない状況は変わらない class-validator をフロントエンドのリクエスト時のバリデーションで利用できなくなる
  7. TypeScript のトランスパイル処理をイジることができる サンプルとして、 • ES6 から d.ts ファイルの生成 • AST

    を舐めて自作 linter を作る • moduleResolution のカスタマイズ などの魔改造コードが掲載されている
  8. カスタムビルドの結果 Swagger に関する decorator だけ消すことができた →これでフロントエンドに @nestjs/swagger が不要になった! var __decorate

    = // 定義は略 import { IsNotEmpty } from 'class-validator'; // 残ってる! var GetFoo = /** @class */ (function () { function GetFoo () { } __decorate([ IsNotEmpty() // 残ってる! ], GetFoo.prototype, “type”, void 0); return GetFoo; }()); export { GetFoo };
  9. 🤔 ・Compiler API は disclaimer にある通り なので、production で利用するのは心配 ・やりすぎてる感、やや難易度が高い印象 🎉

    package.json × TypeScript × Compiler API を 組み合わせることで、互換性のない パッケージ間でも class をいい感じに共有! まとめ Keep in mind that this is not yet a stable API 💡 ・型共有の選択肢として十分ありだと思った ・フロントエンドのバンドルサイズ削減に 使えるかもしれない