これまでのコードを最大限に活用！ViteでJavaScriptをライブラリー化してみよう

ウェブ開発で、過去に作ったUI部品やアルゴリズムを再利用できると便利ですよね！今回は、Viteを使って「Hello World」を表示するシンプルなライブラリーを作成し、どのようにして他のプロジェクトに組み込むかを解説します。

# はじめに

再利用可能なライブラリーを作っておくと、コードをメンテナンスしたり、他プロジェクトでの応用するときに非常に役立ちます。この記事では、Viteを使用して、背景色を虹色に輝かせる「Hello World」を表示するJavaScriptライブラリーの作り方をステップ・バイ・ステップで紹介します。

この記事に記載したソースコードはこちらで公開しています。

https://github.com/takatama/hello-lib

# 1. プロジェクトのセットアップ

まず、Viteを使ってプロジェクトをセットアップします。以下の手順に従って進めてください。

## Viteのインストール

```console
npm init vite@latest hello-lib
```

途中で選択肢が出てきます。今回は素の（Valnilla）JavaScriptで開発してみるので、以下を選択してください。

✔ Select a framework: › Vanilla
✔ Select a variant: › JavaScript

プロジェクトの初期化後、依存関係をインストールします。

```console
cd hello-lib
npm install
```

# 2. 不要なファイルの削除と、必要なディレクトリー・ファイルの追加

この状態で`npm run dev`を実行するとViteを使ったカウンターアプリを実行できます。ただ今回は不要なので、削除しておきます。

```console
rm *.html *.css *.js *.svg
rm -rf public
```

今回、ライブラリーのソースコードは`src`ディレクトリーの下に作っていきます。

```console
mkdir src
```

また、gitで管理するときに、誤って余計なファイルを公開してしまわないよう、`.gitignore`ファイルを作っておきます。また、gitを初期化しておきます。

```console
curl https://raw.githubusercontent.com/vitejs/vite/main/.gitignore -o .gitignore
git init
```

# 3. HTMLテンプレートの作成

次に、表示するコンテンツである「Hello World」のHTMLテンプレートを作成します。さらに、背景色を虹色に輝かせるCSSを追加してスタイルを整えます。

```html:src/template.html
<div class="hello-world">Hello World!</div>
```

```css:src/style.css
.hello-world {
  font-size: 24px;
  color: white;
  background-image: linear-gradient(
    270deg,
    red,
    orange,
    yellow,
    green,
    blue,
    indigo,
    violet
  );
  background-size: 400% 400%;
  animation: rainbowBackground 5s ease infinite;
  padding: 10px;
  border-radius: 5px;
}

@keyframes rainbowBackground {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}
```

# 4. Injectionロジックの実装

ここでは、テンプレートとスタイルをウェブページに挿入するJavaScriptコードを実装します。`HelloLib.init()`関数を作成し、指定されたDOM要素にHTMLとCSSを挿入します。

```javascript:src/main.js
import template from './template.html?raw';
import style from './style.css?raw';

const HelloLib = {
  init(container) {
    const styleElement = document.createElement('style');
    styleElement.innerHTML = style;
    document.head.appendChild(styleElement);

    container.innerHTML = template;
  }
};

export default HelloLib;
```

## ファイルを文字列としてインポートする`?raw`

今回制作するライブラリーは、テンプレートのHTMLやCSSを文字列としてインポートして、動的に注入します。Viteでは外部ファイルを`import`でインポートする際、ファイル名の末尾に`?raw`をつければ文字列として扱えます。

https://ja.vitejs.dev/guide/assets.html#importing-asset-as-string

## 外部から参照するための`export default`

モジュールとして定義したHelloLibを外部から参照できるようにするため、`export default HelloLib;`を記述しておくことは非常に重要です。詳しくはビルドの章で説明します。

# 5. ライブラリーのビルドとパッケージ化

ライブラリーをビルドしてパッケージ化するには、`vite.config.js`ファイルを設定し、ライブラリーモードを利用する必要があります。このファイルはプロジェクトのルートディレクトリーに置きます。

```javascript:vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    lib: {
      entry: './src/main.js',
      name: 'HelloLib',
      formats: ["es", "umd"],
      fileName: (format) => `hello-lib.${format}.js`,      
    },
  },
});
```

https://ja.vitejs.dev/guide/build.html#library-mode

## ビルドコマンド

次に、以下のコマンドを実行してライブラリーをビルドします。

```console
npm run build
```

実行結果は次の通りです:

```console
> hello-lib@0.0.0 build
> vite build

vite v5.4.3 building for production...
✓ 3 modules transformed.
dist/hello-lib.es.js  0.36 kB │ gzip: 0.26 kB
dist/hello-lib.umd.js  0.59 kB │ gzip: 0.41 kB
✓ built in 29ms
```

## ESMとUMDの違い

ビルドが完了すると、`dist`フォルダに2つの形式のファイルが生成されます。

### ESM形式 (`hello-lib.es.js`)

JavaScriptのESモジュール（ESM）形式のファイルです。ESM形式は、モジュールを他のモジュールとしてインポートするための標準的な仕組みです。

ESM形式では、import文を使ってモジュールを読み込みます。例えば、他のJavaScriptファイルで以下のようにインポートします。

```javascript
import HelloLib from './dist/hello-lib.es.js';
HelloLib.init(container);
```

モダンなブラウザや、ESMをサポートするNode.js環境で使用されます。

開発時には、ESM形式を使って直接モジュールを利用し、動作確認ができるようにします。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>HelloLib Example</title>
  </head>
  <body>
    <div id="hello-container"></div>
    <script type="module">
      import HelloLib from './src/main.js';

      const container = document.getElementById("hello-container");
      HelloLib.init(container);
    </script>
  </body>
</html>
```

### UMD形式 (`hello-lib.umd.js`)

UMD（Universal Module Definition）形式は、モジュールをブラウザやNode.jsの両方で動作させるために使用される形式です。UMD形式では、グローバルスコープにモジュールが自動的に定義されます。

UMD形式では、`<script>`タグで読み込むと、HelloLibがグローバルオブジェクトとして定義され、直接利用できます。

```javascript
<script src="/dist/hello-lib.umd.js"></script>
<script>
  const container = document.getElementById('hello-container');
  HelloLib.init(container);
</script>
```

UMD形式は、ブラウザやNode.jsの両方で動作するため、幅広い環境で使用されます。

ビルドしたUMD形式のファイルが正しく動作しているかを確認するため、`test`ディレクトリーを作り、その下に確認用の`index.html`を置いて動作を確認できるようにします。

```console
mkdir test
```

```html:test/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Test of HelloLib Example</title>
  </head>
  <body>
    <h3>Test</h3>
    <div id="hello-container"></div>
    <script src="../dist/hello-lib.umd.js"></script>
    <script>
      const container = document.getElementById("hello-container");
      HelloLib.init(container);
    </script>
  </body>
</html>
```

# 6. 動作確認

## ホットリロードを活用した効率的な開発

前述した通り、ESM形式を使う`index.html`と、UMD形式を使う`test/index.html`を準備したら、動作を確認してみます。

開発用サーバーを実行し、`index.html`でHello Worldが正しく表示されているかを確認します。ホットリロードが有効になっているため、関連するソースコードを変更すると自動的に再読み込みされます。

```console
npm run dev
```

を実行して、表示されたURLにアクセスしてみましょう。

```console

  VITE v5.4.3  ready in 89 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
```

## ビルドした成果物の動作確認

開発が一段落したら、ビルドで出力されたライブラリーが正しく動作しているかを確認します。

```cosole
npm run build
```

でビルドが成功したら、`test/index.html`を開いて正しく表示されることを確認します。

# 7. ライブラリーの設定を`package.json`に追加

ライブラリーがさまざまなJavaScriptの環境でスムーズに動作するよう、公式ドキュメントが推奨する`package.json`の記述を追記しておきます。

https://ja.vitejs.dev/guide/build.html#library-mode

```javascript:package.json
{
  "name": "hello-lib",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "files": [
    "dist"
  ],
  "main": "./dist/hello-lib.umd.js",
  "module": "./dist/hello-lib.es.js",
  "exports": {
    ".": {
      "import": "./dist/hello-lib.es.js",
      "require": "./dist/hello-lib.umd.js"
    }
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "^5.4.1"
  }
}
```

追記する項目について説明します。

1. `files`フィールド
***files***: 公開時にnpmパッケージに含めるファイルを指定します。パッケージを軽量化するため、不要なファイルのみを定義します。

2. `main`フィールド
***main***: CommonJS形式のエントリーポイントを指定します。これは、古いJavaScript環境やNode.jsで使われる形式です。ここでは、ビルドしたUMD形式のファイルを指定します（`"main": "./dist/hello-lib.umd.js"`）。

3. `module`フィールド
***module***: ESモジュール形式（ESM）でのエントリーポイントを指定します。これは、モダンなブラウザやJavaScript環境で利用される形式です（`"module": "./dist/hello-lib.es.js"`）。
`main`と`module`の2つのフィールドを指定することで、ユーザーは自動的に環境に応じて適切な形式を選択してライブラリをインポートできます。

4. `exports`フィールド
***exports***: ライブラリがどの形式でエクスポートされるかを詳細に定義します。これにより、環境ごとに適切なビルド形式を提供できます。たとえば、ESM形式が必要な場合と、CommonJS形式が必要な場合のパスを指定できます。以下の例は、`import`を使用する場合は`ESM`が、`require`を使用する場合は`UMD/CJS`が読み込まれるようになります。

```json
{
  "exports": {
    "./": {
      "import": "./dist/hello-lib.es.js",
      "require": "./dist/hello-lib.umd.js"
    }
  }
}
```

# `.cjs`ファイルについての注意事項

Viteのライブラリーモードでビルドを行う際、`vite.config.js`で`fileName`を指定しないと、UMD形式のファイルはデフォルトで拡張子`.cjs`として出力されます。しかし、`.cjs`拡張子はNode.jsでのCommonJSモジュールとして使用されることが一般的であり、ブラウザでは適切に処理されない場合があります。

特に、Cloudflareなどのホスティングサービスでは、`.cjs`ファイルが`application/node`というMIMEタイプで提供されることがあります。このMIMEタイプはブラウザでは実行できないため、次のようなエラーが発生します。

```console
Refused to execute script from 'https://your-site/dist/hello-lib.umd.cjs' because its MIME type ('application/node') is not executable.
```

このエラーは、ブラウザが`.cjs`ファイルをNode.js専用のモジュールとして認識してしまうためです。この問題を回避するためには、`vite.config.js`で`formats`と`fileName`を明示的に指定し、UMD形式のファイルを`.js`として出力する設定に変更します。

```json:vite.config.js 
  lib: {
    ...
    formats: ["es", "umd"],
    fileName: (format) => `hello-lib.${format}.js`,
```
これにより、ビルドされたUMD形式のライブラリーは`.js`拡張子で出力され、クラウドホスティングサービス上でのMIMEタイプエラーを防ぎ、ブラウザで正しく認識・実行されます。

# まとめ

今回の記事では、Viteを使って「Hello World」を表示するJavaScriptライブラリーを作成しました。この記事の手順に従うことで、他のプロジェクトに再利用可能なライブラリーを作成できるようになります。

この記事には基本的なことしか書いてありませんが、皆さんがこれまでに開発してきた資産を再利用するきっかけになれば幸いです。