mkdir -p hello-es6 cd hello-es6 touch project.clj
本指南要求 ClojureScript 1.10.238 或更高版本,并假设您熟悉 快速入门.
本页面介绍如何将现代 JavaScript 源文件无缝地混合到现有的 ClojureScript 项目中。所述功能应被视为 alpha 质量,可能会发生变化。
ClojureScript 最初发布时,编译到 JavaScript 仍然是一个新奇的事物,除了缩小之外的源代码转换也很少见。从那时起,JavaScript 的源代码到源代码编译变得越来越流行,无论是像 React JSX 这样的嵌入式 HTML DSL,还是 新的 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 的依赖项的项目。
首先让我们看看如何将 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"
,因此此 JavaScript 文件从 ClojureScript 中使用的命名空间将是 js.hello
。
编辑此文件,使其看起来像这样
export var sayHello = function() {
console.log("Hello, world!");
};
让我们检查我们的监视脚本是否有效
cljs -m cljs.main -co build.edn -w -c
您可以通过在 main.js
上调用 Node 来验证脚本是否按预期工作
node main.js Hello world!
由于 JavaScript 模块只是被编译成 Google Closure 命名空间,因此所有通用的 ClojureScript REPL 功能都正常工作。例如,如果您想要自动热加载 ES6 源文件,只需使用 Figwheel。
我们将使用标准的 Node.js REPL 演示手动热加载。
启动 REPL
clj -M -m cljs.main -co build.edn -r
加载 js.hello
命名空间并试用它
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,但您可能希望使用 JavaScript 生态系统中的其他预处理器 - 例如 Babel 的 JSX 转换。在这种情况下,我们希望利用 Nashorn。
将您的 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})
Babel-standalone 包来自 Cljsjs,它提供了必要的 JavaScript 文件和一个可以用作 :preprocess
处理程序的函数。该函数使用 Nashorn JS 引擎来运行 Babel 并处理外部库。可以通过将 :cljsjs.babel-standalone/babel-opts
属性添加到外部库映射来提供 Babel 的选项。
让我们将 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)
运行监视脚本
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 文件没有通过 import
声明其对 React、ReactDOM 或 ReactDOMServer 的依赖关系。正确处理此问题取决于 Google Closure 的一个待处理补丁,以支持 ES6 源文件的 Node.js 模块解析。当此更改发布后,本指南将更新。
但是,Node.js 解析的 CommonJS 支持今天已经可以使用了。下一节将涵盖此主题,并最终也将适用于 ES6 文件。
在前面的示例中,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