前言

 磨刀不误砍柴工,本篇将介绍如何搭建Chrome插件的ClojureScript开发环境。
具体工具栈:vim(paredit,tslime,vim-clojure-static,vim-fireplace) + leiningen(lein-cljsbuild,lein-doo,lein-ancient) + com.cemerick/piggieback

写得要爽

 首先抛开将cljs编译为js、调试、测试和发布等问题,首先第一要务是写得爽~
 cljs中最让人心烦的就是括号(),过去我想能否改个语法以换行来代替括号呢?而paredit.vim正好解决这个问题。

安装

在.vimrc中添加

Plugin 'paredit.vim'

在vim中运行

:source %
:PluginInstall

设置<Leader>

" 设置<Leader>键
let mapleader=','
let g:mapleader=','

用法

  1. 输入([{",会自动生成)]}",并且光标位于其中,vim处于insert状态;
  2. normal模式时,输入<Leader>+W会生成括号包裹住当前光标所在的表达式;
  3. normal模式时,输入<Leader>+w+[会生成[]包裹住当前光标所在的表达式;
  4. normal模式时,输入<Leader>+w+"会生成""包裹住当前光标所在的表达式。

更多用法就通过:help paredit查看paredit的文档即可。

编译环境

 cljs要被编译为js后才能被运行,这里我采用leiningen。
在shell中运行

# 创建工程
$ lein new crx-demo
$ cd crx-demo

工程目录中的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"]               ;; 通过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 ActionPage ActionContent ScriptsBackground等均是相对独立的模块相互并不依存,并且它们运行的方式和环境不尽相同,因此将它们作为独立子项目配置、编译和优化更适合。
  然后新建resources/public/img目录,并附上名为icon.jpg的图标即可。
&esmp;然后在resources/public下新建manifest.json文件,修改内容如下

{"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输出不兼容的问题。

Browser Action/Popup运行环境

 这里有个限制,就是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运行环境

 由于content scripts是直接运行脚本,没有页面让我们如popup那样控制脚本加载方式和顺序,因此只能通过optimizations :whitespace将所有依赖打包成一个js文件了。也就是说编译起来会相对慢很多~很多~多~~~

开发得爽

 到这里我们似乎可写上一小段cljs,然后编译运行了。且慢,没有任何智能提示就算了,还时不时要上网查阅API DOC,你确定要这样开发下去?

在vim中查看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

 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 = 1vmap <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.logjs/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

发布前引入externs

 辛苦开发后我们将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...

Chrome Extension in CLJS —— 搭建开发环境相关推荐

  1. 仿掘金社区全栈项目开发(一)-搭建开发环境

    整个项目的技术栈 搭建开发环境 linux操作系统 我是直接买的阿里云服务器,没有用虚拟机. 常用linux命令 查看linux系统 lsb_release -a 查看操作系统的信息 uname -a ...

  2. CRM客户关系管理系统开发第一讲——搭建开发环境

    这个小项目是我们学习完Spring,Hibernate,Struts2这三个框架后,为了加深对它们的理解所做的SSH项目,为CRM客户关系管理系统. CRM客户关系管理系统的概述 什么是CRM客户关系 ...

  3. PCL-1.8.1从源码搭建开发环境三(QHULL库的编译)

    原文首发于微信公众号「3D视觉工坊」:PCL-1.8.1从源码搭建开发环境三(QHULL库的编译) 首先,介绍一下QHull库. QHull是一个开源的程序软件,用来研究解决凸包问题,生成凸包形体.官 ...

  4. PCL-1.8.1从源码搭建开发环境二(FLANN库的编译)

    原文首发于微信公众号「3D视觉工坊」,PCL-1.8.1从源码搭建开发环境二(FLANN库的编译) 首先,快速近似最近邻搜索库FLANN-Fast Library for Approximate Ne ...

  5. Android移动APP开发笔记——最新版Cordova 5.3.1(PhoneGap)搭建开发环境

    引言 简单介绍一下Cordova的来历,Cordova的前身叫PhoneGap,自被Adobe收购后交由Apache管理,并将其核心功能开源改名为Cordova.它能让你使用HTML5轻松调用本地AP ...

  6. 《iOS 8开发指南(第2版)》——第1章,第1.3节工欲善其事,必先利其器——搭建开发环境...

    本节书摘来自异步社区<iOS 8开发指南(第2版)>一书中的第1章,第1.1节1.3 工欲善其事,必先利其器--搭建开发环境,作者 管蕾,更多章节内容可以访问云栖社区"异步社区& ...

  7. (001) RN开发之Mac搭建开发环境

    接触RN第一步:React Native中文网 搭建开发环境 必须安装的依赖有:Node.Watchman 和 Xcode. 我们推荐使用Homebrew来安装 Node 和 Watchman.在命令 ...

  8. 001.搭建开发环境

    搭建开发环境 课程内容:搭建AS3开发环境,写第一个AS3程序 课程目的:先入为主 知识点: 1.  开发环境配置 2.  使用FlashDevelop创建AS3工程 3.  在FlashDevelo ...

  9. 一个快速实现彩屏应用的跨平台快速原型开发工具平台,最重要的是还免费!8ms.xyz平台原以为是单片机版墨刀,今天上去玩了才知道平台厉害的很,基于WEB端免搭建开发环境,跑的还是C代码编译出来的程序!

    哈哈哈哈,最近发现一个好用的在线编译.下载.烧录的跨平台快速原型开发工具平台,名字好记–8ms,单看名字是真的不知道干嘛的,不知道为啥叫这个?不多想了,好用就得分享给大家,独乐乐不如众乐乐呀-- 好用 ...

最新文章

  1. mysql 8.0什么时候发布_MySQL 8.0.22正式发布
  2. Android XML小工具
  3. 一ElasticSearch安装启动
  4. NSURLProtocol 拦截 NSURLSession 请求时body丢失问题解决方案探讨
  5. https开头的网址是什么意思_我想打这个面试官,他给我挖坑,问我:URI中的 “//” 有什么用?...
  6. Tyvj P1463 智商问题 分块
  7. Scikit-learn 秘籍 第三章 使用距离向量构建模型
  8. NSUserDefaults写作和阅读对象定义自己
  9. 初识数据库——Mysql入门
  10. python 输出 2到n的素数 附源码 注释超详细。。。
  11. Excel VBA 学习总结 - 基础知识
  12. Uniapp实现实时音视频的基础美颜滤镜功能
  13. matlab 误码曲线,用matlab画误码率曲线
  14. 平安智盈人寿保险计算
  15. 英语六级口语 计算机,关于四六级口语,你所要知道的一些事
  16. 汇编语言:8421 BCD码加减法的修正问题
  17. 俄罗斯 搜索引擎 邮箱创建
  18. 18个月自学AI,2年写就三万字长文,过来人教你如何掌握这几个AI基础概念
  19. invalid vcs root mapping 怎么解决_一加黑鲨华硕OPPO等手机root后微信指纹支付不可用怎么解决...
  20. android 广告视频,Android开屏视频广告

热门文章

  1. java创建文件和目录
  2. 设置nginx 防止上传恶意脚本
  3. java学习笔记(七)----异常
  4. android studio换主题,为Android Studio换上一副更加好看的主题
  5. 字典写入excel_使用Python扫描邮件/填写Excel表格实现办公自动化
  6. POJ3114强连通+spfa
  7. LA3602DNA序列
  8. hdu1561 树形dp
  9. poj1182 and 携程预赛2第一题 带权并查集
  10. php 数学函数bc的使用(浮点数计算)