Chrome Extension in CLJS —— 搭建开发环境
前言
磨刀不误砍柴工,本篇将介绍如何搭建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=','
用法
- 输入
(
、[
、{
和"
,会自动生成)
、]
、}
和"
,并且光标位于其中,vim处于insert状态; - normal模式时,输入
<Leader>+W
会生成括号包裹住当前光标所在的表达式; - normal模式时,输入
<Leader>+w+[
会生成[]
包裹住当前光标所在的表达式; - 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 Action、Page Action、Content Scripts和Background等均是相对独立的模块相互并不依存,并且它们运行的方式和环境不尽相同,因此将它们作为独立子项目配置、编译和优化更适合。
然后新建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 = 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
发布前引入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/fsjohnhuang/p/7622745.html _肥仔John
参考
http://astashov.github.io/blog/2014/07/30/perfect-clojurescript-development-environment-with-vim/
https://github.com/emezeske/lein-cljsbuild
https://nvbn.github.io/2014/12/07/chrome-extension-clojurescript/
https://github.com/binaryage/cljs-devtools/blob/master/docs/configuration.md
https://clojurescript.org/tools/testing
https://github.com/google/closure-compiler/wiki/Externs-For-Common-Libraries
Chrome Extension in CLJS —— 搭建开发环境的更多相关文章
- 从0开发3D引擎(三):搭建开发环境
本系列使用Reason语言,因此需要搭建它的开发环境. 上一篇博文 从0开发3D引擎(二):准备预备知识 搭建开发环境 建议使用VSCode编辑器来开发Reason,因为它的插件支持得最好. 具体搭建 ...
- Vagrant 搭建开发环境实践
介绍 Development Environments Made Easy -官网标题 vagrant是一个命令行的虚拟机管理程序.用于简化搭建开发环境. vagrant使用ruby语言基于Chef ...
- GJM : Unity3D HIAR -【 快速入门 】 二、搭建开发环境
感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...
- maven实战(01)_搭建开发环境
一 下载maven 在maven官网上可下载maven:http://maven.apache.org/download.cgi 下载好后,解压.我的解压到了:D:\maven\apache-mave ...
- Android移动APP开发笔记——最新版Cordova 5.3.1(PhoneGap)搭建开发环境
引言 简单介绍一下Cordova的来历,Cordova的前身叫PhoneGap,自被Adobe收购后交由Apache管理,并将其核心功能开源改名为Cordova.它能让你使用HTML5轻松调用本地AP ...
- Win10 IoT C#开发 1 - Raspberry安装IoT系统及搭建开发环境
Windows 10 IoT Core 是微软针对物联网市场的一个重要产品,与以往的Windows版本不同,是为物联网设备专门设计的,硬件也不仅仅限于x86架构,同时可以在ARM架构上运行. The ...
- java攻城师之路(Android篇)--搭建开发环境、拨打电话、发送短信、布局例子
一.搭建开发环境 1.所需资源 JDK6以上 Eclipse3.6以上 SDK17, 2.3.3 ADT17 2.安装注意事项 不要使用中文路径 如果模拟器默认路径包含中文, 可以设置android_ ...
- python入门到精通[一]:搭建开发环境
摘要:Python认识,及在windows和linux上安装环境,测试是否安装成功. 1.写在前面 参加工作也有5年多了,一直在做.net开发,近一年有做NodeJS开发.从一开始的不习惯,到逐步适应 ...
- Android开发系列之搭建开发环境
接触Android好久了,记得09年刚在中国大陆有点苗头的时候,我就知道了google有个Android,它是智能机操作系统.后来在Android出1.5版本之后,我第一时间下载了eclipse开发工 ...
随机推荐
- MongoDB对应SQL语句
-------------------MongoDB对应SQL语句------------------- 1.Create and Alter 1. sql: crea ...
- 消息摘要技术(MD5)
1.使用消息摘要技术对密码加密 数据库存储的是经过消息摘要技术加密之后的信息, 避免保存密码明文,提升了系统安全性 必要性说明: 如果存储明文密码,数据库系统管理员和攻破系统的黑客是可以拿到你的所有信 ...
- spring mvc:ueditor跨域多图片上传失败解决方案
公司在开发一个后台系统时需要使用百度的UEditor富文本编辑器,应用的场景如下: UEditor的所有图片.js等静态资源在一个专有的静态服务器上: 图片上传在另外一台服务器上: 因为公司内部会使用 ...
- C# 异步编程2 EAP 异步程序开发
在前面一篇博文记录了C# APM异步编程的知识,今天再来分享一下EAP(基于事件的异步编程模式)异步编程的知识.后面会继续奉上TPL任务并行库的知识,喜欢的朋友请持续关注哦. EAP异步编程算是C#对 ...
- MySQL(七)MySQL常用函数
前言 上一篇给大家介绍了,MySQL常用的操作符其实已经是非常的详细了,现在给大家分享的是MySQL的常用函数.希望对我和对大家都有帮助. 一.字符串函数 1.1.LOWER.lcase(string ...
- ReentrantLock和synchronized的性能对比
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytpo8 ReentrantLock和内部锁的性能对比 Reentran ...
- 5.分析内核中断运行过程,以及中断3大结构体:irq_desc、irq_chip、irqaction
本节目标: 分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc.irq_chip.irqaction 在裸板程序中(参考stmdb和ldmia详解): 1.按键按下, 2 ...
- 团队作业10——beta阶段项目复审
小组的名字和链接 优点 缺点(bug报告) 最终名次 拖鞋大队 基本功能都实现了,符合用户的需求:每次都能按时完成博客,满足题目要求,所以作业完成的也比较优秀.较alpha版本新增了查重自定义的功能, ...
- 团队作业八——第二次团队冲刺(Beta版本)第3天
一.每个人的工作 (1) 昨天已完成的工作 对界面进行完善,并增加简单界面(包含简单界面内含的界面),简单模式与复杂模式的选择界面. (2) 今天计划完成的工作 做一下用户注册的功能和登录功能. (3 ...
- Java学习2——HelloWorld(编写第一个java程序)
编写 在自己的工作文件目录下(如上一篇中配置的classpath路径)创建HelloWorld.java文件,编写如下代码,并保存 public class HelloWorld { public s ...