这篇文章经过 Thomas Greco 和 Jérémy Heleine 的同行评议。感谢所有 SitePoint 的评议者们,是你们使 SitePoint 的内容如此的棒!
从几个月前起,越来越多的开发者秉承着“抱紧 JavaScript 的大腿”的理念,然而,被编译为 JavaScript 的编程语言与日俱增,其中包含有 Dart、TypeScript、CoffeeScript、ClojureScript 等。
在这篇文章中我们会讨论一下 ClojureScript,一种目标为 JavaScript 的新 Clojure 编译器。我们会探讨一下使用 ClojureScript 的好处,以及怎么样快速的使用 npm 和“你们最爱的”Node.js 库搭建起它的环境。
在网上有很多的文章解释了 ClojureScript 的好处,总的来说有以下几点:
简洁性: 从语法角度来说,ClojureScript 是一种基于 Lisp 的语言,这使得它的语法十分简洁。它的简洁性使得我们甚至能在本篇文章中完全地解释它。除此之外,ClojureScript 还提供了帮助我们更方便的异步编程的工具。
安全性: 这意味着更少的漏洞!ClojureScript 和其他的函数编程语言都有很多的特性来帮助我们减少和减轻一些常见的漏洞。
高性能: ClojureScript 使用了 Google的Closure编译器 (译者注:需翻墙),这使得ClojureScript能支持 无用代码清除 以及其他的一些特点。
实时编程: ClojureScript 生态圈提供了很多的工具来“实时编程”。这意味着当代码一改变,在你的项目上就会马上反应出来。在这篇文章中我们会看下 Figwheel 使读者能更好的理解这个概念。
代码复用性: ClojureScript 能在全局中运行,很多人称之“同态”。这代表你能在服务器端和客户端运行相同的代码,这在 Node.js 的生态系统中已经是个流行的模式了。除此之外,ClojureScript 还能使用 Node.js 和 Java 生态圈当中的库。
在这篇文章中,我们将在 Mac OSX 环境中安装工具链。以防万一,在 这里 你能找到 ClojureScript wik i中在其他环境下的安装指南。首先我们将会需要一些系统依赖项,其中一个就是 OSX 下流行的包管理器, Homebrew 。
ClojureScript 需要最新版本的 Java (在这篇文章完成的时候为 Java8)。如果在运行 lein 这个命令的时候你遇到了一个看起来像这样的错误:
Exception in thread "main" java.util.regex.PatternSyntaxException: Unknown inline modifier near index 2 (?U)^[/p{Alpha}_$]^, compiling:(cljs/util.clj:158:33)
那么你就需要去安装最新的 Java 了。
首先,让我们在命令行界面中运行如下命令:
brew tap caskroom/cask brew install brew-cask
如果终端出现了“already installed”这个错误,根据你终端中的引导将链接取消。一旦完成,再次安装。如下命令可以完成这个动作:
brew unlink brew-cask brew install brew-cask
此时,执行我们需要的最后一个命令:
brew cask install java
Leiningen 是一个 Clojure 项目的构建工具,我们将用它来执行 ClojureScript 代码并安装依赖项。这一步假设你已经安装了 Homebrew 。
brew install leiningen
如果这步失败了,也许你需要 手动安装 。
既然现在我们已经安装了 Leningen,我们就可以开始熟悉 ClojureScript 的语法了。
执行
lein rep
命令,你应该会得到类似于这样的输出:
$ lein repl nREPL server started on port 58371 on host 127.0.0.1 - nrepl://127.0.0.1:58371 REPL-y 0.3.7, nREPL 0.2.10 Clojure 1.7.0 Java HotSpot(TM) 64-Bit Server VM 1.6.0_65-b14-466.1-11M4716 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e user=>
我们现在已经在 ClojureScript Repl 中了,这让我们能快速执行 ClojureScript 并且查看结果。如果要离开 repl 你可以按下 Control+D。
执行这步之后,我们现在已经准备好在 ClojureScript 的语法海洋遨游并做点好玩的事啦!
ClojureScript 是一种函数语言,这代表它使用函数以及有限的额外语言结构。在接下来的模块中我将会讲述这种语言的一些特性。
ClojureScript 支持下列的原生类型:
数字
user=> 1.23 1.23
字符串
user=> "foo" "foo"
矢量(数组)
user=> [:bar 3.14 "hello"] [:bar 3.14 "hello"]
地图(混合数组)
user=> {:msg "hello" :pi 3.14 :primes [2 3 5 7 11 13]} {:msg "hello", :pi 3.14, :primes [2 3 5 7 11 13]}
关键字(用于访问地图)
user=> :foo :foo
集合(唯一数组)
user=> #{:bar 3.14 "hello"} #{"hello" 3.14 :bar}
函数是 ClojureScript 的基础组件。你甚至是通过内置的 defn 函数来定义自己的函数。
下面是一个函数定义的例子。我们要定义一个叫 myfunction 的函数。这个函数有一个参数,叫 argument1,然后简单的返回这个参数。这个函数没啥用,但是可以很好的为我们示范语法。
user=> (defn myfunction [argument1] argment1)
估计你会觉得这个语法有些怪异,下面是 Javascript 的等价代码:
function myfunction(argument1){ return argument1;}
通过圆括号把函数名和参数包含起来的方式来调用函数。
user=> (myfunction "hello world") "hello world"
在非函数式编程语言里,可以使用特殊的“操作符”或者关键字。例如 Javascript 中,通常可以用到 + - == if。但是在 ClojureScript 和其他基于 Lisp 的语言里,没有特殊的操作符,只有常规的函数。
If 表达式是一个函数:
user=> (if true "do true stuff here" "do false stuff here") "do true stuff here"
数学表达式也是函数,如下所示:
user=> (+ 2 3) 5 user=> (* 2 3) 6
关于更多的 Javascript 与 ClojureScript 的等价代码的示例,可以参考这个网站 。
创建一个 ClojureScript 项目是十分方便的,Leningen 提供了项目模版来激起你的兴趣并使你能迅速地掌握怎么运行一个样板化项目。
模版是一种让我们实验并熟悉一些功用和设置的很好的资源。 Clojars.org 上有许多的模版,同时,你也可以在网上找到其他一些。在我们的项目当中,我们将使用 Nodejs Figwheel 项目模版 。
首先,让我们在命令行界面中运行如下命令来开始:
$ lein new figwheel-node hello-world
这会在 ./hello-world 目录下创建一个新的 ClojureScript 项目。这篇文章的余下部分将假设项目名称是 hello-world,当然了,如果你想的话你可以使用其他名字,但我建议你保留这个项目名,如此一来你可以跟着文章走并且不需要害怕有什么东西会出错。
接下来,进到我们创建的目录当中并安装 npm 依赖项:
$ cd hello-world $ npm install
项目文件夹中包含一些文件,在这个段落中我将突出强调一些关于它们的重要概念:
package.json:这对于Node.js用户来说应该很熟悉了,我们的npm依赖项将会添加在这个文件中。
project.clj:这个文件是ClojureScript项目的配置文件,可以说是ClojureScript版本的package.json。我们能在这里配置Clojure依赖项以及编译目标。这个文件同样包含如标题和叙述的项目细节
figwheel.js: 这个文件只在Figwheel项目中存在,是我们项目的启动文件。它将Figwheel指向我们的源代码让它来监听代码更新。我们将使用node figwheel.js命令来运行它。
./src/hello-world/core.cljs:这是我们的入口资源文件,也就是我们启动项目的地方。把它想象成类似于Node.js项目中的一个index.js文件吧。
core.cljs文件包含以下的内容,我添加了注释使你能明白发生了什么:
;; 这里声明了一个当前文件的命名空间以及必要依赖项 (ns hello-world.core (:require [cljs.nodejs :as nodejs])) ;; 这里升级了默认的println方法使它能写到Node.js的stdout当中 (nodejs/enable-util-print!) ;; 模块的主函数 ;; 它将会打印并输出"Hello World!"到stdout中 (defn -main [] (println "Hello world!")) ;; *main-cli-fn* 是一个用于为node应用设定入口*point的“半魔法”变量 (set! *main-cli-fn* -main)
若要运行当前项目,打开终端窗口并移动到我们的hello-world项目文件夹中,然后执行以下命令:
lein figwheel
这将启动Figwheel监听需要构建的更新,将这个窗口放着让他运行并开启一个新的终端。在这个终端中再次移动到项目文件夹中并执行以下命令:
node figwheel.js
你应该会看到如下的“Hello world”输出:
$ node figwheel.js Hello world! Figwheel: trying to open cljs reload socket Figwheel: socket connection established
既然现在我们已经搭建好了 ClojureScript 项目的基础,我们就可以开始在新的终端中使用一些我们熟悉的库了。在 hello-world 文件夹中执行以下命令:
npm install --save express
然后我们需要将./src/hello-world/core.cljs更新为:
(ns hello-world.core (:require [cljs.nodejs :as nodejs] [clojure.string :as string])) (nodejs/enable-util-print!) (defonce express (nodejs/require "express")) (defonce http (nodejs/require "http")) (defonce server-port 3000) (def app (express)) (. app (get "/hello" (fn [req res] (. res (send "Hello world"))))) (def -main (fn [] (doto (.createServer http #(app %1 %2)) (.listen server-port)))) (.listen server)))) (println (string/join " " ["Server running on" server-port]) ) (set! *main-cli-fn* -main)
现在当你在项目中执行 node figwheel.js 时,你应该会看到 running on 3000 的输出。如果你访问 http://localhost:3000/hello ,你应该会看到 express route 的输出“Hello world.”
在本篇文章中我们讨论了如何建立一个新的 ClojureScript 项目并在其中安装常用的 Node 依赖项。这为我们将 ClojureScript 作为一种语言来理解打下了很好的基础。同时我将这篇文章用到的 源代码 放上了 Github,它已经有些超出了本文的范围,演示了如何整合 React 服务器端渲染。