12k
All articles

Rslib で JavaScript ライブラリを公開する

RslibでJavaScriptライブラリを公開する方法を解説。ESM・CJSビルド、TypeScript宣言ファイル、コンポーネントのスタイル設定とCIでのパッケージ検証を紹介します。

OpenReplay Team
OpenReplay Team
Rslib で JavaScript ライブラリを公開する

Rslib は、Rsbuild と Rspack をベースにしたライブラリ向けビルドツールです。1 つの設定ファイルから、ESM と CommonJS の出力、TypeScript の型宣言ファイル、フレームワーク向けにコンパイルされたコンポーネント、処理済みのアセットを生成します。対象は、デプロイするアプリではなく npm に公開するパッケージです。

この違いは重要です。アプリのバンドルは、1 つのブラウザタブで動けば十分です。一方ライブラリは、他の人のバンドラー、さまざまな Node.js のバージョン、異なる TypeScript 設定からインポートされます。そのため、自分のテストアプリでは見えない形で壊れます。require() が失敗する、型が any に解決される、コンポーネントがスタイルなしで配布される、といった問題です。本ガイドでは、2026 年 9 月 3 日にリリースされた Rslib 1.0 を使い、1 つのパッケージを最初のビルドから CI での公開前チェックまで進めます。

重要なポイント

  • Rslib 1.0 では、設定と JavaScript API が安定版となり、セマンティックバージョニングに従います。動作には Node.js 20.19 以降または 22.12 以降が必要です。
  • Rslib のドキュメントでは、新しいパッケージには pure ESM を推奨しています。ESM を読み込めない特定の利用者がいる場合にのみ、ESM/CJS のデュアルビルドを使います。
  • Node.js ^20.19.0 または >=22.12.0 では、CommonJS のコードから pure ESM パッケージを require() できます。ただし、エントリを含め、読み込まれるどのモジュールもトップレベル await を使っていないことが条件です。
  • バンドルモード(bundle: true)は SDK、CLI、Node.js ユーティリティに適しています。バンドルレスモード(bundle: false)はソースのファイル構成を維持するため、コンポーネントライブラリに適しています。
  • rsbuild-plugin-publint と rsbuild-plugin-arethetypeswrong は、CI 上でのみ rslib build の中で実行できます。これにより、パッケージングのミスがあれば npm publish の前にビルドが失敗します。

ライブラリの公開がアプリのビルドより難しいのはなぜか

ライブラリの公開がアプリのビルドより難しいのは、公開可能なパッケージでは 4 つのことを同時に正しく整えなければならないからです。まず、サポート対象のすべてのランタイムで読み込める ESM 出力と、必要に応じて CJS 出力が必要です。次に、利用者それぞれの moduleResolution 設定で解決できる型宣言ファイルが必要です。さらに、コンポーネントは CSS と一緒に配布しなければなりません。そして、package.json の exports フィールドは、各条件が実際に存在するファイルを指している必要があります。

通常、これらはそれぞれ別のツールが担います。JavaScript はバンドラー、型宣言は tsc、スタイルは PostCSS の処理ステップが担当し、それらを手書きの exports マップでまとめます。どれか 1 つのツールが出力パスを変えるたびに、パッケージは壊れてしまいます。

Rslib とは

Rslib は、この 4 つの作業すべてを 1 回のビルドでこなすライブラリ向けビルドツールです。lib 配列で各出力フォーマットを定義します。dts オプションは型宣言ファイルを生成します。バンドルレスモードでは、宣言ファイル内のパスエイリアスやインポートの拡張子も、出力する JavaScript ファイルに合わせて修正します。CSS の扱いはビルドモードによって異なります。exports のミスは Publint が検出します。なお、Rslib が exports フィールドを代わりに書いてくれるわけではありません。自分で書いた内容をチェックする仕組みです。

最小構成の Rslib ビルドを一通り試す

最も手早く始めるには、プロジェクトのひな形を生成するか、CLI コマンドを 1 つ実行します。既存のパッケージの場合は、フォーマットごとに lib エントリを 1 つずつ持つ設定ファイルを追加します。クイックスタートによると、Rslib v1 には Node.js 20.19 以降または 22.12 以降が必要です。

npm create rslib@latest
# or, in an existing package
npm add @rslib/core -D

単一フォーマットのビルドなら、設定ファイルは不要です。

npx rslib build --entry src/index.ts --format esm --dts

--entry でソースのエントリを指定し、--format esm でモジュールフォーマットを選択し、--dts で型宣言ファイルを出力します。デュアル出力にする場合は、rslib.config.ts を作成します。

// rslib.config.ts
import { defineConfig } from '@rslib/core';

export default defineConfig({
  lib: [
    { format: 'esm', dts: true },
    { format: 'cjs', dts: true },
  ],
  syntax: 'es2023',
});

トップレベルで設定した内容は、すべての出力に適用されます。Rslib はこれを各 lib 項目とマージするため、各エントリには差分だけを記述すれば済みます。また Rslib 1.0 では、デフォルトの ESM 出力が 1 つだけでよければ、lib 自体を省略できます。ビルドスクリプトを追加しましょう。

{ "scripts": { "build": "rslib build" } }

ビルド後の出力ディレクトリはデフォルトで dist です。ここに ESM ビルド、CJS ビルド、型宣言ファイルが含まれます。ファイルの拡張子は lib.autoExtension に従います。"type": "module" がある場合、ESM 出力は .js、CJS 出力は .cjs になります。ない場合は、CJS が .js、ESM が .mjs になります。デフォルトでは、どちらのフォーマットも同じ .d.ts という名前で型宣言ファイルを出力することがあります。フォーマットごとの宣言ファイルの拡張子は、dts オプションで制御します。exports マップでは、import 条件を ESM のエントリに、require 条件を CJS のエントリに向け、それぞれに対応する型宣言を指定します。パスは推測せず、自分の dist から実際のパスをコピーしてください。

ESM と CJS の出力では、Rslib はデフォルトで dependencies、optionalDependencies、peerDependencies をバンドルしません。そのため、React などのピア依存は外部のまま残ります。一方、devDependencies はバンドルされます。

ESM のみで公開するか、ESM/CJS のデュアルで公開するか

Rslib の出力フォーマットガイドでは、新しいパッケージは pure ESM で公開し、利用者が明確に必要とする場合にのみ CJS を追加するよう推奨しています。デュアル出力のコストは、ビルドステップが 1 つ増えるだけではありません。正しく保つべき exports エントリが 2 組になり、テストすべき構成も 2 倍になります。さらに、両方のフォーマットを読み込む利用者側ではパッケージのインスタンスが 2 つできてしまい、共有状態や instanceof のような同一性チェックが壊れる可能性があります。

多くの CommonJS 利用者には、もはや CJS ビルドは必要ありません。Node.js ^20.19.0 または >=22.12.0 では、pure ESM パッケージを直接読み込めます。ただし、2 つの条件を満たす必要があります。exports で require() 呼び出しが ESM のエントリに到達できるようにしておくこと、そして読み込まれるモジュールグラフ内のどのモジュールもトップレベル await を使っていないことです。

const utils = require('my-esm-only-package');

デュアル出力で公開するのは、CommonJS ユーザーの一部が require() 経由で ESM を読み込めない Node のバージョン、ツール、ランタイムを使っている場合か、ユーザーから個別の CJS ファイルを求められた場合に限りましょう。どちらを選ぶ場合も、package.json 側の設定については Node.js のパッケージ例が参考になります。

公開前のチェック

Rslib のビルドでは、2 つの Rsbuild プラグインを使って、npm に公開する前にパッケージングのバグを検出できます。rsbuild-plugin-publint は、package.json、ファイル構成、exports の設定に含まれるよくあるミスを探します。rsbuild-plugin-arethetypeswrong は、利用者が異なるモジュール解決設定を使っても型が正しく読み込まれるかをテストします。Rslib 1.0 のリリース告知では、CI が設定されている場合にのみ両方を実行する例が示されています。

// rslib.config.ts
import { defineConfig } from '@rslib/core';
import { pluginAreTheTypesWrong } from 'rsbuild-plugin-arethetypeswrong';
import { pluginPublint } from 'rsbuild-plugin-publint';

export default defineConfig({
  lib: [
    { format: 'esm', dts: true },
    { format: 'cjs', dts: true },
  ],
  plugins: [
    pluginPublint({ enable: Boolean(process.env.CI) }),
    pluginAreTheTypesWrong({ enable: Boolean(process.env.CI) }),
  ],
});

どちらも開発依存としてインストールします。こうすればローカルのビルドは高速なまま、CI で既に実行している rslib build ステップが公開前のゲートとして機能します。

大規模プロジェクトでは、型宣言ファイルの生成も高速になります。TypeScript 7 以降では、Rslib は自動的に tsgo を使用し、型チェックも引き続き行います。実験的な dts.isolated オプションはさらに高速ですが、型チェックを省略するため、別途型チェックのステップを併用してください。Rslib チームが Rsbuild リポジトリで実施したテストでは、型宣言ファイルの生成時間は TypeScript 6 で 9.7 秒、TypeScript 7 で 4.1 秒、isolated declarations で 2.3 秒でした。実際の効果はプロジェクトによって異なります。

コンポーネントライブラリとバンドルレスモード

コンポーネントライブラリにはバンドルレスモードを、SDK や CLI にはバンドルモードを使います。bundle: false にすると、Rslib はソースファイルを 1 つずつ個別の出力ファイルに変換し、ソースと同じフォルダ構成で出力します。これにより、ツリーシェイキングやオンデマンド読み込みを利用者側のバンドラーに任せられます。React、Vue、Svelte、Solid のコンパイラは Rsbuild プラグインとして組み込めます。また、create-rslib には各フレームワーク向けのテンプレートが用意されています。

// rslib.config.ts
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rslib/core';

export default defineConfig({
  bundle: false,
  output: { target: 'web' },
  plugins: [pluginReact()],
});

コンポーネントでは、通常どおりの方法でスタイルをインポートします。

import './button.css';
import styles from './button.module.css';

CSS Modules と PostCSS は追加設定なしで動作し、Rslib によるスタイルの出力方法はビルドモードに応じて変わります。Sass、Less、Stylus、Tailwind CSS は Rsbuild プラグインで追加します。Vue、Svelte、Solid のライブラリも、それぞれのプラグインを使えば同じ形の設定で構成できます。Rslib 1.0 のリリース告知に掲載された 10,000 個の React コンポーネントを使ったベンチマークでは、0.7.0 と比べて、1.0 はキャッシュなしのビルド時間を約 24.3%、キャッシュありのビルド時間を約 56.7% 短縮しました。出力サイズも、gzip 前で約 32.2%、gzip 後で 4.1% 小さくなっています。

Module Federation 向けの出力

Rslib では、format: 'mf' を使ってライブラリを Module Federation のリモートとしてビルドすることもできます。こうすると、複数のアプリケーションが npm からインストールするのではなく、実行時にライブラリを読み込むようになります。バージョン管理されたパッケージリリースよりも、共有コードを独立してデプロイできることが重要な場合に活用しましょう。

Rslib、tsup、Vite ライブラリモードの比較

Rslib、tsup、Vite ライブラリモードの違いは、速度よりも対象範囲にあります。tsup は esbuild をベースにしており、TypeScript のバンドルに特化しています。Vite ライブラリモードは、アプリケーション向けのビルドツールをライブラリのエントリに向けて使う仕組みです。Rslib は Rsbuild と Rspack の上にライブラリ専用として構築されています。出力フォーマット、バンドルモードとバンドルレスモード、型宣言ファイル、フレームワーク向けのコンパイル、アセット、公開前チェックのすべてを 1 つの設定でカバーします。アプリで既に Rsbuild や Rspack を使っているなら、ライブラリとプラグインや設定を共有できます。

Rspack がより広いエコシステムの中でどのような位置にあるかは、JavaScript バンドラーの現状を参照してください。汎用バンドラーとプラグインを組み合わせて独自のパイプラインを構築したい場合は、こちらの Rollup 入門ガイドから始めるとよいでしょう。移行については、Rslib に tsc からの移行ガイドがあり、クイックスタートには tsup 向けの同様のガイドへのリンクもあります。

まとめ

ライブラリを公開できる状態になるのは、フォーマット、型宣言ファイル、スタイル、exports マップのすべてが互いに整合しているときです。Rslib は、それらをまとめてビルドし、チェックします。まずは pure ESM で始め、利用者が必要とする場合にのみ lib 配列に CJS エントリを追加しましょう。そして次回のリリースまでに、CI で publint と arethetypeswrong を有効にしておきましょう。

よくある質問

依存パッケージを外部化せず、Rslib の出力にバンドルするにはどうすればよいですか?

lib エントリ内で output.autoExternal を設定し、バンドルしたい依存フィールドを無効にします。たとえば dependencies を false に設定します。Rslib v1 では lib.autoExternal が非推奨となり、Rsbuild の output.autoExternal が推奨されていますが、従来のオプションも引き続き動作します。自動外部化はバンドルモードでのみ適用されます。バンドルレスモードでは、依存パッケージがバンドルされることはありません。特定のパッケージだけを外部化したい場合は、output.externals にそれらを列挙します。

Rslib ですべての TypeScript 型宣言を 1 つのファイルにバンドルできますか?

はい。@microsoft/api-extractor を開発依存としてインストールし、lib エントリで dts.bundle を true に設定します。型宣言のバンドルは API Extractor が行い、dts.bundle のデフォルトは false です。外部化されていない直接の依存パッケージは、その型宣言が自動的にインライン化されます。dts.bundle.bundledPackages を設定するとこのデフォルトのリストが置き換えられるため、推移的な依存パッケージをバンドルする必要がある場合に便利です。

Rslib で CDN や script タグ向けの UMD バンドルをビルドできますか?

はい。lib エントリで format を 'umd' に設定し、lib.umdName にバンドルが公開するグローバル名(例:MyLibrary)を指定します。UMD 出力では自動外部化がデフォルトで無効になっているため、output.externals に列挙しない限り依存パッケージはバンドルされます。output.externals の各エントリでは、パッケージをブラウザのグローバル変数にマッピングします(例:react を React に)。UMD では lib.autoExtension がデフォルトで有効です。

CommonJS ビルド向けに .d.cts の型宣言ファイルを出力するにはどうすればよいですか?

各 lib エントリで dts.autoExtension を有効にします。これは lib.autoExtension と同じルールに従います。package.json で type が module に設定されている場合、ESM の型宣言は .d.ts、CJS の型宣言は .d.cts になります。このオプションはデフォルトで無効のため、ESM と CJS の出力が同じ .d.ts というファイル名になってしまうことがあります。require 条件の types は .d.cts ファイルを指すように設定してください。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.