ClojureScript

JavaScript モジュール (アルファ版)

このガイドは ClojureScript 1.10.238 以降を必要とし、クイックスタートに精通していることを前提としています。.

このページでは、既存の ClojureScript プロジェクトに最新の JavaScript ソースファイルをシームレスに組み込む方法について説明します。文書化された機能は、アルファ品質であり、変更される可能性があることに注意してください。

動機

ClojureScript が最初にリリースされたとき、JavaScript へのコンパイルはまだ目新しく、minify を超えるソース変換はまれでした。それ以来、React JSX のような埋め込み HTML DSL や、JavaScript の古い弱点の多くに対処する新しい ECMAScript 標準など、JavaScript のソースからソースへのコンパイルがますます一般的になっています。しかし、これらの新しい種類のソースファイルを ClojureScript プロジェクトに統合するには、JavaScript ビルドツールに依存する必要があり、それらは正確なデッドコード削除やコード分割のような Google Closure コンパイラの高度な機能に欠けていました。

幸いなことに、Google Closure は JavaScript 言語へのさまざまな拡張機能の多くに対応しているだけでなく、さまざまな一般的な JavaScript モジュール形式(CommonJS、AMD、ES6)から Google Closure 名前空間規則への変換も提供しています。ClojureScript は現在、このすべての機能を公開しており、Java 8 の Nashorn JavaScript エンジンの助けを借りて、最先端の JavaScript ソース変換も比較的簡単に行うことができます。

さらに、Google Closure は Node.js の解決アルゴリズムのサポートも備えています。ClojureScript コンパイラは、NPM からの依存関係を使用したいプロジェクトを構築できるようになりました。

前提条件

クイックスタートと同様に、このガイドでは、最新リリースのJDK 8Node.js >= 6.9.4、および rlwrap がインストールされていることを前提としています。このガイドでは、依存関係を管理するためにLeiningenのみを使用していますが、BootまたはMavenにも簡単に適用できます。

JavaScript モジュール

まず、JavaScript モジュールがどのようにビルドの一部になるかを見てみましょう。

mkdir -p hello-es6
cd hello-es6
touch project.clj

次の内容でbuild.ednファイルを作成します

{:output-to    "main.js"
 :output-dir   "out"
 :main         hello-es6.core
 :target       :nodejs
 :foreign-libs [{:file "src"
                 :module-type :es6}]
 :verbose      true})

:foreign-libs エントリで :file のディレクトリを指定できるようになったことに注意してください。この場合、ClojureScript コンパイラはこのディレクトリを再帰的に検索して .js ファイルを探し、指定されたオプションを使用して :foreign-libs エントリを自動的に作成します。各エントリの :provides 名前空間は、ディレクトリ構造から自動的に計算されます。

メインの ClojureScript 名前空間を作成しましょう

mkdir -p src/hello_es6
touch src/hello_es6/core.cljs

このファイルを次のように編集します

(ns hello-es6.core
  (:require [cljs.nodejs :as nodejs]
            [js.hello :as hello]))

(nodejs/enable-util-print!)

(defn -main [& args]
  (hello/sayHello))

(set! *main-cli-fn* -main)

JavaScript ファイルは、他の Google Closure 名前空間と同様にインポートできることに注意してください。

JavaScript を書きましょう

mkdir -p src/js
touch src/js/hello.js

JavaScript ファイルは名前空間を宣言しないため、ClojureScript コンパイラはエントリの場所に基づいて名前空間を計算します。:foreign-libs エントリが "src" を指定したため、ClojureScript からの使用のためのこの JavaScript ファイルの名前空間は js.hello になります。

このファイルを次のように編集します

export var sayHello = function() {
    console.log("Hello, world!");
};

watch スクリプトが機能するかどうかを確認しましょう

cljs -m cljs.main -co build.edn -w -c

Node を main.js で起動することで、スクリプトが意図したとおりに動作することを確認できます。

node main.js
Hello world!

REPL の使用

JavaScript モジュールは単に Google Closure 名前空間にコンパイルされるため、すべての一般的な ClojureScript REPL 機能はそのまま動作します。たとえば、ES6 ソースファイルの自動ホットローディングが必要な場合は、Figwheel を使用してください。

標準の Node.js REPL で手動ホットローディングを実演します。

REPL を起動します

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

js.hello 名前空間を require して試してみましょう

user> (require '[js.hello :as hello])
true
user> (hello/sayHello)
Hello world!

REPL を終了せずに、src/js/hello.js を次のように編集します

export var sayHello = function() {
    console.log("Hello, world!");
};
export var sayThings = function(xs) {
    for(let x of xs) {
        console.log(x);
    }
};

JavaScript モジュールをリロードして、新しい機能を試してみましょう

user> (require '[js.hello :as hello] :reload)
true
user> (hello/sayThings ["ClojureScript", "+", "JavaScript", "Rocks!"])
ClojureScript
+
JavaScript
Rocks!

ClojureScript ベクターは ES6 イテレーションプロトコルをサポートしているため、ES6 for…of はそのまま動作します。

Google Closure は ES6 を処理できますが、Babel の JSX トランスフォームなどの JavaScript エコシステムの他のプリプロセッサを使用したい場合もあります。この場合、Nashorn を活用する必要があります。

Babel トランスフォーム

deps.edn ファイルを次のように変更します

{:deps {org.clojure/clojurescript {:mvn/version "1.9.854"}
        cljsjs/react {:mvn/version "15.4.2-0"}
        cljsjs/react-dom {:mvn/version "15.4.2-0"}
        cljsjs/react-dom-server {:mvn/version "15.4.2-0"}
        cljsjs/babel-standalone {:mvn/version "6.18.1-3"}}}

build.edn を次のように変更します

{:output-to    "main.js"
 :output-dir   "out"
 :main         hello-es6.core
 :target       :nodejs
 :foreign-libs [{:file "src"
                 :module-type :es6
                 :preprocess cljsjs.babel-standalone/transform}] ;; CHANGED
 :verbose      true})

Cljsjs の Babel-standalone パッケージは、必要な JavaScript ファイルと、:preprocess ハンドラとして使用できる関数を提供します。この関数は Nashorn JS エンジンを使用して Babel を実行し、外部ライブラリを処理します。Babel のオプションは、外部ライブラリマップにプロパティ :cljsjs.babel-standalone/babel-opts を追加することで指定できます。

React JSX コンポーネントを src/js/hello.js に追加しましょう

export var sayHello = function() {
    console.log("Hello, world!");
};
export var sayThings = function(xs) {
    for(let x of xs) {
        console.log(x);
    }
};
export var reactHello = function() {
    return <div>Hello world!</div>
};

ClojureScript を変更しましょう

(ns hello-es6.core
  (:require [cljsjs.react]
            [cljsjs.react.dom]
            [cljsjs.react.dom.server]
            [cljs.nodejs :as nodejs]
            [js.hello :as hello]))

(nodejs/enable-util-print!)

(defn -main [& args]
  (hello/sayHello)
  (println (.renderToString js/ReactDOMServer (hello/reactHello))))

(set! *main-cli-fn* -main)

watch スクリプトを実行します

lein trampoline run -m clojure.main watch.clj

ビルドが完了したら、コードを実行します

node main.js

次のような出力が表示されるはずです

Hello, world!
<div data-reactroot="" data-reactid="1" data-react-checksum="1334186935">Hello world!</div>

ES6 ファイルが React、ReactDOM、または ReactDOMServer への依存関係を import で宣言していないことに気付いたかもしれません。これを正しく処理するには、ES6 ソースファイルに対する Node.js モジュール解決をサポートするための Google Closure への保留中のパッチに依存します。この変更が実装されたら、このガイドは更新されます。

ただし、Node.js 解決の CommonJS サポートは現在機能しています。次のセクションではこのトピックについて説明し、最終的には ES6 ファイルにも適用されます。

カスタム JavaScript トランスフォーム

前の例では、Babel 変換関数は Cljsjs パッケージによって提供されました。異なる変換を使用する必要がある場合は、独自のプリプロセッシング関数を作成できます。Babel 変換は、Cljsjs パッケージなしで次のように実装できます

プロジェクトの project.clj から cljsjs/babel-standalone 依存関係を削除します。

babel.min.js をプロジェクトディレクトリにダウンロードします

curl -O https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.18.1/babel.min.js

新しい src/hello_es6/babel.clj ファイルを作成します

(ns hello-es6.babel
  (:require [clojure.java.io :as io]
            [cljs.build.api :as b])
  (:import javax.script.ScriptEngineManager))

(def engine
  (doto (.getEngineByName (ScriptEngineManager.) "nashorn")
    (.eval (io/reader (io/file "babel.min.js")))))

(defn transform-jsx [js-module opts]
  (let [code (str (gensym))]
    (.put engine code (:source js-module))
    (assoc js-module :source
      (.eval engine (str "Babel.transform("code", {presets: ['react', 'es2016']}).code")))))

build.edn を次のように変更してリビルドします

{:output-to    "main.js"
 :output-dir   "out"
 :main         hello-es6.core
 :target       :nodejs
 :foreign-libs [{:file "src"
                 :module-type :es6
                 :preprocess 'hello-es6.babel/transform-jsx}] ;; CHANGED
 :verbose      true})

オリジナル作成者: David Nolen