内容简介:随着 Firefox 57 的到来,之前维护的一个浏览器插件闲话少说,下面的内容依次会介绍 cljs 的工作机制、开发环境,如何让 cljs 适配浏览器插件规范,以及重写 gooreplacer 时的一些经验。本文的读者需要对 Clojure 语言、浏览器插件开发一般流程有基本了解,并且完成 ClojureScript 的
随着 Firefox 57 的到来,之前维护的一个浏览器插件 gooreplacer 必须升级到 WebExtensions 才能继续使用,看了下之前写的 JS 代码,毫无修改的冲动,怕改了这个地方,那个地方突然就 broken 了。因此,这次选择了 cljs,整体下来流程很顺利,除了迁移之前的功能,又加了更多功能,希望能成为最简单易用的重定向插件 :-)
闲话少说,下面的内容依次会介绍 cljs 的工作机制、开发环境,如何让 cljs 适配浏览器插件规范,以及重写 gooreplacer 时的一些经验。
本文的读者需要对 Clojure 语言、浏览器插件开发一般流程有基本了解,并且完成 ClojureScript 的 Quick Start 。对于 Clojure,我目前在 sf 上有 一套视频课程 ,供参考。
ClojureScript 工作机制
ClojureScript 是使用 Clojure 编写,最终编译生成 JS 代码的一个 编译器 ,在编译过程中使用 Google Closure Compiler 来优化 JS 代码、解决 模块化引用 的问题。整体工作流程如下:
Cljs 还提供 与原生 JS 的交互 、 集成 第三方类库 的支持,所以,只要能用 JS 的地方,都能用 cljs,
开发环境准备
开发 cljs 的环境首选 lein + figwheel ,figwheel 相比 lein-cljsbuild 提供了热加载的功能,这一点对于开发 UI 很重要!
对于一般的 cljs 应用,基本都是用一个 script 标签去引用编译后的 js 文件,然后这个 js 文件再去加载其他依赖。比如:
<html> <body> <scripttype="text/javascript"src="js/main.js"></script> </body> </html>
js/main.js 是 project.clj 里面指定的输出文件,它会去加载其他所需文件,其内容大致如下:
var CLOSURE_UNCOMPILED_DEFINES = {}; var CLOSURE_NO_DEPS = true; if(typeof goog == "undefined") document.write('<script src="js/out/goog/base.js"></script>'); document.write('<script src="js/out/goog/deps.js"></script>'); document.write('<script src="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>if (typeof goog != \"undefined\") { goog.require(\"figwheel.connect.build_dev\"); }</script>"); document.write('<script>goog.require("hello_world.core");</script>');
消除 inline script
对于一般的 Web 项目,只引用这一个 js 文件就够了,但是对于浏览器插件来说,有一些问题,浏览器插件出于安全因素考虑,是 不让执行 incline script ,会报如下错误
为了去掉这些错误,手动加载 js/main.js 里面动态引入的文件,require 所需命名空间即可,修改后的 html 如下:
<html> <body> <scriptsrc="js/out/goog/base.js"></script> <scriptsrc="js/out/cljs_deps.js"></script> <scriptsrc="js/init.js"></script> </body> </html>
其中 init.js 内容为:
// figwheel 用于热加载,这里的 build_dev 其实是 build_{build_id},默认是 dev goog.require("figwheel.connect.build_dev"); // 加载为 main 的命名空间 goog.require("hello_world.core");
这样就可以正常在浏览器插件环境中运行了。
对于 background page/option page/popup page 这三处都可采用这种措施,但是 content script 没法指定 js 脚本加载顺序,采用的做法是设置 cljsbuild 的 optimizations
为 whitespace,得到一个文件,然后引用这一个就可以了, 这个方法不是很完美 ,采用 whitespace 一方面使编译时间更长,另一方面使失去了热加载的功能,gooreplacer 里面只使用了 background page 与 option page,所以这个问题也就避免了。
区分 dev 与 release 模式
这里的 dev 是指正常的开发流程,release 是指开发完成,准备打包上传到应用商店的过程。
在 dev 过程中,推荐设置 cljsbuild 的 optimizations
为 none,以便得到最快的编译速度;
在 release 过程中,可以将其设置为 advanced
,来压缩、优化 js 文件,以便最终的体积最小。
为了在两种模式中复用使用的图片、css 等资源,gooreplacer 采用了软链的方式,供大家参考:
. ├── css │ └── main.css ├── dev │ ├── background │ │ ├── index.html │ │ └── init.js │ ├── manifest.json -> ../manifest.json │ └── option │ ├── css -> ../../css │ ├── img -> ../../img │ ├── index.html │ └── init.js ├── img │ ├── 16.png │ ├── 32.png │ ├── 48.png │ └── gooreplacer.ico ├── manifest.json └── release ├── background │ ├── index.html │ └── main.js ├── manifest.json -> ../manifest.json └── option ├── css -> ../../css ├── img -> ../../img ├── index.html └── main.js
其次,为了方便开启多个 figwheel 实例来分别编译 background、option 里面的 js,定义了多个 lein 的 profiles,来指定不同环境下的配置,具体可参考 gooreplacer 的 project.clj 文件。
externs
在 optimizations 为 advanced 时,cljs 会充分借用 Google Closure Compiler 来压缩、混淆代码,会把变量名重命名为 a b c 之类的简写,为了不使 chrome/firefox 插件 API 里面的函数混淆,需要加载它们对应的 externs 文件,一般只需要这两个 chrome_extensions.js 、 chrome.js 。
测试环境
cljs 自带的 test 功能比较搓,比较好用的是 doo ,为了使用它,需要先提前安装 phantom 来提供 headless 环境,写好测试就可以执行了:
lein doo phantom {build-id} {watch-mode}
非常棒的一点是它也能支持热加载,所以在开发过程中我一直开着它。
<html> <body> <scripttype="text/javascript"src="out/main.js"></script> </body> </html>
re-agent
re-agent 是对 React 的一个封装,使之符合 cljs 开发习惯。毫无夸张的说,对于非专业前端 程序员 来说,要想使用 React,cljs 比 jsx 是个更好的选择, Hiccup-like 的语法比 jsx 更紧凑,不用再去理睬 webpack , babel 等等层出不穷的 js 工具,更重要的一点是 immutable 在 cljs 中无处不在,re-agent 里面有自己维护状态的机制 atom,不在需要严格区分 React 里面的 props 与 state。
了解 re-agent 的最好方式就是从它 官网给出的示例 开始,然后阅读 re-frame wiki 里面的 Creating Reagent Components ,了解三种不同的 form 的区别,98% gooreplacer 都在使用 form-2。如果对原理感兴趣,建议也把其他 wiki 看完。
re-agent 还有一点比较实用,提供了对 React 原生组件的转化函数: adapt-react-class ,使用非常简单:
(def Button (reagent/adapt-react-class (aget js/ReactBootstrap "Button"))) [:div [:h2 "A sample title"] [Button "with a button"]]
这样就不用担心 React 的类库不能在 cljs 中使用的问题了。
说到 re-agent,就不能不提到 om.next ,这两个在 cljs 社区里面应该是最有名的 React wrapper,om.next 理念与使用难度均远高于 re-agent,初学者一般不推荐直接用 om.next。感兴趣的可以看看这两者之间的比较:
坑
宏
cljs 里面加载宏的机制有别于 Clojure,一般需要单独把宏定义在一个文件里面,然后在 cljs 里面用 (:require-macros [my.macros :as my])
这样的方式去引用,而且宏定义的文件名后缀必须是 clj 或 cljc,不能是 cljs,这一点坑了我好久。。。
由于宏编译与 cljs 编程在不同的时期,所以如果宏写错了,就需要把 repl 杀掉重启来把新的宏 feed 给 cljs,这点也比较痛苦,因为 repl 的启动速度实在是有些慢。这一点在 Clojure 里面虽然也存在,但是 Clojure 里面一般 repl 开了就不关了,直到电脑重启。
IDE
Clojure 里面采用 Emacs + Cider 的开发环境非常完美,但是到了 cljs 里面,开发流程没有那么平滑,总是有些磕磕绊绊,也给 cider 提了个 issue ,貌似一直没人理,支持确实不好,不过有了 figwheel,在一定程度上能弥补这个缺陷。在 Emacs 里面配置 repl 可参考:
Cider 默认会使用 rhino 作为 repl 求值环境,这个在开发浏览器插件时功能很有限,但是对于查看函数定义还是可以的。可以根据需要换成 figwheel。
总结
ClojureScript 可以算是 Clojure 语言的一个 杀手级 应用 ,React 使得后端程序员也能快速作出美观实用的界面。ClojureScript + React,用起来不能再开心啦!
JS 社区里面层出不穷的框架每次都让跃跃欲试的我望而却步,有了 cljs,算是把 Lisp 延伸到了更宽广的“领土”。最近看到这么一句话,与大家分享:
也许 Lisp 不是解决所有问题最合适的语言,但是它鼓励你设计一种最合适的语言来解决这个难题。
出处忘记了,大体是这么个意思。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- gulp插件解决浏览器缓存问题
- gulp插件解决浏览器缓存问题
- 用VueJS写一个Chrome浏览器插件
- ExtAnalysis:一款浏览器插件安全分析框架
- OSCHINA Chrome 浏览器插件 v1.4 发布
- OSCHINA Chrome 浏览器插件 v1.6 发布
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
反应式设计模式
Roland Kuhn、Brian Hanafee、Jamie Allen / 何品、邱嘉和、王石冲、林炜翔审校 / 清华大学出版社 / 2019-1-1 / 98.00 元
《反应式设计模式》介绍反应式应用程序设计的原则、模式和经典实践,讲述如何用断路器模式将运行缓慢的组件与其他组件隔开、如何用事务序列(Saga)模式实现多阶段事务以及如何通过分片模式来划分数据集,分析如何保持源代码的可读性以及系统的可测试性(即使在存在许多潜在交互和失败点的情况下)。 主要内容 ? “反应式宣言”指南 ? 流量控制、有界一致性、容错等模式 ? 得之不易的关于“什么行不通”的经验 ? ......一起来看看 《反应式设计模式》 这本书的介绍吧!