Viteのlibraryテンプレートでブラウザ向けnpmパッケージを作ってみる
はじめに
TL;DR
Vite の library テンプレートを使うと、簡単なブラウザ向け npm ライブラリを比較的楽に作ることができます。
概要と対象読者
最近筆者は luma-splatting-for-babylonjs という npm パッケージを公開しました。この npm パッケージは機能的にはすごいシンプルなのでサクッと公開したいと考えており、その際に create-vite CLI で使える library というテンプレートを使って見たところ結構良かったのでご紹介します。
この記事でご紹介する内容には Node.js や npm(pnpm)、Vite、TypeScript といったツールが登場します。ある程度これらについて知識があると読みやすいでしょう。
本記事の読者像を下記に示します。
- npm パッケージを作って公開してみたい
- TypeScript を使って Web フロントエンド開発をしている・したことがある
- Vite を使っている
これらについては完全に当てはまる必要はありませんので、お気軽に読んでいただければ嬉しいです。また、本記事ではパッケージマネージャに pnpm を採用していますので、ご自分の環境に合わせてコマンドを読み替えてください。
検証環境
記事の執筆において用いた検証環境を次に示します。
- Windows 10 Home
- Node.js 20.9.0
- pnpm 8.12.1
- Vite 5.0.10
サンプルプロジェクト
本記事の内容に沿って作成されたサンプルプロジェクトを GitHub にて公開しておりますので、合わせてご覧ください。
プロジェクトの作成とロジックの記述
それでは npm ライブラリを作成していきましょう。まずはライブラリ本体の作成から着手します。
create-vite CLIによるプロジェクトの作成
Vite を使ったプロジェクトを作成する際には create-vite という CLI を使用します。下記コマンドによって create-vite を起動しましょう。
pnpm create vite@latest
するといくつか選択肢が現れますので、「Others」を選択します。
Ohtersを選択
そうすると別の選択肢が現れます。今回は Electron に関係ないので「create-vite-extra」を選択します。
create-vite-extraを選択
最後に提示される項目をスクロールして「library」を選択しましょう。
libraryを選択
そうすると、今回のベースとなるプロジェクトが作成されます。今回は試験的に「ninisan-demo-library」という名前で作成してみました。この状態で依存解決とした後に dev サーバを立ち上げると次のような画面が出てくるはずです。
ライブラリのロジックを編集
プロジェクトを作成すると、ルートにsrc/
ディレクトリとlib/
が作成されました。
-
src/
: dev サーバで立ち上がる Web フロントのプロジェクト -
lib/
: ライブラリ本体。src/
で参照されている
この状態から少し整理して、最終的に次に示すようなファイル構成に整理してみました。
/
├─ lib/
│ ├─ api.ts
│ └─ index.s
├─ src/
│ └─ main.ts
├─ tsconfig.ts
└─ package.json
ライブラリ本体では、単純に 2 数を加算するadd
関数と引数の文字列に対してあいさつする文字列を返すsayHello
関数を実装し、外部へ公開しました。
またライブラリのエントリポイントとしてlib/index.ts
を用意し、ライブラリで公開したい API を記述しました。
export const add = (a: number, b: number) => a + b;
export const sayHello = (name: string) => `Hello, ${name}!`;
export * from "./api";
そしてライブラリで作成した 2 つの関数をインポートして、実際に Web ページ上で実行結果を表示します。
import { add, sayHello } from "../lib";
const result = add(1, 2);
const message = sayHello("Alice");
const resultView = document.getElementById(
"result"
) as HTMLParagraphElement | null;
const messageView = document.getElementById(
"message"
) as HTMLParagraphElement | null;
if (resultView && messageView) {
resultView.textContent = `1+2=${result}`;
messageView.textContent = `message is "${message}"`;
}
HTML と CSS を適当に整えて結果を表示すると次のようなページが表示されました。意図したとおりに動いているようですね。
型定義ファイルの出力
この時点で既に、ビルドすると 2 つの関数を含んだライブラリファイルがビルドされますが、型定義ファイルを出力には対応していません。TypeScript でも使えるように、型定義ファイルを自動生成する仕組みを導入しましょう。
tsconfigの編集
まずはtsconfig.ts
の設定をします。次に示すような変更を加えましょう。
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
- "noEmit": true,
+ // "noEmit": true,
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "./dist",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
- "include": ["src"]
+ "include": ["lib"]
}
pluginによる型定義の出力
ビルド時に型定義ファイルを生成させるにはいくつか方法があり、tsc 単体で出力する方法もあるのですが、今回は Vite のプラグインを使います。次のコマンドでvite-plugin-dts
をインストールしましょう。
pnpm add -D vite-plugin-dts
そしてvite.config.ts
にプラグインの設定を追記します。ライブラリのエントリポイントが変わったことにより、Vite の lib モードで出力する際の設定も変わっていますので合わせて確認しましょう。
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import { resolve } from "path";
export default defineConfig({
plugins: [dts()],
build: {
lib: {
entry: resolve(__dirname, "./lib/index.ts"),
name: "ninisan-demo", // パッケージの名前に変更
fileName: "index",
formats: ["es", "umd"],
},
},
});
この状態でpnpm build
を実行すると、無事にdist/
以下に型定義ファイルが出力されました。
npmパッケージの公開
最後にパッケージの公開のための設定や動作確認をしていきます。
package.jsonの編集
npm パッケージを公開する際には、package.json
へいくつか設定を追加する必要があります。Vite の library テンプレートでは既に main, module, types フィールドなどの設定が追加されていますが、今回は型定義ファイルの場所や出力される JS ファイルの名前が変わっているので修正しましょう。
また、description や homepage、keywords などの要素も付け加えるとちゃんとした npm パッケージっぽさが出てきます。ライセンスの記載も必要ですね。
最終的に出来上がったpackage.json
は次のようになりました。
{
"name": "ninisan-demo-library",
"private": false,
"version": "0.1.0",
"description": "ninisan's demo npm package",
"author": "drumath2237",
"homepage": "https://github.com/drumath2237/ninisan-demo-library",
"license": "Apache-2.0",
"keywords": [
"npm",
"typescript"
],
"packageManager": "pnpm@8.12.1",
"repository": {
"type": "git",
"url": "https://github.com/drumath2237/ninisan-demo-library"
},
"type": "module",
"files": [
"dist"
],
"main": "./dist/index.umd.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.umd.cjs"
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"prepare": "pnpm build"
},
"devDependencies": {
"@types/node": "^20.10.5",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vite-plugin-dts": "^3.7.0"
}
}
ローカル環境での動作テストと公開
あとは公開するだけですが、一応パッケージの動作確認をしてみましょう。npm パッケージとして公開する前だとしても、パッケージマネージャのシンボリックリンクによるインストールによってローカル環境で動作を確認できます。今回は pnpm を使った方法をご紹介しますが、npm や yarn にも同様の機能は存在します。
まずはパッケージのビルドを実行し、次のコマンドを npm パッケージのディレクトリ(package.json がある階層)で実行してグローバル環境にシンボリックリンクを張りましょう。
# ビルド
pnpm build
# シンボリックリンクをグローバルに貼る
pnpm link --global
次に適当な Web フロントのプロジェクトを Vite などで作成し、そのプロジェクトのディレクトリで下記コマンドによりシンボリックリンクによるインストールを行います。
pnpm link --global <パッケージ名>
パッケージ名は package.json に記載した名前を使います。
今回は簡単な Vanilla-TypeScript なプロジェクトを Vite で作成してみました。
コード上で通常通りにパッケージをインストールして使って見ます。型定義ファイルを出力しているので型補完もされていますね。
// 中略
import { add, sayHello } from "ninisan-demo-library";
const result = add(1, 2);
const msg = sayHello("Bob");
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<div>
<h1>1+2=${result}, "${msg}"</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<p class="read-the-docs">
Click on the Vite and TypeScript logos to learn more
</p>
</div>
`;
そうして dev サーバを起動すると次のような Web ページが表示されました。どうやらちゃんと動いているみたいですね。
最後にパッケージのディレクトリに戻って次のコマンドを実行すれば npm パッケージを公開できます。
npm publish
おわりに
まとめ
本記事では Vite の library テンプレートでプロジェクトを作成して、さらに型定義ファイルを出力する設定も追加しました。簡単なブラウザ向け npm パッケージを作るには比較的サクッと試せる方法ですので、ぜひどこかで試してみてください。
最後まで読んでいただきありがとうございました。
参考文献
Discussion