ClojureScript

依存関係

このガイドでは、ClojureScript 1.10.238 以降が必要であり、クイックスタートに関する知識があることを前提としています。.

すべての非自明なClojureScriptアプリケーションは、最終的に他の人が作成したコードを利用する必要があります。ClojureScript開発者はもちろん、ClojureScriptを使用して作成されたコードを活用できます。ただし、ClojureScript開発者は、それがClojureScriptを念頭に置いて記述されたかどうかに関係なく、任意のJavaScriptコードを利用することもできます。

このガイドでは、クイックスタートガイドを完了しており、そこで紹介された依存関係を備えていることを前提としています。

JavaScriptコードの利用

任意のJavaScriptコードを利用できますが、そのコードを含めるための最適なメカニズムは常に同じではありません。次のセクションでは、サードパーティのJavaScriptコードを利用するためのさまざまなオプションについて説明します。

Closureライブラリ

最も簡単に利用できるJavaScriptコードは、GoogleのClosureライブラリ(GCL)のコードであり、これはClojureScriptに自動的にバンドルされています。GCLは、ClojureScriptコード自体と同様に名前空間に整理されたJavaScriptコードの大規模なコレクションです。したがって、ClojureScriptの名前空間と同じ方法でGCLから名前空間をrequireできます。次の例は、基本的な使用法を示しています。

(ns hello-world.core
  (:require [goog.dom :as dom]
            [goog.dom.classes :as classes]
            [goog.events :as events])
  (:import [goog Timer]))

(let [element (dom/createDom "div" "some-class" "Hello, World!")]
  (classes/enable element "another-class" true)
  (-> (dom/getDocument)
    .-body
    (dom/appendChild element))
  (doto (Timer. 1000)
    (events/listen "tick" #(.warn js/console "still here!"))
    (.start)))

Closureライブラリの:import:requireの違いについては、このブログ記事を参照してください。

要するに、Closureのクラスとenumには:importを使用し、その他すべてには:requireを使用します。

外部JavaScriptライブラリ

GCLに必要な機能が含まれていない場合、またはサードパーティのJavaScriptライブラリを利用したい場合は、コードを直接使用できます。

yayQueryという名前の高度なJavaScriptライブラリを使用したい場合を考えてみましょう。JavaScriptライブラリを利用するには、通常どおりJavaScriptを参照するだけです。ファイルが外部でロードされるかインラインでロードされるかは関係なく、どちらも実行時に同じように適用されます。簡単にするために、このライブラリをインラインで定義します。

<script type="text/javascript">
    yayQuery = function() {
        var yay = {};
        yay.sayHello = function(message) {
            console.log(message);
        }
        yay.getMessage = function() {
            return 'Hello, world!';
        }
       return yay;
    };
</script>

ClojureScriptからこのライブラリを使用するには、シンボルを直接参照するだけです。{:optimizations :none}を使用して次のコードをビルドすると、すべてが正常に機能し、JavaScriptコンソールにメッセージが表示されます。

(ns hello-world.core)

(let [yay (js/yayQuery)]
  (.sayHello yay (.getMessage yay)))

これは最適化されていないコードでは問題なく動作しますが、高度な最適化を使用すると失敗します。同じコードを{:optimizations :advanced}でコンパイルして、ブラウザをリロードしてみてください。次のようなエラーメッセージが表示されます(正確には以下のようではない場合があります)。

Uncaught TypeError: sa.B is not a function

なぜこのようなことが起こったのでしょうか?高度な最適化を使用すると、Google Closure Compilerはシンボル名を変更します。ほとんどの場合、同じシンボルのすべてのインスタンスの名前が一貫して変更されるため、これは問題にはなりません。ただし、この場合、外部シンボル(JavaScriptコードの名前)はコンパイルユニットとは分離されているため、名前が一致しなくなります。幸いなことに、高度なコンパイルのすべての利点を失うことなく、この問題を解決するためのオプションがあります。

externsの使用

ソースコードをまったく変更せずにコンパイルを修正するには、externsファイルを追加できます。externsファイルは、特定のライブラリのシンボル名を定義し、Google Closure Compilerが名前を変更してはならないシンボルを決定するために使用されます。これがyayQueryライブラリの最小限のexternsファイルです。

var yayQuery = function() {}
yayQuery.sayHello = function(message) {}
yayQuery.getMessage = function() {}

このファイルがyayquery-externs.jsという名前であると仮定すると、build.ednファイルで次のように参照できます。

{:output-to "out/main.js"
 :externs ["yayquery-externs.js"]
 :optimizations :advanced})

:externsベクターで参照されるすべてのパスはクラスパス上にある必要があることを理解することが重要です。たとえば、上記のexternsファイルをresourcesディレクトリの下に配置したとします。次に、スタンドアロンのClojureScript JARを使用する場合は、次のようにビルドスクリプトを起動する必要があります。

clj -M -m cljs.main -co build.edn -c

externsファイルを参照して再コンパイルすると、コードは修正なしで再び動作するはずです。多くの一般的なJavaScriptライブラリでは、ライブラリの作成者またはより広いコミュニティによって作成済みのexternsファイルを見つけることができる場合があります。これらのファイルは、ClojureScriptを使用していない開発者を含め、Google Closure Compilerを利用するすべての開発者にとって役立ちます。

文字列名の使用

少数のJavaScriptシンボルのみを参照する単純なケースでは、ソースコードを変更して文字列名でコードを参照することもできます。Google Closure Compilerは文字列の名前を変更することはないため、このスタイルはexternsファイルを作成する必要なしに機能します。次のコードは、externsなしでも高度なコンパイルモードで動作します。

(let [yay ((goog.object.get js/window "yayQuery"))]
  ((goog.object.get yay "sayHello") ((goog.object.get yay "getMessage"))))

注意深い読者は、上記の例で、失敗した例でjs/yayQueryで行ったのと同様にjs/windowを参照していることに気付くでしょう。Google Closure Compilerには、ブラウザAPI用のexternsが多数付属しているため、この場合は機能します。これらはデフォルトで有効になっています。

JavaScriptコードのバンドル

コンテンツ配信の効率を最大化するために、コンパイルされたClojureScriptコードと一緒にJavaScriptコードをバンドルできます。

Google Closure Compiler互換コード

外部JavaScriptコードがGoogle Closure Compilerと互換性があるように記述されており、goog.provideを使用して名前空間を公開している場合、最も効率的なインクルード方法は:libsを使用してバンドルすることです。このバンドルメカニズムは、高度なモードのコンパイルを最大限に活用し、外部JavaScriptライブラリのシンボル名を変更し、デッドコードを削除します。前の例のyayQueryライブラリを以下のように適応させてみましょう。

goog.provide('yq');

yq.debugMessage = 'Dead Code';

yq.yayQuery = function() {
    var yay = {};
    yay.sayHello = function(message) {
        console.log(message);
    };
    yay.getMessage = function() {
        return 'Hello, world!';
    };
    return yay;
};

このコードは、前のインラインバージョンとほぼ同じですが、goog.provideを使用して公開された「名前空間」内にパッケージ化されています。このライブラリは、ClojureScriptで簡単に参照できます。

(ns hello-world.core
  (:require [yq]))

(let [yay (yq/yayQuery)]
  (.sayHello yay (.getMessage yay)))

バンドルされた出力をビルドするには、次のコマンドを使用します。

clj -M -m cljs.main -co build.edn -O advanced -c

このコードは高度なコンパイルと互換性があるため、externsを作成する必要はありません。コンパイルされた出力を確認すると、関数名が変更され、参照されていないdebugMessageがGoogle Closure Compilerによって完全に削除されていることがわかります。

外部JavaScriptをバンドルするための非常に効率的な方法ですが、ほとんどの一般的なライブラリはこのアプローチと互換性がありません。

「外部」JavaScriptコードのバンドル

バンドルしたいコードがGoogle Closure Compilerとの互換性を念頭に置いて作成されていない場合は、外部ライブラリとして含めることができます。外部ライブラリは最終的な出力に含まれますが、高度なコンパイルは通過しません。goog.provideを含まないyayQueryのバージョンについて考えてみましょう。

yayQuery = function() {
    var yay = {};
    yay.sayHello = function(message) {
        console.log(message);
    };
    yay.getMessage = function() {
        return 'Hello, world!';
    };
    return yay;
};

ClojureScriptで外部ライブラリのコードを使用することは、<script>タグを介してページに直接含められたコードを使用することとよく似ていますが、1つの重要な違いがあります。

(ns hello-world.core
  (:require [yq]))

(let [yay (js/yayQuery)]
  (.sayHello yay (.getMessage yay)))

ns宣言に:requireが存在することに注意してください。これはyqという名前の「名前空間」を参照していますが、yayQueryファイルに対応するgoog.provideはありません。外部ライブラリの場合、「名前空間」はビルド構成で提供されます。:providesキーの名前が:requireと一致し、参照されているライブラリ間で一意である限り、任意の名前を付けることができます。

{:output-to "out/main.js"
 :externs ["yayquery-externs.js"]
 :foreign-libs [{:file "yayquery.js"
                 :provides ["yq"]}]}

ここでexternsファイルを再導入したことに注意してください。外部ライブラリはバンドルされていますが、それ以外の場合は、スクリプトが外部に含まれていた場合とまったく同じように参照する必要があります。

CLJSJS

前のセクションでは、外部JavaScriptコードと統合するためのさまざまな方法について説明しました。ライブラリを統合するための最適な方法を見つけるのは、特にexternsを調達する必要がある場合は、難しい場合があります。幸いなことに、最も一般的なJavaScriptライブラリの多くには、より簡単な方法があります。CLJSJSプロジェクトは、ClojureScriptコンパイラで直接サポートされている方法で、外部JavaScriptライブラリを自動的にパッケージ化します。特定のコンテキスト(たとえば、高度な最適化を使用する場合は、縮小されたライブラリを含む)でライブラリの最適なバージョンを自動的にパッケージ化し、適切なexternsを自動的に含めます。

愛用していたyayQueryライブラリを使い果たし、代わりにjQueryを使用したいとしましょう。これは、事前にパッケージ化されている多くの一般的なライブラリの1つです。以下のようにコピーを取得できます。

curl -O https://clojars.org/repo/cljsjs/jquery/1.9.0-0/jquery-1.9.0-0.jar

ダウンロードしたJARファイル(unzip jquery-1.9.0-0.jar deps.cljs)の中を覗いてみると、バンドルされたdeps.cljsファイルの内容が表示されます。

{:foreign-libs
 [{:file "cljsjs/development/jquery.inc.js",
   :file-min "cljsjs/production/jquery.min.inc.js",
   :provides ["cljsjs.jquery"]}],
 :externs ["cljsjs/common/jquery.ext.js"]}

前のセクションに従った場合、この時点でこれはすべて非常に明確になるはずです。:providesデータは、このコードを参照するために必要なすべてを教えてくれます。

(ns hello-world.core
  (:require [cljsjs.jquery]))

(.text (js/$ "body") "Hello, World!")

この場合のビルドファイルは非常に簡単です。ライブラリ参照は、スクリプトを呼び出すときに参照するJARに完全に含まれているためです。

{:output-to "out/main.js"}

以下のようにコードをコンパイルします(クラスパスにJARを追加することに注意してください)。ブラウザをロードすると、メッセージが表示されるはずです。

clj -M -m cljs.main -co build.edn -O advanced -c

(推移的な)CLJSJS依存関係をライブラリの別のビルドに置き換える

CLJSJSライブラリへの推移的な依存関係があるが、依存関係を手動で含めるか、カスタムビルドを使用したい場合があります。その場合は、次の2つのことを行う必要があります。(1):exclusionsで依存関係を除外し、(2)ビルドが中断しないように、cljsjs名前で空の名前空間を作成します。

たとえば、omcljsjs/reactに依存しています。カスタムビルドを含めるには、次が必要です。

;; project.cljs
;; ...
:dependencies [[org.omcljs/om "0.9.0" :exclusions [cljsjs/react]] ;; ...
;; src/cljsjs/react.cljs
(ns cljsjs.react)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
<script src="resources/public/js/compiled/your_cljs_code.js" type="text/javascript"></script>

ClojureScriptコードの利用

任意のJavaScriptライブラリを利用できる機能により、ClojureScriptはJavaScriptアプリケーションを作成するための非常に柔軟で強力な言語になります。もちろん、ClojureScript開発者は、他の人が作成したClojureScriptライブラリを簡単に含めることもできます。

ライブラリの直接利用

複雑なデータ型を検証できるようにするClojureScriptライブラリであるSchemaを使用してみましょう。まず、ライブラリのコピーを入手する必要があります。

curl -O https://clojars.org/repo/prismatic/schema/0.4.0/schema-0.4.0.jar

CLJSJSライブラリと同様に、すべてがJARファイルにパッケージ化されており、コンパイル時にクラスパスで参照します。ただし、CLJSJSライブラリとは異なり、ClojureScriptライブラリJARにはexternsやdeps.cljsマッピングは含まれていません。

ライブラリの使用は簡単です。ClojureScriptコードとClojureマクロは同じライブラリにパッケージ化されていることに注意してください。

(ns hello-world.core
  (:require [schema.core :as s :include-macros true]))

(def Data {:a {:b s/Str :c s/Int}})

(s/validate Data {:a {:b "Hello" :c "World"}})

ビルドスクリプトはさらに簡単です。

{:output-to "out/main.js"}

これで、ビルドを実行できます。以下のようにJARを参照するだけです。

clj -M -m cljs.main -co build.edn -c

ブラウザをロードすると、JavaScriptコンソールにSchemaからの役立つ検証エラーが表示されます。:cキーを整数値に変更し、このエラーが消えることを確認したい場合は、リビルドしてください。