磨刀不誤砍柴工,本篇將介紹如何搭建Chrome插件的ClojureScript開發環境。
具體工具棧:vim(paredit,tslime,vim-clojure-static,vim-fireplace) + leiningen(lein-cljsbuild,lein-doo,lein-ancient) + com.cemerick/piggiebackhtml
首先拋開將cljs編譯爲js、調試、測試和發佈等問題,首先第一要務是寫得爽~
cljs中最讓人心煩的就是括號()
,過去我想可否改個語法以換行來代替括號呢?而paredit.vim正好解決這個問題。node
在.vimrc中添加git
Plugin 'paredit.vim'
在vim中運行github
:source % :PluginInstall
<Leader>
鍵" 設置<Leader>鍵 let mapleader=',' let g:mapleader=','
(
、[
、{
和"
,會自動生成)
、]
、}
和"
,而且光標位於其中,vim處於insert狀態;<Leader>+W
會生成括號包裹住當前光標所在的表達式;<Leader>+w+[
會生成[]
包裹住當前光標所在的表達式;<Leader>+w+"
會生成""
包裹住當前光標所在的表達式。更多用法就經過:help paredit
查看paredit的文檔便可。chrome
cljs要被編譯爲js後才能被運行,這裏我採用leiningen。
在shell中運行shell
# 建立工程 $ lein new crx-demo $ cd crx-demo
工程目錄中的project.clj
就是工程文件,咱們將其修改以下json
(defproject crx-demo "0.1.0-SNAPSHOT" :description "crx-demo" :urnl "http://fsjohnhuang.cnblogs.com" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"] ;; 經過dependencies聲明項目依賴項 [org.clojure/clojurescript "1.9.908"]] :plugins [[lein-cljsbuild "1.1.7"]] ;; 經過plugins聲明leiningen的插件,而後就能夠經過lein cljsbuild調用lein-cljsbuild這個插件了 :jvm-opts ["-Xmx1g"] ;; 設置JVM的堆容量,有時編譯失敗是應爲堆過小 :cljsbuild {:builds [{:id "browser_action" :source-paths ["src/browser_action"] :compiler {:main browser-action.core :output-to "resources/public/browser_action/js/ignoreme.js" :output-dir "resources/public/browser_action/js/out" :asset-path "browser_action/js/out" :optimizations :none ;; 注意:爲提升編譯效率,必須將優化項設置爲:none :source-map true :source-map-timestamp true}} {:id "content_scripts" :source-paths ["src/content_scripts"] :compiler {:main content-scripts.core :output-to "resources/public/content_scripts/js/content_scripts.js" :output-dir "resources/public/content_scripts/js/out" :asset-path "content_scripts/js/out" :optimizations :whitespace :source-map true :source-map-timestamp true}}}]} :aliases {"build" ["cljsbuild" "auto" "browser_action" "content_scripts"] ;; 設置別名,那麼經過lein build就可一次性編譯browser_action和content_scripts兩個子項目了。 })
上述配置很明顯我是將browser_action和content_scripts做爲兩個獨立的子項目,其實Chrome插件中Browser Action、Page Action、Content Scripts和Background等均是相對獨立的模塊相互並不依存,而且它們運行的方式和環境不盡相同,所以將它們做爲獨立子項目配置、編譯和優化更適合。
而後新建resources/public/img目錄,並附上名爲icon.jpg的圖標便可。
&esmp;而後在resources/public下新建manifest.json文件,修改內容以下vim
{ "manifest_version": 2, "name": "crx-demo", "version": "1.0.0", "description": "crx-demo", "icons": { "16": "img/icon.jpg", "48": "img/icon.jpg", "128": "img/icon.jpg" }, "browser_action": { "default_icon": "img/icon.jpg", "default_title": "crx-demo", "default_popup": "browser_action.html" }, "content_scripts": [ { "matches": ["*://*/*"], "js": ["content_scripts/js/core.js"], "run_at": "document_start" } ], "permissions": ["tabs", "storage"] }
接下來新建resources/public/browser_action.html
,並修改內容以下瀏覽器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script src="browser_action/js/out/goog/base.js"></script> <script src="browser_action/js/out/goog/deps.js"></script> <script src="browser_action/js/out/cljs_deps.js"></script> <script src="browser_action.js"></script> </body> </html>
到這一步咱們會發現哪來的browser_action.js
啊?先別焦急,這裏涉及到Browser Action的運行環境與google closure compiler輸出不兼容的問題。網絡
這裏有個限制,就是default_popup
所指向頁面中不能存在內聯腳本,而optimizations :none
時google closure compiler會輸出以下東東到ignoreme.js
中
var CLOSURE_UNCOMPILED_DEFINES = {}; var CLOSURE_NO_DEPS = true; if(typeof goog == "undefined") document.write('<script src="resources/public/browser_action/js/out/goog/base.js"></script>'); document.write('<script src="resources/public/browser_action/js/out/goog/deps.js"></script>'); document.write('<script src="resources/public/browser_action/js/out/cljs_deps.js"></script>'); document.write('<script>if (typeof goog == "undefined") console.warn("ClojureScript could not load :main, did you forget to specify :asset-path?");</script>'); document.write('<script>goog.require("process.env");</script>'); document.write('<script>goog.require("crx_demo.core");</script>');
這很明顯就是加入內聯腳本嘛~~~因此咱們要手工修改一下,新增一個resources/public/browser_action.js
,而後添加以下內容
goog.require("process.env") goog.require("crx_demo.core")
這裏咱們就搞定Browser Action/Popup的編譯運行環境了^_^。你們有沒有發現goog.require("crx_demo.core")
這一句呢?咱們的命名空間名稱不是crx-demo.core
嗎?注意了,編譯後不只路徑上-
會變成_
,連在goog中聲明的命名空間名稱也會將-
變成了_
。
因爲content scripts是直接運行腳本,沒有頁面讓咱們如popup那樣控制腳本加載方式和順序,所以只能經過optimizations :whitespace
將全部依賴打包成一個js文件了。也就是說編譯起來會相對慢不少~不少~多~~~
到這裏咱們彷佛可寫上一小段cljs,而後編譯運行了。且慢,沒有任何智能提示就算了,還時不時要上網查閱API DOC,你肯定要這樣開發下去?
經過vim-fireplace咱們能夠手不離vim,查閱API文檔,和查閱項目代碼定義哦!
1.裝vim插件
Plugin 'tpope/vim-fireplace'
在vim中運行
:source % :PluginInstall
2.安裝nRepl中間件piggieback
nRepl就是網絡repl,能夠接收客戶端的腳本,而後將運行結果回顯到客戶端。咱們能夠經過lein repl
啓動Clojure的nRepl。
而fireplace則是集成到vim上鍊接nRepl的客戶端,但默認啓動的僅僅是Clojure的nRepl,因此要經過中間件附加cljs的nRepl。這是咱們只需在project.clj中添加依賴便可。
:dependencies [[com.cemerick/piggieback "0.2.2"]] :repl-options {:port 9000 :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
在shell中更新依賴lein deps
3.設置fireplace監聽端口
在項目目錄下建立文件,echo 9000 > .nreplport
4.啓動nRepl,lein repl
這時在vim中輸入:Source map
就會看到cljs.core/map
的定義,若不行則按以下設置:
:Connect Protocol: nrepl Host: localhost Port: 9000 Scope connection to: ~/crx-dome
這樣就設置好fireplace和nrepl間的連接了。
5.別開心太早
不知道是什麼緣由咱們只能用fireplace中部分的功能而已,如經過:Source <symbol>
查看定義,:FindDoc <keyword>
查看匹配的Docstring,但沒法經過:Doc <symbol>
來查看某標識的Docstring。
另外若要經過:Source <symbol>
查看當前項目的定義時,請先lein build
將項目編譯好,不然沒法查看。另一個十分重要的信息是,在optimizations
不爲:none
的項目下的文件是沒法執行fireplace的指令的,因此在開發Content Scrpts時就十分痛苦了~~~
那有什麼其餘辦法呢?不怕有tslime.vim幫咱們啊!
tslime.vim讓咱們能夠經過快捷鍵將vim中內容快速地複製到repl中執行
1.安裝vim插件
Plugin 'jgdavey/tslime.vim'
在vim中運行
:source % :PluginInstall
2..vimrc配置
" 設置複製的內容自動粘貼到tmux的當前session和當前window中 let g:tslime_always_current_session = 1 let g:tslime_always_current_window = 1 vmap <C-c><C-c> <Plug>SendSelectionToTmux nmap <C-c><C-c> <Plug>NormalModeSendToTmux nmap <C-c>r <Plug>SetTmuxVars
3.將clojure repl升級cljs repl
經過lein repl
咱們創建了一個cljs nrepl供fireplace使用,但在終端中咱們看到的是一個clojure的repl,而tslime剛好要用的就是這個終端的repl。那如今咱們只要在clojure repl中執行(cemerick.piggieback/cljs-repl (cljs.repl.rhino/repl-env))
便可。
而後就能夠在vim中把光標移動到相應的表達式上按<C-c><C-c>
,那麼這個表達式就會自動複製粘貼到repl中執行了。
因爲cljs擁有比js更爲豐富的數據類型,也就是說直接將他們輸出到瀏覽器的console中時,顯示的格式會不太清晰。因而咱們須要爲瀏覽器安裝插件,但經過devtools咱們就不用顯式爲瀏覽器安裝插件也能達到效果(太神奇了!)
在project.clj中加入
:dependencies [[binaryage/devtools "0.9.4"]] ;; 在要格式化輸出的compiler中添加 :compiler {:preloads [devtools.preload] :external-config {:devtools/config {:features-to-install [:formatters :hints :async]}}}
而後在代碼中經過js/console.log
、js/console.info
等輸出的內容就會被格式化的了。
爲了保證開發的質量,單元測試怎麼能少呢?在project.clj中加入
:plugins [[lein-doo "0.1.7"]]
而後在test/crx_demo
下新建一個runner.cljs文件,並寫入以下內容
(ns crx-demo.runner (:require-macros [doo.runners :refer [doo-tests]]) (:require [crx-demo.content-scripts.util-test])) ;; 假設咱們要對crx-demo.content-scripts.util下的函數做單元測試,而測試用例則寫在crx-demo.content-scripts.util-test中 (doo-tests 'crx-demo.content-scripts.util-test)
而後建立crx-demo.content-scripts.util-test.cljs測試用例文件
(ns crx-demo.content-scripts.util-test (:require-macros [cljs.test :refer [deftest is are testing async]] (:require [crx-demo.content-scripts.util :as u])) (deftest test-all-upper-case? (testing "all-upper-case?" (are [x] (true? x) (u/all-upper-case? "abCd") (u/all-upper-case? "ABCD")))) (deftest test-all-lower-case? (testing "all-lower-case?" (is (true? (u/all-lower-case? "cinG"))))) (deftest test-get-async (async done (u/get-async (fn [item] (is (seq item)) (done)))))
而後再新增一個測試用的子項目
{:id "test-proj" :source-paths ["src/content_scripts" "test/crx_demo"] :compiler {:target :nodejs ;;運行目標環境是nodejs :main crx-demo.runner :output-to "out/test.js" :output-dir "out/out" :optimizations :none :source-map true :source-map-timestamp true}}
而後在shell中輸入lein doo node test-proj
辛苦開發後咱們將optimizations
設置爲advanced
後編譯優化,將做品發佈時發現相似於以下的報錯
Uncaught TypeError: sa.B is not a function
這到底是什麼回事呢?
開發時最多就是將optimizations
設置爲simple
,這時標識符並無被壓縮,因此如chrome.runtime.onMessage.addListener
等外部定義標識符依然是原裝的。但啓用advanced
編譯模式後,因爲上述外部標識符的定義並不歸入GCC的編譯範圍,所以GCC僅僅將調用部分代碼壓縮了,而定義部分仍是原封不動,那麼在運行時調用中天然而然就找不到相應的定義咯。Cljs早已爲咱們找到了解決辦法,那就是添加extern文件,extern文件中描述外部函數、變量等聲明,那麼GCC根據extern中的聲明將不對調用代碼中同簽名的標識符做壓縮。
示例:chrome的extern文件chrome.js片斷
/** * @constructor */ function MessageSender(){} /** @type {!Tab|undefined} */ MessageSender.prototype.tab
1.訪問https://github.com/google/closure-compiler/tree/master/contrib/externs,將chrome.js和chrome_extensions.js下載到項目中的externs目錄下
2.配置project.clj文件
:compiler {:externs ["externs/chrome.js" "externs/chrome_extensions.js"]}
最後獲得的project.clj爲
(defproject crx-demo "0.1.0-SNAPSHOT" :description "crx-demo" :urnl "http://fsjohnhuang.cnblogs.com" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"] [org.clojure/clojurescript "1.9.908"] [binaryage/devtools "0.9.4"] [com.cemerick/piggieback "0.2.2"]] :plugins [[lein-cljsbuild "1.1.7"] [lein-doo "0.1.7"] [lein-ancient "0.6.12"]] ;; 經過`lein ancient upgrade` 或 `lein ancient upgrade:plugins`更新依賴項 :clean-targets ^{:protect false} [:target-path "out" "resources/public/background" "resources/public/content_scripts" "resources/public/browser_action"] :jvm-opts ["-Xmx1g"] :repl-options {:port 9000 :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} :cljsbuild {:builds [{:id "browser_action" :source-paths ["src/browser_action"] :compiler {:main browser-action.core :output-to "resources/public/browser_action/js/ignoreme.js" :output-dir "resources/public/browser_action/js/out" :asset-path "browser_action/js/out" :optimizations :none :source-map true :source-map-timestamp true :externs ["externs/chrome.js" "externs/chrome_extensions.js"] :preloads [devtools.preload] :external-config {:devtools/config {:features-to-install [:formatters :hints :async]}}}} {:id "content_scripts" :source-paths ["src/content_scripts"] :compiler {:main content-scripts.core :output-to "resources/public/content_scripts/js/content_scripts.js" :output-dir "resources/public/content_scripts/js/out" :asset-path "content_scripts/js/out" :optimizations :whitespace :source-map true :source-map-timestamp true :externs ["externs/chrome.js" "externs/chrome_extensions.js"] :preloads [devtools.preload] :external-config {:devtools/config {:features-to-install [:formatters :hints :async]}}}}]} :aliases {"build" ["cljsbuild" "auto" "browser_action" "content_scripts"] "test" ["doo" "node" "test-proj"]})
隨着對cljs的應用的深刻,我會逐步完善上述配置^_^
尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohn... ^_^肥仔John
http://astashov.github.io/blo...
https://github.com/emezeske/l...
https://nvbn.github.io/2014/1...
https://github.com/binaryage/...
https://clojurescript.org/too...
https://github.com/google/clo...