ClojureScript

ClojureScriptは孤島ではない:Nodeモジュールの統合

2017年7月12日
António Nuno Monteiro

これはスニークプレビューシリーズの2番目の投稿です。

ClojureScriptは、2011年の最初のリリース以来、ファーストクラスのJavaScript相互運用性を備えています。Clojureと同様に、ホストを受け入れることは常に明確な目標でした。上記のことは構文の観点からは真実ですが、外部JavaScriptライブラリとの統合は、歴史的に手作業1手動で組み立てられたバンドルまたはコミュニティの努力主導のパッケージング)を伴うコストがかかっていました。1(手動で組み立てられた バンドル、または コミュニティの努力 によって主導されるパッケージング)。

外部JavaScriptとのより良い相互作用への一歩

2015年のClojureScriptのGoogle Summer of Codeプロジェクトは、外部JavaScriptモジュールとの相互作用を容易にすることに成功しました。当時新しくなったGoogle Closure Compilerの、広く知られているほとんどのJavaScriptモジュール形式を理解し、Closure互換のJavaScriptに変換する機能2を利用して、Maria Gellerは、これらのモジュール機能をClojureScriptに正常に統合しました。また、一般的なJavaScriptコンパイル技術であるBabelなどの機能をClojureScriptプロジェクトから快適に使用できるようにするカスタムプリプロセッシング手順も含まれています3

その後、Closure Compilerチームは2016年にモジュールの*解決*サポートを追加しました。これにより、Closureに手作業でモジュールパスを入力して変換する代わりに、 `node_modules` インストールからモジュールを直接使用できるようになりました。

NPM依存関係とのシームレスな相互作用

私たちはMariaのモジュール作業を基に、この新しいClosure機能に対応するために構築しました。ClojureScriptの次のリリースは、NPMエコシステムとの相互運用方法に大幅な改善を加えることで、あらゆるClojureScriptプロジェクトが任意のJavaScriptモジュールにアクセスできるようにするための、もう一つの大きなマイルストーンとなります。

ClojureScript名前空間からNPMモジュールを要求することは、ClojureScriptバージョン1.9.518から可能になりました。しかし、Node.jsはCommonJSモジュールを要求するための複数のパターンをサポートしており、一般的なペインポイントは、ClojureScriptから ` "react-dom/server" ` 形式のモジュールを要求しようとする人々でした。これは ` :require ` スペックの有効なシンボルではありませんでした。

このリリースでは、上記の課題を解決するために、名前空間フォームでの文字列ベースのrequireをサポートしました。これらのタイプのモジュールを ` ns ` 宣言から快適に要求できるようになりました。

すべてを結びつけるのは ` :npm-deps ` コンパイラフラグです。 इसमें हम कंपाइलर को बताते हैं कि उसे किन dependencies के बारे में पता होना चाहिए। ClojureScriptは、これらの依存関係をインストールし、Closure Compilerの変換パイプラインを通じて実行します。これには、以下で詳しく説明する最適化も含まれます。

実践例

以下のような ` build.clj ` ファイルがあるとします

(require '[cljs.build.api :as b])

(b/build "src"
  {:output-dir "out"
   :output-to "out/main.js"
   :optimizations :none
   :main 'example.core
   :install-deps true
   :npm-deps {:react "15.6.1"
              :react-dom "15.6.1"}})

最も単純な ` src/example/core.cljs ` ファイルは、以下のスニペットのようになります

(ns example.core
  (:require [react :refer [createElement]]
            ["react-dom/server" :as ReactDOMServer :refer [renderToString]]))

(js/console.log (renderToString (createElement "div" nil "Hello World!")))

` "react-dom/server" ` をどこにも宣言する必要がないことに注意してください。ただrequireすればいいのです。ClojureScriptは、これらのCommonJSモジュールを見つけて、Google Closure Compiler互換コードに処理するのに十分なほどスマートになりました。

これは大きな進歩です

Google ClosureでJavaScriptモジュールを使用することの影響は非常に大きいです。ClojureScriptプロジェクトで使用される外部ライブラリは、生成されたバンドルに付加されるだけでなく、Closure Compilerのすべての最適化(デッドコードの削除や、コード分割を利用するプロジェクトではモジュール間のコード移動など)の対象となります。たとえば、テストでは、ReactはClosureの高度なコンパイルでは、既存の一般的なJavaScriptツールよりもかなり小さくなります(約16%)4。さらに、ClojureScriptとJavaScriptの混在コードベースがある場合、コードのJavaScript部分(JSX変換など)をシームレスに使用できるだけでなく、ベンダーの依存関係をClojureScript部分が使用する依存関係と共有してバンドルすることもできます。

Node.jsでも動作します!

ClojureScriptのモジュール処理機能は、主に依存関係がまとめてバンドルされるブラウザをターゲットとするプロジェクトで使用されることを意図していることに注意してください。しかし、Node.jsをターゲットとするプロジェクトがこの機能を利用できないわけではありません。実際、Node.jsをターゲットとする場合でも、ローカルの ` node_modules ` インストールにあるNodeモジュールを名前空間宣言からシームレスに要求できるようにしました。ClojureScriptは、Nodeモジュールを要求していることを認識し、 ` require ` 宣言を生成して、Node.js独自のJavaScriptモジュール読み込み機能と統合します。

最後に

ClojureScriptは、約6年後には、世界中の多くの開発者が頼りにするプラットフォームとなり、ホストとの完全な相互運用性を実現し続けたいと考えています。既存の膨大なJavaScriptエコシステムとの統合を確実にすることで、ClojureScriptの次のバージョンで登場するこれらの新機能は、ClojureScriptの長期的な持続可能性を保証する足がかりになると考えています。

これらの新機能を私たちと同じように楽しんでいただければ幸いです。読んでいただきありがとうございます!


1. ClojureScriptの初期リリース以来深く統合されているGoogle Closure Libraryは別として。
2. Closure Compilerがデッドコードの削除(ツリーシェイキング)、変数名の書き換え、定数の畳み込みとインライン化などの最適化を実行するために必要なJavaScriptのサブセット。
3. Mariaは、そのプロジェクトに取り組んでいる間、定期的にブログを更新していました。 こちら で読むことができます。
4. 実際、ReagentチームはすでにNPMモジュールを使用するウェブサイトのバージョンをテストしています。また、以前のバージョンと比較もしています。