NPMとBrowserifyでScala.jsを使用する

公開: 2022-03-11

Scala言語から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をインストールしなくてもこれらすべてを実行できます。

BrowserifyとScala.js

Browserifyの魔法は、Javaの世界でもうまく機能します。
つぶやき

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ライブラリが機能していません

また、他のライブラリでは、他の理由でこの苛立たしい経験に直面する可能性があります。 簡単に言えば、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から依存関係をインストールし、Br​​owserifyを呼び出してブラウザーの依存関係を収集し、それらを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を使用すると、サーバーとクライアント間でコードを共有できます

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.jsondependenciesブロックを次のように更新します。

 "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を使用してもそれほど多くの違いに気付くことはありませんが、それでも多くの利点があります。 秘訣は、それぞれの世界を最大限に活用し、それらがうまく連携することを保証することです。

関連: Scalaを学ぶ必要があるのはなぜですか?