🍕

【TypeScript】importの記述を不要にするunplugin-auto-import

2022/04/17に公開

JavaScript/TypeScriptで開発していると、よく使うモジュールを何度もimportする必要が出てきます。

// これを何度も書く必要がある
import { useState } from 'react'

エディターが自動で補完してくれる場合も多いですが、何回も同じことを繰り返すのはDRYではありません。

そこで本記事ではこのimport文の記述を不要にするunplugin-auto-importについて使い方と簡単な仕組みをご紹介します!

環境

  • unplugin-auto-import: 0.7.1
  • TypeScript: 4.6.3

unplugin-auto-importとは?

unplugin-auto-importは事前に設定しておいたモジュールのimport文を不要にするJavaScriptのビルドツールプラグインです。Vue界隈やvitest、slidevの開発で有名なAnthony Fuさんによって開発されています。

https://github.com/antfu/unplugin-auto-import

実際の例を見てみましょう。
Reactでよく使うuseState をunplugin-auto-importを使って書くと、useState部分の記述が不要になります。

// import { useState } from 'react' ← この記述が不要に 🙌
export function Counter() {
  const [count, setCount] = useState(0)
  return <div>{ count }</div>
}

また、unplugin-auto-importはunpluingの一種で、Vite、Rollup、Webpack、esbuildなど様々なビルドツールで利用することができるのも嬉しいポイントです。


使い方

基本的な使い方を最小のプロジェクトで確認してみます。

事前準備

事前にプロジェクトを作成しておきます。

yarn create vite
yarn create v1.22.17
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-vite@2.9.1" with binaries:
      - create-vite
      - cva
✔ Project name: … auto-import-sample
✔ Select a framework: › react
✔ Select a variant: › react-ts

Scaffolding project in /Users/matsumoto.kazuya/lab/auto-import-sample...

Done. Now run:

  cd auto-import-sample
  yarn
  yarn dev

✨  Done in 9.86s.

以下のコマンドを入力して、ビルドが通って画面が表示されることを確認しておきましょう。

cd auto-import-sample
yarn
yarn dev

自動生成されたApp.tsx の冒頭で、useStateが使われているのでunpluing-auto-import を使って、この記述を不要にしていきます。

src/App.tsx
import { useState } from 'react' // この行を書かなくていいようにしていく

当たり前ですが、この時点ではimport行をコメントアウトすると、コンパイルエラーになります。
before

インストール

  1. unplugin-auto-import をプロジェクトにインストールします。

    yarn add -D unplugin-auto-import
    
  2. vite.config.tsのpluginsにAutoImportを追加します。

    import vue from '@vitejs/plugin-vue'
    import AutoImport from 'unplugin-auto-import/vite'
    import { defineConfig } from 'vite'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue(),
        AutoImport({/* ここに設定を書く */})
      ]
    })
    

以上で利用する準備ができました!

設定を書く

続いて、プラグインの設定を書いていきます。

AutoImport({
  include: [/\.[tj]sx?$/], // .ts, .tsx, .js, .jsx
  imports: ['react'],
  dts: './src/auto-imports.d.ts'
})

include

AutoImportの変換対象のファイルの拡張子を正規表現で指定します。

/\.[tj]sx?$/ がわかりにくと感じる方は

[/\.ts$/, /\.tsx$/, /\.js$/, /\.jsx$/]

のように書くと長くはなりますが、読みやすくなります👌

imports

グローバルにimportするモジュールを指定します。

ライブラリ側でvuereactなど、いくつかpresetsを用意してくれており、今回はreact のpresetsを使用しました。

個別にimportすることも可能です。

imports: [
  // presets
  'react',
  // カスタム
  {
    '@vueuse/core': [
      // named importsを使いたい場合
      'useMouse', // import { useMouse } from '@vueuse/core',
      // aliasを使いたい場合
      ['useFetch', 'useMyFetch'], // import { useFetch as useMyFetch } from '@vueuse/core',
    ],
    'axios': [
      // defaultを使いたい場合
      ['default', 'axios'], // import { default as axios } from 'axios',
    ],
    '[package-name]': [
      '[import-names]',
      // alias
      ['[from]', '[alias]'],
    ],
  },
]

dts

global declareで型を定義した .d.ts ファイルの出力先を指定します。
大概の場合はtsconfig.jsonの"include": ["src"] に合わせて、以下の記述になることが多いかと思います。

dts: './src/auto-imports.d.ts'

動作を確認

一通り設定できたので、再度ビルドしてみましょう。
useStateをコメントアウトしても、ビルドが通るようになりました🎉
after


仕組み

仕組みが全くわからないまま使うのも気持ち悪いので、仕組みについても簡単に説明します。
プラグインに渡されたオプションはソースコードと共にcreateUnpluginの関数に渡され、transform関数でコードの変換処理が行われます。

transformの中では以下の2つの処理を行っています。

  1. transform(core/transform)でソースコードを変換
  2. generateConfigFiles()で.d.tsファイルを生成
export default createUnplugin<Options>((options) => { // オプションを引数で受けとる
  ...
  return {
    ...
    async transform(code, id) {
    // 1. ソースコードを変換
      const res = await transform(code, id, resolved)
      if (res)
        // 2. .d.tsファイルを生成
        generateConfigFiles()

      return res
    }
		...
  }
})

transform(core/transform)でソースコードを変換

core/transform関数でソースコードの変換を行います。

この中でimportsのオプションを読み取って、ビルド後のソースにimport文を差し込んでいることがわかります。

export async function transform(
  code: string,
  id: string,
  {
    imports,
    sourceMap,
    resolvers,
    resolvedImports = {},
    ignore = [],
  }: TransformOptions,
) {
  // modulesのRecord
  const modules: Record<string, ImportInfo[]> = {}

  for (const name of Array.from(identifiers)) {
   // importsの情報からmodulesのレコードを生成
    let info = getOwn(resolvedImports, name) || getOwn(imports, name)
    ...
    addToModules({
      from: info.from,
      name: info.name,
      as: name,
    })
		...
  }

  // modulesからimport文を作成
  const importStatements = Object.entries(modules)
    ....

  // コードの頭にimprot文を差し込む
  // ex) import { useState } from 'react'
  const s = new MagicString(code)
  s.prependLeft(0, importStatements)

  return {
    code: s.toString(),
    ...
  }
}

先ほどのreactの例だとビルド後のソースの頭にimport { useState } from 'react' が追加されます。

import { useState } from 'react';import "./App.css"; // ←この行が自動で追加される
import logo from "./logo.svg";
import { jsx as _jsx } from "react/jsx-runtime";
....

generateConfigFiles()で.d.tsファイルを生成

generateConfigFilesではimportsのオプションから動的に型宣言ファイルを生成します。

import type { ImportsFlatMap } from '../types'

export function generateDeclaration(imports: ImportsFlatMap, resolvedImports: ImportsFlatMap = {}) {
  const body = [
    ...Object.entries(imports),
    ...Object.entries(resolvedImports),
  ]
    .sort((a, b) => a[0].localeCompare(b[0]))
    .map(([name, info]) => `  const ${name}: typeof import('${info.from}')${info.name !== '*' ? `['${info.name || name}']` : ''}`)
    .join('\n')
  return `// Generated by 'unplugin-auto-import'\n// We suggest you to commit this file into source control\ndeclare global {\n${body}\n}\nexport {}\n`
}

結果としてimportsに定義したモジュールを declare globalで宣言したauto-imports.d.tsが生成され、importなしで、グローバルに型が利用できるようになります。

// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
  ...
  const useState: typeof import('react')['useState']
  ...
}
export {}

まとめ

import文を不要にするunplugin-auto-import についてご紹介しました。
実際プロジェクトに導入してみると、長いimport文が消えてかなりコードの見通しがよくなり、予想以上にDXが向上しました!
ぜひ開発中のプロジェクトで試してみてください🔥

宣伝

マネーフォワードでは一緒に働けるエンジニアを募集しています!

Discussion