NPMとBrowserifyでScala.jsを使用する
公開: 2022-03-11Scala言語からJavaScriptへのコンパイラーであるScala.jsを使用する場合、Scala.jsの標準的な依存関係管理は、現代のJavaScriptの世界では制限が多すぎることに気付くかもしれません。 Scala.jsはWebJarsを使用して依存関係を管理し、JavaScript開発者はNPMを使用して依存関係を管理します。 NPMによって生成される依存関係はサーバー側であるため、通常、BrowserifyまたはWebpackを使用してブラウザーコードを生成する追加の手順が必要です。
この投稿では、Scala.jsをNPMで利用可能な多数のJavaScriptモジュールと統合する方法について説明します。 ここで説明する手法の実際の例については、このGitHubリポジトリを確認できます。 この例を使用し、この投稿を読むことで、NPMを使用してJavaScriptライブラリを収集し、Browserifyを使用してバンドルを作成し、その結果を独自のScala.jsプロジェクトで使用できるようになります。 すべてがSBTによって管理されているため、Node.jsをインストールしなくてもこれらすべてを実行できます。
Scala.jsの依存関係の管理
今日、JavaScriptにコンパイルされる言語でアプリケーションを作成することは、非常に一般的な方法になりつつあります。 ますます多くの人々が、CoffeeScriptやTypeScriptなどの拡張JavaScript言語、または今日ES6を使用できるようにするBabelなどのトランスパイラーに移行しています。 同時に、Google Web Toolkit(JavaからJavaScriptへのコンパイラ)は主にエンタープライズアプリケーションに使用されます。 これらの理由から、Scala開発者として、私はもはやScala.jsを使用することを奇妙な選択だとは考えていません。 コンパイラーは高速で、生成されたコードは効率的であり、全体として、フロントエンドとバックエンドの両方で同じ言語を使用する方法にすぎません。
とはいえ、JavaScriptの世界でScalaツールを使用することは、まだ100%自然ではありません。 JavaScriptエコシステムからScalaエコシステムへのギャップを埋める必要がある場合があります。 言語としてのScala.jsは、JavaScriptとの優れた相互運用性を備えています。 Scala.jsはScala言語からJavaScriptへのコンパイラーであるため、Scalaコードを既存のJavaScriptコードにインターフェースするのは非常に簡単です。 最も重要なことは、Scala.jsを使用すると、型付きのインターフェース(またはファサード)を作成して、型なしのJavaScriptライブラリーにアクセスできるようになります(TypeScriptで行う場合と同様)。 Java、Scala、さらにはHaskellのような強く型付けされた言語に慣れている開発者にとって、JavaScriptはあまりにも緩く型付けされています。 あなたがそのような開発者である場合、おそらく主な理由は、Scala.jsを使用したいのは、型なし言語の上に(強く)型付き言語を取得することだからです。
SBTに基づいており、まだいくらか開いたままになっている標準のScala.jsツールチェーンの問題は次のとおりです。プロジェクトに追加のJavaScriptライブラリなどの依存関係を含めるにはどうすればよいですか。 SBTはWebJarで標準化されているため、依存関係を管理するためにWebJarを使用することになっています。 残念ながら、私の経験では、それは不十分であることがわかりました。
WebJarsの問題
前述のように、JavaScriptの依存関係を取得する標準のScala.jsの方法は、WebJarsに基づいています。 結局のところ、ScalaはJVM言語です。 Scala.jsは主にプログラムの構築にSBTを使用し、最後にSBTはJARの依存関係の管理に優れています。
このため、WebJar形式は、JVMの世界でJavaScriptの依存関係をインポートするために正確に定義されました。 WebJarはWebアセットを含むJARファイルであり、単純なJARファイルにはコンパイルされたJavaクラスのみが含まれます。 したがって、Scala.jsの考え方は、ScalaがJAR依存関係を追加するのと同様に、WebJar依存関係を追加するだけでJavaScript依存関係をインポートする必要があるということです。
それが機能しないことを除いて、素晴らしいアイデア
Webjarの最大の問題は、ランダムなJavaScriptライブラリの任意のバージョンがWebJarとして利用できることはめったにないことです。 同時に、JavaScriptライブラリの大部分はNPMモジュールとして利用できます。 ただし、自動化されたnpm-to-webjar
ジャーを使用して、NPMとWebJarsの間にブリッジが想定されています。 そこで、NPMモジュールとして利用できるライブラリをインポートしようとしました。 私の場合、それはVoxelJSでした。これは、WebページでMinecraftのような世界を構築するためのライブラリです。 ライブラリをWebJarとしてリクエストしようとしましたが、記述子にライセンスフィールドがないという理由だけで、ブリッジが失敗しました。
また、他のライブラリでは、他の理由でこの苛立たしい経験に直面する可能性があります。 簡単に言えば、WebJarとして実際に各ライブラリにアクセスすることはできないようです。 JavaScriptライブラリにアクセスするためにWebJarsを使用する必要があるという要件は、制限が多すぎるようです。
NPMとBrowserifyを入力してください
すでに指摘したように、ほとんどのJavaScriptライブラリの標準パッケージ形式は、Node.jsのすべてのバージョンに含まれているNode Package Manager(NPM)です。 NPMを使用すると、利用可能なほぼすべてのJavaScriptライブラリに簡単にアクセスできます。
NPMはノードパッケージマネージャーであることに注意してください。 NodeはV8JavaScriptエンジンのサーバー側の実装であり、Node.jsによってサーバー側で使用されるパッケージをインストールします。 現状では、NPMはブラウザには役に立ちません。 ただし、NPMパッケージとしても配布されているユビキタスなBrowserifyツールのおかげで、NPMの有用性はブラウザアプリケーションで機能するようにしばらく拡張されています。
Browserifyは、簡単に言えば、ブラウザーのパッケージャーです。 ブラウザアプリケーションで使用できる「バンドル」を生成するNPMモジュールを収集します。 多くのJavaScript開発者はこのように機能します。つまり、NPMを使用してパッケージを管理し、その後、それらをBrowserifyしてWebアプリケーションで使用します。 Webpackのように、同じように機能する他のツールがあることに注意してください。
SBTからNPMへのギャップを埋める
今説明した理由から、私が望んでいたのは、NPMを使用してWebから依存関係をインストールし、Browserifyを呼び出してブラウザーの依存関係を収集し、それらをScala.jsで使用する方法でした。 タスクは私が予想したよりも少し複雑であることが判明しましたが、それでも可能です。 確かに、私はその仕事をしました、そして私はそれをここで説明しています。
簡単にするために、SBT内で実行できることもわかったので、Browserifyを選択しました。 私はWebpackを試したことがありませんが、それも可能だと思います。 幸いなことに、私は真空状態で始める必要はありませんでした。 すでにいくつかの部品があります:
- SBTはすでにNPMをサポートしています。 Play Framework用に開発された
sbt-web
プラグインは、NPMの依存関係をインストールできます。 - SBTはJavaScriptの実行をサポートしています。
sbt-jsengine
プラグインのおかげで、Node自体をインストールせずにNodeツールを実行できます。 - Scala.jsは生成されたバンドルを使用できます。 Scala.jsには、アプリケーションに任意のJavaScriptライブラリを含めるための連結関数があります。
これらの機能を使用して、NPMの依存関係をダウンロードしてBrowserifyを呼び出し、 bundle.js
ファイルを生成できるSBTタスクを作成しました。 プロシージャをコンパイルチェーンに統合しようとしましたが、すべてを自動的に実行できますが、コンパイルごとにバンドルを処理する必要があるのは遅すぎます。 また、依存関係を常に変更するわけではありません。 したがって、依存関係を変更するときに、時々手動でバンドルを作成する必要があるのは合理的です。
したがって、私の解決策はサブプロジェクトを構築することでした。 このサブプロジェクトは、JavaScriptライブラリをダウンロードしてNPMとBrowserifyでパッケージ化します。 次に、依存関係の収集を実行するためのbundle
コマンドを追加しました。 結果のバンドルは、Scala.jsアプリケーションで使用されるリソースに追加されます。
JavaScriptの依存関係を変更するときはいつでも、この「バンドル」を手動で実行することになっています。 前述のように、コンパイルチェーンでは自動化されていません。
Bundlerの使用方法
私の例を使用したい場合は、次のようにします。まず、通常のGitコマンドでリポジトリをチェックアウトします。
git clone https://github.com/sciabarra/scalajs-browserify/
次に、Scala.jsプロジェクトのbundle
フォルダーをコピーします。 バンドル用のサブプロジェクトです。 メインプロジェクトに接続するには、 build.sbt
ファイルに次の行を追加する必要があります。
val bundle = project.in(file("bundle")) jsDependencies += ProvidedJS / "bundle.js" addCommandAlias("bundle", "bundle/bundle")
また、 project/plugins.sbt
ファイルに次の行を追加する必要があります。
addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")
完了すると、依存関係を収集するために使用できる新しいコマンド、 bundle
ができます。 src/main/resources
フォルダーの下にファイルbundle.js
が生成されます。
バンドルはScala.jsアプリケーションにどのように含まれていますか?
今説明したbundle
コマンドは、NPMとの依存関係を収集してから、 bundle.js
を作成します。 fastOptJS
またはfullOptJS
コマンドを実行すると、ScalaJSはmyproject-jsdeps.js
を作成します。これには、JavaScriptの依存関係として指定したすべてのリソース、つまりbundle.js
も含まれます。 バンドルされた依存関係をアプリケーションに含めるには、次のインクルージョンを使用する必要があります。
<script src="target/scala-2.11/myproject-jsdeps.js"></script> <script src="target/scala-2.11/myproject-fastopt.js"></script> <script src="target/scala-2.11/myproject-launcher.js"></script>
これで、バンドルがmyproject-jsdeps.js
の一部として利用できるようになりました。 バンドルの準備が整い、タスク(依存関係のインポートとブラウザーへのエクスポート)がある程度完了しました。 次のステップはJavaScriptライブラリを使用することです。これは別の問題であるScala.jsコーディングの問題です。 完全を期すために、Scala.jsでバンドルを使用する方法と、インポートしたライブラリを使用するためのファサードを作成する方法について説明します。

Scala.jsアプリケーションでの汎用JavaScriptライブラリの使用
要約すると、NPMとBrowserifyを使用してバンドルを作成し、そのバンドルをScala.jsに含める方法を見てきました。 しかし、どうすれば一般的なJavaScriptライブラリを使用できますか?
投稿の残りの部分で詳細に説明する完全なプロセスは次のとおりです。
- NPMからライブラリを選択し、それらを
bundle/package.json
に含めます。 - これらを
require
を使用して、bundle/lib.js
のライブラリモジュールファイルにロードします。 - Scala.jsのファサードを記述して、Scala.jsの
Bundle
オブジェクトを解釈します。 - 最後に、新しく入力したライブラリを使用してアプリケーションをコーディングします。
依存関係の追加
NPMを使用するには、標準であるpackage.json
ファイルに依存関係を含める必要があります。
したがって、jQueryとLoadashのような2つの有名なライブラリを使用するとします。 適切なモジュールを備えたScala.jsの依存関係として利用可能なjQueryの優れたラッパーがすでに存在し、LodashはScalaの世界では役に立たないため、この例はデモンストレーションのみを目的としています。 それでも、それは良い例だと思いますが、例としてのみ取り上げてください。
したがって、 npmjs.com
Webサイトにアクセスして、使用するライブラリを見つけ、バージョンも選択します。 jquery-browserify
バージョン13.0.0とlodash
バージョン4.3.0を選択したとします。 次に、 packages.json
のdependencies
ブロックを次のように更新します。
"dependencies": { "browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0" }
バンドルを生成するために必要なので、常にbrowserify
を保持します。 ただし、バンドルに含める必要はありません。
NPMがインストールされている場合は、 bundle
ディレクトリから入力するだけでよいことに注意してください。
npm install --save jquery-browserify lodash
また、 package.json
も更新されます。 NPMがインストールされていない場合でも、心配する必要はありません。 SBTは、JavaバージョンのNode.jsとNPMをインストールし、必要なJARをダウンロードして実行します。 これらはすべて、SBTからbundle
コマンドを実行するときに管理されます。
ライブラリのエクスポート
これで、パッケージをダウンロードする方法がわかりました。 次のステップは、それらをバンドルに集めてアプリケーションの残りの部分で利用できるようにするようにBrowserifyに指示することです。
Browserifyはrequire
の収集者であり、ブラウザーのNode.jsの動作をエミュレートします。つまり、ライブラリをインポートするrequire
がどこかにある必要があります。 これらのライブラリをScala.jsにエクスポートする必要があるため、バンドルはBundle
という名前のトップレベルのJavaScriptオブジェクトも生成しています。 したがって、必要なのは、JavaScriptオブジェクトをエクスポートするlib.js
を編集し、すべてのライブラリをこのオブジェクトのフィールドとして要求することです。
Scala.js jQueryおよびLodashライブラリにエクスポートする場合、コードでは次のことを意味します。
module.exports = { "jquery": require("jquery-browserify"), "lodash": require("lodash") }
これで、コマンドbundle
を実行するだけで、ライブラリがダウンロードされ、収集されてバンドルに配置され、Scala.jsアプリケーションで使用できるようになります。
バンドルへのアクセス
ここのところ:
- バンドルサブプロジェクトをScala.jsプロジェクトにインストールし、正しく構成しました。
- 必要なライブラリについては、
package.json
に追加しました。 -
lib.js
でそれらを必要としました。 -
bundle
コマンドを実行しました。
その結果、 Bundle
のトップレベルのJavaScriptオブジェクトが作成され、このオブジェクトのフィールドとして使用できるライブラリのすべてのエントリポイントが提供されます。
これで、Scala.jsで使用する準備が整いました。 最も単純なケースでは、次のようなことを実行してライブラリにアクセスできます。
@js.native object Bundle extends js.Object { def jquery : js.Any = js.native def lodash: js.Any = js.native }
このコードを使用すると、Scala.jsからライブラリにアクセスできます。 ただし、ライブラリはまだ型指定されていないため、Scala.jsで行うべき方法ではありません。 代わりに、型指定された「ファサード」またはラッパーを作成する必要があります。これにより、元々型指定されていないJavaScriptライブラリを型付きのスカリーな方法で使用できます。
Scala.jsでラップしたい特定のJavaScriptライブラリに依存するため、ファサードの書き方をここで説明することはできません。 議論を完了するために、例だけを示します。 詳細については、Scala.jsの公式ドキュメントを確認してください。 また、利用可能なファサードのリストを参照して、インスピレーションを得るためにソースコードを読むことができます。
これまで、任意の、まだマップされていないライブラリのプロセスについて説明してきました。 記事の残りの部分では、すでに利用可能なファサードを備えたライブラリについて言及しているため、説明は単なる例です。
Scala.jsでJavaScriptAPIをラップする
JavaScriptでrequire
を使用すると、さまざまなものになり得るオブジェクトが得られます。 関数またはオブジェクトにすることができます。 文字列またはブール値にすることもできます。その場合、 require
は副作用に対してのみ呼び出されます。
私が行っている例では、 jquery
は関数であり、追加のメソッドを提供するオブジェクトを返します。 典型的な使用法はjquery(<selector>).<method>
です。 jquery
では$.<method>
の使用も許可されているため、これは単純化されていますが、この単純化された例では、これらすべてのケースをカバーするつもりはありません。 一般に、複雑なJavaScriptライブラリの場合、すべてのAPIを静的Scalaタイプに簡単にマッピングできるわけではないことに注意してください。 JavaScriptオブジェクトへの動的な(型指定されていない)インターフェースを提供するjs.Dynamic
に頼る必要があるかもしれません。
したがって、より一般的なユースケースをキャプチャするために、 Bundle
オブジェクトjquery
で定義しました。
def jquery : js.Function1[js.Any, Jquery] = js.native
この関数はjQueryオブジェクトを返します。 私の場合、トレイトのインスタンスは単一のメソッドで定義されています(簡略化すると、独自のメソッドを追加できます)。
@js.native trait Jquery extends js.Object { def text(arg: js.Any): Jquery = js.native }
Lodashライブラリの場合、ライブラリ全体をJavaScriptオブジェクトとしてモデル化します。これは、直接呼び出すことができる関数のコレクションであるためです。
def lodash: Lodash = js.native
lodash
の特性が次のようになっている場合(簡略化すると、ここにメソッドを追加できます):
@js.native trait Lodash extends js.Object { def camelCase(arg: js.Any): String = js.native }
これらの定義を使用して、最終的に、基盤となるjQueryライブラリとLodashライブラリを使用してScalaコードを記述できます。どちらも、NPMから読み込まれ、参照されます。
object Main extends JSApp { def main(): Unit = { import Bundle._ jquery("#title").text(lodash.camelCase("This is a test")) } }
ここで完全な例を確認できます。
結論
私はScala開発者ですが、サーバーとクライアントの両方に同じ言語を使用できるため、Scala.jsを発見したときに興奮しました。 ScalaはJavaよりもJavaScriptに似ているため、Scala.jsはブラウザーで非常に簡単で自然です。 さらに、Scalaの強力な機能を、ライブラリ、マクロ、強力なIDEおよびビルドツールの豊富なコレクションとして使用することもできます。 その他の重要な利点は、サーバーとクライアント間でコードを共有できることです。 この機能が役立つ場合はたくさんあります。 Coffeescript、Babel、TypescriptなどのJavascript用のトランスパイラーを使用する場合、Scala.jsを使用してもそれほど多くの違いに気付くことはありませんが、それでも多くの利点があります。 秘訣は、それぞれの世界を最大限に活用し、それらがうまく連携することを保証することです。