v2.2.0
Package management

Package management

Melange はnpm レジストリ (opens in a new tab)opam リポジトリ (opens in a new tab)の両方からパッケージを利用できます。

  • Melange ライブラリとバインディング(コンパイル時の依存関係)については、Getting Started で説明するパッケージ管理の選択肢の 1 つを使用してください。このガイドでは、opam を使用していることを前提としています。
  • Melange バインディングが必要とする JavaScript パッケージ(実行時依存関係)については、npm (opens in a new tab)(またはその代替 (opens in a new tab))を使用してください。

opam と統合することで、Melange プロジェクトにネイティブツールチェーンを提供します。Opam は OCaml 言語用に設計されており、Melange プロジェクトが PPX (opens in a new tab)、コンパイラライブラリ、エディタ統合ソフトウェア、その他のツールにファーストクラスでアクセスできるようにします。

以下のセクションでは、opam を使用してアプリケーションの依存関係を定義する方法と、opam リポジトリにパッケージを公開する方法について詳しく説明します。ただし、このドキュメントは網羅的なものではなく、Melange 開発者にとって最も重要だと思われる部分のみを取り上げています。opam についてさらに詳しく知りたい場合は、opam のマニュアル (opens in a new tab)FAQ ページ (opens in a new tab)を参照してください。

Melange 開発者のための opam

Before diving into specifics about using opam, there are the two relevant differences between opam and npm that are worth mentioning.

opam の具体的な使い方に入る前に、opam と npm の違いについて 2 つ触れておきます。

1. 各パッケージ 1 バージョン

任意の時点で、どの opam スイッチも、最大でひとつのバージョンのパッケージしかインストールできません。これはフラットな依存関係グラフとして知られており、いくつかのパッケージマネージャ(Bower (opens in a new tab)のような)も同様のアプローチに従っています。

フラットな依存関係グラフは、例えば、同じプロジェクトにreason-react (opens in a new tab)の 2 つのバージョンをインストールすることが不可能であることを意味します。これにより、うっかり 2 つのバージョンの依存関係をインストールしてしまった場合の頭痛の種を避けることができます。また、特に  Melange にとっては、JavaScript バンドルの無駄を省き、ブラウザベースのアプリケーションのページロードを減らすのに役立ちます。

一方、プロジェクトの依存関係をより新しいバージョンにアップグレードするのは、厄介な ことになるかもしれません。1 つのパッケージは 1 つのバージョンしかインストールできないという制約があるため、依存関係の制約の間で競合が発生する可能性が高くなります。opam が解決策を見つけられない場合、これらの競合は手動で解決する必要があります。一般的には、競合する依存関係を更新して、新しいバージョンの Melange や一時的な依存関係と互換性を持たせることになります。

2. コンパイル言語のソースベースパッケージマネージャ

opam はパッケージのソースコードだけを配布し、コンパイルの段階は、パッケージが取得された後、パッケージを消費するときに実行されるビルド段階に任せます。OCaml のようなコンパイル言語のパッケージマネージャとして、opam はこのビルドステップをファーストクラスでサポートしています。すべてのパッケージは、どのようにビルドすべきかを opam に伝える必要があり、その方法は、パッケージの.opamファイル内のbuildフィールド (opens in a new tab)を使用することです。これは npm の使用方法とは異なります。npm レジストリにあるほとんどの公開パッケージは、ビルドステップに依存していません。

Melange はコンパイルの段階で OCaml パッケージ(PPX、リンター、インストルメンテーション、その他のビルド時パッケージのいずれか)に依存するため、OCaml プログラマーが慣れ親しんでいるネイティブツールチェインと統合されており、ライブラリ作者はビルド済みバージョンのパッケージを作成して配布する負担から解放されます。


それでは、Melange プロジェクトで opam を使用する際の最も一般的な操作を説明します。以下のガイドは、Louis (@khady (opens in a new tab))によるnpm/yarn ユーザーのための素晴らしい opam ガイド (opens in a new tab))に基づいています。

初期設定

まず最初にすべきことは、opam をインストールすることです。インストールに関する公式ドキュメントのページ (opens in a new tab)があります。ほとんどの場合、パッケージ・マネージャーから入手できます。そうでない場合は、各プラットフォーム用のバイナリが提供されています。

opam を使う前に必要な最初のステップ:

opam init -a

opam initコマンドのドキュメントにはこうあります:

init コマンドは、ローカルの「opam root」(デフォルトでは~/.opam/)を初期化し、opam のデータとパッケージを保持します。これは opam の通常の操作に必要なステップです。初期ソフトウェア・リポジトリが取得され、設定とオプションに従って初期「switch」をインストールすることもできます。これらはその後、opam switch と opam repository を使って設定することができます。

さらに、このコマンドは opam のシェル統合の一部をカスタマイズすることができます。

興味深いのはこの部分です:

  • opam のルートは~/.opam にあります
  • opam はシェルの統合を使い、私たちの生活を楽にしてくれる
  • opam は switch という概念を使う

switch は、npm の世界でいうnode_modulesフォルダに相当します。インストールされているすべてのパッケージが含まれています。local switch と global switch があります。同じように、プロジェクトにローカルなnode_modulesフォルダを持つことも、yarn globalnpm install -gを使ってグローバルな依存関係をインストールすることもできます。global switch は便利な場合もありますが、混乱を避けるため、使わないことを推奨します。

opam initを呼び出す際に-aオプションを省略すると、デフォルトの設定を変更できます。

最低限の app.opam ファイル

package.jsonに相当するのはapp.opamファイルで、appはパッケージ名です。同じディレクトリやプロジェクトに複数の opamファイルを持つことができます。

opam ファイルを操作する opam コマンドはありません。npm inityarn addのようなコマンドは opam には存在しないため、.opamファイルの更新は手作業で行う必要があります。

最小限の.opamファイルは以下のようになります:

opam-version: "2.0"
name: "my-app"
authors: "Louis"
homepage: "https://github.com/khady/example"
maintainer: "ex@ample.com"
dev-repo: "git+ssh://git@github.com:khady/example.git"
bug-reports: "https://github.com/khady/example/issues"
version: "0.1"
build: [
  [ "dune" "subst" ] {pinned}
  [ "dune" "build" "-p" name "-j" jobs ]
]
depends: [
  "dune" {build}
]

build: プロジェクトのビルドにのみ dune が必要であることを opam に伝えます。

パッケージのインストール

最初に必要なのは、現在のプロジェクト内の local switch です。switch がすでに存在するかどうかを確認するには、プロジェクトのルートに _opam ディレクトリを探すか、opam switch コマンドを使ってプロジェクト・フォルダにスイッチがすでに存在するかどうかを確認します。

存在しない場合は、次のようにして作成します:

opam switch create . 5.1.1 --deps-only

もしそれが存在すれば、プロジェクトの依存関係をインストールすることができます:

opam install . --deps-only

新しいパッケージを追加する

opam switch に新しいパッケージを追加するには、次のようにします:

opam install <package_name>

しかし、opam はインストール中にapp.opamファイルを変更しないので、dependsフィールドにパッケージ名を追加して、手作業で行う必要があります。

開発用パッケージのリンク

これは opam pin で実現できます。例えば、パッケージを GitHub の特定のコミットにピン留めする場合です:

opam pin add reason-react.dev https://github.com/reasonml/reason-react.git#61bfbfaf8c971dec5152bce7e528d30552c70bc5

ブランチ名も使用できます:

opam pin add reason-react.dev https://github.com/reasonml/reason-react.git#feature

すでに opam リポジトリで公開されているパッケージの場合、最新バージョンに固定する近道は--dev-repoフラグを使うことです。

opam pin add melange.dev --dev-repo

任意のパッケージのピン止めを解除するには、 opam unpin <パッケージ名> または opam pin remove <パッケージ名> を使ってください。

その他のオプションについては、公式ドキュメント (opens in a new tab)を参照してください。

パッケージのアップグレード

opam は、Debian でapt-getが行うように、opam リポジトリのローカルコピーを保存します。そのため、アップグレードを行う前に、このコピーを更新しておくと良いでしょう:

opam update

そして、インストールされているパッケージを最新バージョンにアップグレードするには、次のように実行します:

opam upgrade <package_name>

opam upgrade は、パッケージ名が与えられていない場合、local switch の全てのパッケージをアップグレードすることもできます。

Dev dependencies

with-dev-setupフィールド (opens in a new tab)を使って、開発時にのみ必要な依存関係を定義することができます。例えば:

depends: [
  "ocamlformat" {with-dev-setup}
]

これは、依存関係をインストールする際に--with-dev-setupフラグと組み合わせる必要があります(例:opam install --deps-only --with-dev-setup)。

Lock ファイル

Lock ファイルは、opam 世界ではあまり使われていませんが、次のように使うことができます:

  • opam lock を使用して、必要なときに Lock ファイルを生成する(基本的には opam installopam upgradeの後に)
  • opam install --deps-onlyopam switch create .コマンドのすべてに --locked を追加する

バインディングとパッケージ管理

既存の JavaScript パッケージにバインドする Melange ライブラリを作成する場合、Melange ライブラリのユーザーは、それらの JavaScript パッケージがインストールされていることを確認する必要があります。

これは、OCaml のシステムライブラリへのバインディングの仕組みと似ています。ocaml-mariadb (opens in a new tab)ocurl (opens in a new tab)などの例を参照してください。

このアプローチの利点は、JavaScript パッケージをバインディングの中で提供するのとは対照的に、バインディングのユーザーに、JavaScript パッケージのダウンロードやバンドル方法について完全な柔軟性を与えることです。

Melange では、check-npm-deps (opens in a new tab) opam プラグインを使用して、opam パッケージからopamファイル内の npm パッケージへの依存関係を定義する方法を提供しています。

このプラグインを使用すると、ライブラリの作者は、opam depextsフィールド内に npm 形式の制約を含めることができます。例えば、reason-reactの opam ファイルには、次のようなセクションを含めることができます:

depexts: [
  ["react" "react-dom"] {npm-version = "^17.0.0 || ^18.0.0"}
]

これはreason-reactのバージョンが ReactJS のバージョン 17 と 18 と互換性があることを示しています。

Melange バインディングのユーザーは、check-npm-depsプラグインを使用することで、スイッチにインストールされた opam パッケージで定義された制約が、node_modulesにインストールされたパッケージで満たされていることを確認できます。このためには、プラグインをインストールするだけで使用できます:

opam install opam-check-npm-deps

そして、opam switch とnode_modulesフォルダが存在するプロジェクトのルート・フォルダから呼び出します:

opam-check-npm-deps

Melange 互換パッケージの検索と使用

opam packages

Melange のパッケージは通常opamで入手できます。パッケージ検索は opam CLI で opam search <package-name> (例: opam search reason-react) によって行えます。opam install <パッケージ名> を実行すると、opam パッケージをダウンロードしてビルドし、switch にインストールすることができます。opam は依存関係を自動的に<your-project>.opamファイルに追加しないので、手動で追加する必要があることを覚えておいてください:

...
depends: [
  ...
  "reason-react" {>= "0.11.0"}
]

インストールされたパッケージのライブラリを使用するには、dune ファイルの libraries フィールドにライブラリ名を追加します。例えば、プロジェクトの構成が以下のようになっている場合:

project_name/
├── _opam
├── src
   ├── dune
   ├── ReactComponent1.ml
   ├── ReactComponent2.ml
   └── lib
        ├── dune
        └── data.ml
├── dune-project
├── dune
├── package.json
└── ...

であれば、reason-reactsrcフォルダー下のduneファイルに追加する必要があります:

(melange.emit
 (target output)
 (alias react)
 (libraries lib reason-react)
 (preprocess
  (pps reason-react-ppx))
 (module_systems es6))

一部のライブラリは、付属の PPX で処理された後でないと動作しません、例えば、reason-reactreason-react-ppxによる前処理を必要とします。 これらの前処理は、同じパッケージの一部としてライブラリと一緒にインストールされることもあれば、 別のパッケージの一部であることもあり、その場合は別々にインストールする必要があります。

公開されていない opam パッケージ

まだ公開されていない opam パッケージは、opam pin コマンドでインストールできます。 例えば、 opam pin add melange-fetch.dev git+https://github.com/melange-community/melange-fetch とすると、melange-fetch が Git リポジトリから取得され、あなたの switch にインストールされます。 そうすると、<your-project>.opam ファイルが 2 カ所更新されるはずです:

...
depends: [
  ...
  "melange-fetch" {dev}
]
pin-depends: [
  [ "melange-fetch.dev" "git+https://github.com/melange-community/melange-fetch" ]
]

インストールが完了したら、パッケージに含まれているライブラリを dune ファイルに追加することができます:

(melange.emit
 (target output)
 (alias react)
 (libraries lib reason-react melange-fetch)
 (preprocess
  (pps reason-react-ppx))
 (module_systems es6))

npm packages

Melange 互換のパッケージは npm に多数あります。 npm には、bs-jsonのような、古いが今でも役に立つ互換性のある BuckleScript ライブラリが多数あります。 npm install @glennsl/bs-jsonを実行して依存関係をローカルに追加し、プロジェクトのルートにあるpackage.jsonファイルに記録します。

Dune は新しくインストールされたパッケージを認識する必要があります。 このような場合、subdir (opens in a new tab) stanza が便利です:

(subdir
 node_modules
 (dirs @glennsl)
 (subdir
  @glennsl/bs-json/src
  (library
   (name bs_json)
   (wrapped false)
   (modes melange))))

dune ファイルに (dirs :standard \ node_modules) という行が含まれている場合、 Dune が node_modules フォルダ下の新しい Melange ソースを処理できるように、この行を削除する必要があります。

上記のプロジェクト構造では、src/lib フォルダーの下に data.mldata.re) というファイルがあります。 このフォルダでbs-jsonライブラリを使用する場合、次のようになります。

data.reファイルからbs-jsonライブラリを使いたい場合は、同じフォルダにあるduneファイル、つまりsrc/lib/duneにライブラリ名を追加する必要があります:

(library
 (name data)
 (libraries bs_json)
 (modes melange))

ライブラリ bs-jsonsubdir stanza で bs_json と定義され、dune ファイルでは bs_json として参照されていることに注意してください。 これは、Dune でラップされたライブラリは、そのライブラリにちなんだ名前のトップレベルモジュールしか公開しない (opens in a new tab)ため、 ライブラリ名が有効なモジュール名でなければならないためです。このため、「-」のような文字を含むライブラリ名は無効です。

この方法で、利用したいパッケージごとに新しいsubdir stanza を追加することができます。 複数の npm パッケージを使用する大規模な例については、この dune ファイル (opens in a new tab)を参照してください。