📦

Awilixで依存関係の解決を型安全に自動化する方法【TypeScript】

に公開

結論

  • AwilixのloadModulesを使ってserviceの登録を自動化する
  • export defaultしているファイルを収集して型定義ファイルを生成するスクリプトを実行する
    • この記事ではファクトリ関数のみに対応したスクリプトを紹介します
  • 生成された型をawilix.createContainer()の型引数に渡す
// awilix-types.d.ts
import type foo from './src/foo.ts'
import type bar from './src/bar.ts'

export type Cradle = {
  // importしているものをファクトリ関数と決め打っているので戻り値からserviceの型が取れる
  foo: ReturnType<typeof foo>
  bar: ReturnType<typeof bar>
}

// コンテナを作成するコード
const container = awilix.createContainer<Cradle>()

container.loadModules(['src/**/*.ts'], {
  formatName: 'camelCase',
})

スクリプト

genAwilixTypes.ts
import { glob, mkdir, readFile, writeFile } from 'node:fs/promises'
import * as path from 'node:path'


export const generateTypes = async (args: { load: string; outDir: string }) => {
  const outDirPath = path.resolve(args.outDir)
  const outImports = []
  const outCradles = []

  for await (const rawSrcPath of glob(args.load)) {
    const srcPath = path.resolve(rawSrcPath)
    const src = (await readFile(srcPath)).toString()

    if (!src.match(/(^|\n)export default/)) {
      continue
    }

    const key = path.parse(srcPath).name
    const srcPathFromOutFile = path.relative(outDirPath, srcPath)

    outImports.push(`import type ${key} from './${srcPathFromOutFile}'`)
    outCradles.push(`  ${key}: ReturnType<typeof ${key}>`)
  }

  const outFilePath = `${outDirPath}/awilix-types.d.ts`
  const content = `// generated by awilix-types
${outImports.join('\n')}

export type Cradle = {
${outCradles.join('\n')}
}
`

  await mkdir(outDirPath, { recursive: true })
  await writeFile(outFilePath, content)
}

おまけ: バンドラーと併用する場合

tsupの例

tsup.config.ts
import { generateTypes } from './genAwilixTypes.ts'
import { defineConfig } from 'tsup'

await generateTypes({
  load: 'src/**/*.ts',
  outDir: 'src/generated', // ← 生成先のディレクトリ
})

export default defineConfig(() => ({
  // 省略 ...
}))

Discussion