TypeScriptでライブラリを型付きで分割提供する方法!モジュール設計の完全ガイド
生徒
「TypeScriptでライブラリを作って、型情報も一緒に配布する方法はありますか?」
先生
「もちろんあります。TypeScriptでは、型定義ファイルを含めてライブラリを分割提供することができます。」
生徒
「型定義ファイルって何ですか?どうやって配布するんですか?」
先生
「それでは、ライブラリを型付きで分割提供する方法を、基本から順番に見ていきましょう!」
1. 型定義ファイルとは?
TypeScriptで作ったライブラリを配布する際、型情報を一緒に提供すると、ライブラリを使う人がコードを書くときに自動補完や型チェックの恩恵を受けられます。この型情報を記述したファイルが型定義ファイル(.d.tsファイル)です。
例えば、本屋さんで本を買うとき、本のタイトルや著者名、価格などの情報が表紙に書いてあると便利ですよね。型定義ファイルはそれと同じで、「この関数はどんな引数を受け取って、何を返すのか」といった情報を明示的に提供するものです。
型定義ファイルがあることで、ライブラリを使う開発者は、エディタ上で関数の使い方を確認でき、間違った使い方をするとすぐにエラーが表示されるため、開発効率が大幅に向上します。
2. ライブラリプロジェクトの基本構成
TypeScriptでライブラリを作る場合、プロジェクトの構成をしっかり設計することが重要です。一般的なライブラリプロジェクトは以下のような構成になります。
my-library/
├── src/ (TypeScriptのソースコード)
│ ├── index.ts
│ ├── math.ts
│ └── utils.ts
├── dist/ (コンパイル後のJavaScriptファイルと型定義ファイル)
│ ├── index.js
│ ├── index.d.ts
│ ├── math.js
│ ├── math.d.ts
│ ├── utils.js
│ └── utils.d.ts
├── package.json
└── tsconfig.json
srcフォルダには開発時に書くTypeScriptファイルを配置し、distフォルダにはコンパイル後のJavaScriptファイルと型定義ファイルが出力されます。このdistフォルダの内容を配布することで、他の開発者がライブラリを使えるようになります。
3. tsconfig.jsonの設定
TypeScriptでライブラリを作るには、まずtsconfig.jsonというファイルで、コンパイルの設定を行います。このファイルは、TypeScriptコンパイラに「どのようにコードを変換するか」を指示する設定ファイルです。
{
"compilerOptions": {
"target": "ES2015",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
特に重要なのが"declaration": trueという設定です。これを有効にすると、TypeScriptファイルをコンパイルする際に、自動的に型定義ファイル(.d.tsファイル)も生成されます。"declarationMap": trueを設定すると、デバッグ時に元のTypeScriptファイルを参照できるマップファイルも生成されます。
"outDir"はコンパイル後のファイルを出力するフォルダ、"rootDir"はソースコードが配置されているフォルダを指定します。
4. ライブラリのソースコードを作成する
それでは実際にライブラリのソースコードを書いてみましょう。ここでは、数学関数とユーティリティ関数を提供する簡単なライブラリを作ります。
まず、src/math.tsを作成します。
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
次に、src/utils.tsを作成します。
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str: string): string {
return str.split('').reverse().join('');
}
これらの関数は、それぞれexportキーワードを使って外部に公開しています。exportは「このファイルの外からも使えるようにする」という意味です。
5. エントリーポイントを作成する
ライブラリを使いやすくするために、src/index.tsというエントリーポイントファイルを作成します。エントリーポイントとは、ライブラリの入り口となるファイルのことです。
export * from './math';
export * from './utils';
この書き方は、math.tsとutils.tsからエクスポートされているすべての関数を、再度エクスポートし直すという意味です。こうすることで、ライブラリを使う人は以下のように簡潔にインポートできます。
import { add, capitalize } from 'my-library';
エントリーポイントを設けることで、ライブラリの内部構造を隠蔽し、使いやすいインターフェースを提供できます。
6. package.jsonの設定
ライブラリを配布するには、package.jsonの設定も重要です。特に、どのファイルがライブラリのメインファイルか、型定義ファイルはどこにあるかを指定する必要があります。
{
"name": "my-library",
"version": "1.0.0",
"description": "数学関数とユーティリティ関数を提供するライブラリ",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc"
},
"keywords": [
"typescript",
"library",
"math",
"utils"
],
"author": "Your Name",
"license": "MIT"
}
"main"には、JavaScriptのメインファイルのパスを指定します。"types"には、型定義ファイルのパスを指定します。この設定により、ライブラリを使う側のTypeScriptが自動的に型情報を認識できるようになります。
"files"配列には、npmパッケージとして配布する際に含めるファイルやフォルダを指定します。ここではdistフォルダのみを配布対象にしています。
7. ライブラリをビルドする
ライブラリのソースコードができたら、TypeScriptコンパイラでビルドします。ビルドとは、人間が書いたTypeScriptコードを、コンピュータが実行できるJavaScriptコードに変換する作業のことです。
以下のコマンドを実行します。
npm run build
すると、distフォルダに以下のようなファイルが生成されます。
index.js:JavaScriptのメインファイルindex.d.ts:型定義ファイルmath.js、math.d.ts:数学関数のファイルutils.js、utils.d.ts:ユーティリティ関数のファイル
型定義ファイル(.d.tsファイル)を見てみると、以下のような内容になっています。
export declare function add(a: number, b: number): number;
export declare function subtract(a: number, b: number): number;
export declare function multiply(a: number, b: number): number;
export declare function capitalize(str: string): string;
export declare function reverse(str: string): string;
この型定義ファイルには、関数の名前、引数の型、戻り値の型だけが記述されており、実際の処理内容は含まれていません。これにより、ライブラリを使う側は型情報を得られますが、内部の実装詳細は隠蔽されます。
8. ライブラリを使ってみる
作成したライブラリを実際に使ってみましょう。別のプロジェクトで以下のようにインポートして使います。
import { add, multiply, capitalize, reverse } from 'my-library';
const result1 = add(10, 5);
console.log(result1);
const result2 = multiply(3, 4);
console.log(result2);
const capitalized = capitalize('hello');
console.log(capitalized);
const reversed = reverse('typescript');
console.log(reversed);
実行結果は以下のようになります。
15
12
Hello
tpircsepyt
TypeScriptが有効な環境では、エディタで関数名を入力すると自動補完が表示され、引数や戻り値の型も確認できます。また、間違った型の引数を渡すと、コンパイル時にエラーが表示されます。
9. 名前空間を使った分割提供
もう一つの方法として、名前空間を使ってライブラリを分割提供する方法もあります。名前空間とは、関連する機能をグループ化して管理する仕組みです。
export namespace Math {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
}
export namespace Utils {
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str: string): string {
return str.split('').reverse().join('');
}
}
この方法で作成したライブラリは、以下のように使います。
import { Math, Utils } from 'my-library';
const sum = Math.add(10, 5);
const text = Utils.capitalize('hello');
console.log(sum);
console.log(text);
名前空間を使うと、関数が論理的にグループ化されるため、ライブラリの構造が分かりやすくなります。ただし、現代のTypeScript開発では、名前空間よりもモジュールシステム(exportとimport)を使う方が一般的です。
10. 型定義のみを提供する場合
既存のJavaScriptライブラリに対して、後から型定義だけを提供することもできます。この場合、@typesという仕組みを使います。
例えば、JavaScriptで書かれたライブラリがあり、それに型定義を追加したい場合は、以下のような型定義ファイルを作成します。
declare module 'existing-library' {
export function doSomething(value: string): number;
export function processData(data: any[]): void;
}
declareキーワードは、「この関数は実際には別の場所で定義されていますが、型情報だけここで宣言します」という意味です。これにより、JavaScriptライブラリをTypeScriptプロジェクトで型安全に使えるようになります。
11. ライブラリ配布時の注意点
ライブラリを配布する際は、以下の点に注意しましょう。
ソースコードは配布しない
通常、srcフォルダのTypeScriptソースコードは配布せず、distフォルダのコンパイル済みファイルのみを配布します。これにより、ライブラリのサイズを小さくでき、読み込み速度も向上します。
バージョン管理を適切に行う
package.jsonのバージョン番号は、セマンティックバージョニング(major.minor.patch形式)に従って管理しましょう。破壊的な変更があればメジャーバージョンを上げ、新機能追加ならマイナーバージョンを、バグ修正ならパッチバージョンを上げます。
ドキュメントを用意する
READMEファイルに、ライブラリのインストール方法、基本的な使い方、各関数の説明を記載しましょう。ドキュメントが充実していると、利用者が増えやすくなります。