SFC と TS モジュールの import 文を省略してコードを減らす


## 前提条件

- VueCLI v5.0.8
- TypeScript v4.8.1
- Vue v2.7
- unplugin-auto-import v0.11.2
- unplugin-vue-components v0.22.9

## import 文省略

`.vue` や、 `.ts` の `import` 文を省略できるようにしていきます。

`.ts` は、 `import` 文なしで参照できるようになります。

参照されたモジュールは、ビルド時にオンデマンドで自動インポートされます。

`.ts` の `import` 文省略の方法は、[こちら](https://tech.gamewith.co.jp/entry/2022/10/14/123854)で [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) を利用した説明をしています。

### SFC
`.vue` は、 `<template>` 内で記述された場合にオンデマンドで自動インポートされます。

`.vue` の自動インポートは、 [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) で行えるようになります。

まずは、  [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) をインストールします。

```bash
pnpm add -D unplugin-vue-components
```

[unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) で SFC の自動インポートを有効にするには、 `vue.config.js` の中で webpack の設定に以下を追加します。

```js
module.exports = {
  configureWebpack: {
    plugins: [
      require("unplugin-vue-components/webpack")({
        dirs: ["src/packages"], // コンポーネントを探索するディレクトリ
        deep: true, // サブディレクトリを探索
        extensions: ["vue"], // コンポーネントの拡張子
        globs: ["src/packages/**/!(temp)/*.vue"], // glob パターンを用いて自動インポートの対象から外すことが可能 
        dts: "src/components.d.ts", // volar を使っていれば、 TypeScript のサポートを受けれれます
        version: 2.7, // Vue.js のバージョン
      }),
    ],
  },
};
```

`dts` オプションで指定したファイルに SFC の定義がグローバル空間に追加されます。

`tsconfig` の `include` で指定した範囲内に出力されるようにします。

VSCode で [volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) をインストール済みであれば、 `<template>` 内で、コンポーネントの補完が有効になり便利です。

また、 `props` の型サポートも有効になります。

### 型定義の import 文省略

[unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) では、 型エイリアス `export type` や インタフェース `export interface` は、 `import` 文を省略できません。

そのため、 `export` されている 型定義をグローバル定義にして import 文を省略できるようにします。

```ts: src/global-types.d.ts
import * as packages from "@/packages";

declare global {
 // グローバル型定義を追記していく
 type SampleType = pacakges.SampleType,
}
```

`index.ts` で `export` を集約でていないと、 `import` 文を都度追記して代入する必要があるので、手間がかかりすぎてしまいます。

サブディレクトリも含めてすべての `export` を集約できるように `index.ts` を自動生成しておきます。

こうしておくことで、 `packages` ディレクトリ内の全ての型定義を参照できるようなっています。

`index.ts` を再帰的に自動生成するには、 [ctix](https://github.com/imjuni/ctix) を使います。

`export` するモジュールの命名は、ファイル名をプレフィックスにするなど、グローバルネームスペースで衝突を避ける工夫が必要です。

`index.ts` は、バンドラーでは全てのファイルがバンドルされてビルド後のファイル容量が大きくなってしまいます。

しかし、ここでは型定義を集めているだけなのでバンドルサイズは影響を受けません。

#### ESLint ルール

型定義をグローバル定義すると `import` 文を省略してもビルドは通るようになりますが、 ESLint を導入済みであれば [no-undef](https://eslint.org/docs/latest/rules/no-undef) エラーが検出されてしまいます。

エラーを防ぐため、 `import` 文を省略した型定義は、グローバル定義済みであることを ESLint ルールに追加する必要があります。

```json: .eslintrc-global-types.json
{
  "globals": {
    "SampleType": true
  }
}
```

`.eslintrc.js` から `.eslintrc-global-types.json` のルールを `extens` します。

```js: .eslintrc.js
module.exports = {
  extends: [
    ".eslintrc-global-types",
  ]
}
```


ESLint ルール `.eslintrc-global-types.json` を用意する手間を省くため、グローバル型定義ファイル`src/global-types.d.ts` から生成させるスクリプトを実装しました。

```js: tools/generate-eslint-rule-global-types.mjs
import { $ } from "zx";
import fire from "js-fire";
import { render } from "template-file";

const root = process.cwd();

const template = `{
  "globals": {
    {{typesJson}}
  }
}`;

const generateEslintRuleGlobalTypes = {
  __description__: "📦  global-types.d.ts から ESLint ルールのグローバル型を生成する",
  execute: async () => {
    const globalTypesFile = await $`cat ${root}/src/global-types.d.ts`;
    const globalTypes = globalTypesFile.stdout.split("\n")
      .map((line) => line.trim())
      .filter((line) => line.length > 0 && line.match(/^type\s/) !== null)
      .map((line) => {
        //型名を取得
        return line.split(" ")[1];
      })
      .map((line) => {
        // ジェネリスク型を除外;
        return line.replace(/<.*>/, "").replace(/<.*/, "");
      });
    const typesJson = globalTypes.map((line) => {
      return `"${line}": true`;
    }).join(",\n    ");
    const result = await render(template, { typesJson });
    await $`echo ${result} > ${root}/.eslintrc-global-types.json`;
  },
};

fire(generateEslintRuleGlobalTypes);

```

以上のスクリプトを npm scripts から実行できるようにしておきます。

```json: package.json
  "scripts": {
    "gen-eslint-globals": "node tools/generate-eslint-rule-global-types.mjs execute"
  }
```

新たに `import` 文を省略したい型がある場合は、以下の手順で省略可能にします。

1. `src/global-types.d.ts` に型定義を追加。
1. `$ pnpm gen-eslint-globals` を実行。 
1. `.eslintrc-global-types.json` に ESLint ルールが追加されて `no-undef` エラー回避。

## まとめ

`export` しているモジュールは、 `import` 文を省略しつつコード補完が効いて実装が快適になったので概ね良かったと思います。

ローカルコンポーネントの SFC は使用時に `import` 文が不要になり、 `.ts` ファイルは `export` されているモジュールや型定義の `import` 文を省略することができました。

今回は SFC や、 `.ts` ファイルが合計で 9万行程度のプロジェクト内で import 文を省略し、合計で 3,000行の`import` 文を省略させることができました。

コードを減らせるということはレビューコストが減ると思います。

また、`import` 文が省略され自動インポートとなるので、誤って粒度が大きい `index.ts` をインポートしてしまいバンドルファイルサイズが増えるリスクも少なくなります。

注意しなければいけないのは、 `export` するモジュールがグローバル定義となることです。

プロジェクト内のディレクトリ階層はあまり深くせず、 named export の命名はファイル名をプレフィックスにするなど、コンフリクトしないような命名規則にしておく必要があります。