推廣一下 ClojureScript 入門指南 http://cljs-book.clj.im/javascript
得益於最近 ClojureScript(簡稱 cljs) 社區的發展, 運行和編譯 cljs 已經愈來愈方便.
刷一篇文章來展現一下如何用 ClojureScript 來模仿前端寫法運行 React.css
若是你只是想執行一下 cljs 代碼熟悉語法, 能夠直接安裝 Lumo.
Lumo 是一個基於 V8 開發的 cljs 運行環境, 支持 Node.js API.
你能夠經過多種方式安裝 Lumo:html
$ npm install -g lumo-cljs $ brew install lumo
安裝完成以後能夠從命令行直接啓動:前端
$ lumo Lumo 1.5.0 ClojureScript 1.9.542 Node.js v7.10.0 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Exit: Control+D or :cljs/quit or exit cljs.user=> (println "Hello world!") Hello world! nil cljs.user=>
或者也能夠用把代碼貼到一個文件裏, 而後經過 -i
這個參數來運行文件:java
lumo -i main.cljs
你能夠把這個文件保存下來試着用 Lumo 運行, 注意依賴的幾個 React 模塊:node
(ns demo.server-render) (def React (js/require "react")) (def ReactDOM (js/require "react-dom/server")) (def create-class (js/require "create-react-class")) (def comp-demo (create-class #js {:displayName "demo" :render (fn [] (.createElement React "div" nil))})) (println "This is only a demo.") (println (.renderToString ReactDOM (.createElement React comp-demo nil)))
用 cljs 來寫網頁會複雜一些, 由於涉及到編譯, 也涉及到引用 npm 模塊.
不過如今已經好多了, 新版的 cljs 編譯器加上 shadow-cljs 解決了這個麻煩.
shadow-cljs 是一個基於 npm 發佈的 cljs 編譯器, 因此直接用 npm 就能安裝.react
npm install shadow-cljs
cljs 編譯器有 JVM 依賴, 須要看下系統是否安裝了 Java, 若是沒有也能夠用 node-jre
代替.webpack
shadow-cljs 支持將 cljs 編譯到 CommonJS 格式的代碼, 因此原理上很簡單.
咱們要作的就是配置好 shadow-cljs 的編譯流程, 可以生成代碼.
完整的代碼我放在 GitHub 上, 能夠直接下載經過 yarn 運行:
https://github.com/clojure-ch...git
首先來看一下文件結構:github
=>> tree -I "node_modules|target" . ├── README.md ├── dist │ └── index.html ├── entry │ ├── main.css │ └── page.js ├── package.json ├── shadow-cljs.edn ├── src │ └── app │ └── main.cljs ├── webpack.config.js └── yarn.lock 4 directories, 9 files
除了咱們熟悉的 Webpack 開發經常使用的文件, 還有這樣一些文件:
shadow-cljs.edn
編譯工具的配置文件
src/app/main.cljs
這個就是咱們的 ClojureScript 代碼
shadow-cljs.edn
配置很是清晰:
{:source-paths ["src"] :dependencies [[mvc-works/hsl "0.1.2"]] :builds {:app {:target :npm-module :output-dir "target/"}}}
這是經常使用的編譯到 npm 模塊的配置
source-paths
編譯的源碼所在的文件夾
dependencies
cljs 依賴, 不過這個依賴只是個示例
builds
編譯配置的集合, 其中 app
就是一種編譯配置
target
編譯目標, 這裏的 :npm-module
表示 npm 模塊, 也能夠是 :browser
或者更多
output-dir
生成的文件輸出到哪裏
而後咱們經過 npm 本地安裝 shadow-cljs 就能夠經過命令行工具啓動編譯器了:
"scripts": { "watch": "webpack-dev-server --hot-only", "compile-cljs": "shadow-cljs -b app --once", "watch-cljs": "shadow-cljs -b app --dev" }, "devDependencies": { "css-loader": "^0.28.4", "shadow-cljs": "^0.9.5", "style-loader": "^0.18.2", "webpack": "^2.6.1", "webpack-dev-server": "^2.4.5" },
注意 shadow-cljs 的經常使用參數:
-b
其實是 --build
的縮寫, 表示選擇哪一個編譯配置, 這裏選了 app
--once
告訴編譯器只要編譯一次
--dev
告訴編譯器編譯以後繼續監視文件, 當文件改變時自動編譯
通過這樣的配置, 就能夠經過命令啓動了:
yarn watch-cljs
編譯結果會輸出在 target/
當中, 是不少個 .js
文件.
其中 target/app.main.js
是咱們想要的代碼的入口文件.
咱們用 cljs 寫 React 用到的是 JavaScript Interop.
也就是用 cljs 的語法去寫 js 語法的代碼, 編譯器會生成 js 代碼.
下面咱們看一遍具體怎樣實現, 完整的文件能夠看:
https://github.com/clojure-ch...
首先須要在命名空間當中定義 React 的依賴, 主要是三個模塊.
文件的命名空間是 app.main
, 跟 src/app/main.cljs
的路徑相對應.
其中 src/
前面在配置 source-paths
已經寫過了.
在最新的 cljs 當中能夠這樣引用 npm 模塊:
(ns app.main (:require ["react" :as React] ["react-dom" :as ReactDOM] ["create-react-class" :as create-class]))
而後能夠用 create-class
這個函數來調用 React.createClass
定義組件,
其中 #js {}
表示這個 HashMap 會轉成 JavaScript Object,
cljs 語法裏鍵的位置用關鍵字語法, 也會被自動轉成 JavaScript 屬性,
而後還有 React.createElement
這個方法的調用, 是特別的寫法:
(def container (create-class #js {:displayName "container" :render (fn [] (.createElement React "div" nil (.createElement React "span" nil "Hello world!")))}))
而後是掛載組件. 經過 ReactDOM.render
方法來掛載.
注意在 cljs 當中直接引用瀏覽器 API 須要藉助 js/
這個命名空間.
(def mount-point (.querySelector js/document "#app")) (defn render! [] (.render ReactDOM (.createElement React container nil) mount-point))
剛纔的代碼用到了不少 React without JSX 的寫法, 能夠參考官方文檔:
https://facebook.github.io/re...
而後咱們定義一下初始化的代碼, main
在後面的代碼唄調用,
還有 reload
時從新繪製界面. 由於咱們的例子要支持代碼熱替換:
(defn main [] (render!) (println "App loaded.")) (defn reload [] (render!) (println "App reloaded."))
這個文件編譯以後是一個很難看到 js 文件, 能夠看到是支持 CommonJS 規範的.
並且 cljs 通常也會有 SourceMaps 支持, 實際開發當中能夠不看編譯出來的 js 代碼.
var $CLJS = require("./cljs_env"); require("./cljs.core.js"); require("./shadow.npm.react.js"); require("./shadow.npm.react_dom.js"); require("./shadow.npm.create_react_class.js"); var cljs=$CLJS.cljs; var shadow=$CLJS.shadow; var goog=$CLJS.goog; var app=$CLJS.app || ($CLJS.app = {}); goog.dependencies_.written["app.main.js"] = true; goog.provide('app.main'); goog.require('cljs.core'); goog.require('cljs.core'); goog.require('shadow.npm.react'); goog.require('shadow.npm.react_dom'); goog.require('shadow.npm.create_react_class'); app.main.mount_point = document.querySelector("#app"); app.main.container = (function (){var G__10849 = ({"displayName": "container", "render": (function (){ return shadow.npm.react.createElement("div",null,shadow.npm.react.createElement("span",null,"Hello world!")); })}); return shadow.npm.create_react_class(G__10849); })(); app.main.render_BANG_ = (function app$main$render_BANG_(){ return shadow.npm.react_dom.render(shadow.npm.react.createElement(app.main.container,({})),app.main.mount_point); }); app.main.main = (function app$main$main(){ app.main.render_BANG_(); return cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(cljs.core.array_seq(["App loaded."], 0)); }); app.main.reload = (function app$main$reload(){ app.main.render_BANG_(); return cljs.core.println.cljs$core$IFn$_invoke$arity$variadic(cljs.core.array_seq(["App reloaded."], 0)); }); module.exports = app.main; //# sourceMappingURL=app.main.js.map
而後還須要一個入口文件來處理一下啓動的功能, 好比 CSS 和熱替換.
Webpack 提供了 module.hot
API 用來手動處理熱替換.
咱們接受整個 app.main.js
依賴的文件更新, 從新執行 require, 而且調用前面的 reload
函數.
require('./main.css'); window.onload = require('../target/app.main').main; if (module.hot) { module.hot.accept('../target/app.main', function() { require('../target/app.main').reload(); }); }
Webpack 的完整配置我就不重複了, 整個連接我貼在這裏.
https://github.com/clojure-ch...
開發環境是能夠支持 SourceMaps 的, 可是因爲性能不理性, 我關掉了.
最後加一個給 Webpack 啓動的入口文件, 也就注意一下加載順序:
<div id="app"></div> <script type="text/javascript" src="main.js"></script>
最後就能夠啓動整個應用了, 前面寫在了 npm scripts 裏邊:
yarn # 安裝依賴 yarn watch-cljs # 啓動 cljs 的編譯器 # 再開個終端 yarn watch # 啓動 Webpack 的開發環境
再打開 http://localhost:8080/ 就能夠看到 React 渲染的 "Hello world!" 了.
這篇文章介紹的只是很基礎的在 cljs 裏調用 React 的寫法.
實際開發當中會用類庫來寫, 寫起來更簡單, 並且能作一些優化,
目前推薦的類庫有 Reagent 和 Rum, 會涉及一些高級的 cljs 語法.
學習 ClojureScript 是個比較麻煩的過程, 這個語言你們平時都不習慣,
有興趣的話卻是能夠循着這些連接繼續翻下去:
https://github.com/shaunlebro...
https://github.com/clojure-ch...
http://www.braveclojure.com/c...
若是你想了解 shadow-cljs 編譯器更多的用法, 能夠查看 Wiki:
https://github.com/thheller/s...
我也寫了幾個經常使用功能的例子, 又能夠直接 clone 到本地經過 yarn 運行:
https://github.com/minimal-xy...
另外我最近(06-13)在 SegmentFault 有個簡短分享, 感興趣的話能夠來評論.
ClojureScript 帶給 React 項目的借鑑意義