使用 ClojureScript 开发浏览器插件

栏目: 编程语言 · Clojure · 发布时间: 5年前

内容简介:随着 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 代码、解决 模块化引用 的问题。整体工作流程如下:

使用 ClojureScript 开发浏览器插件

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 ,会报如下错误

使用 ClojureScript 开发浏览器插件

为了去掉这些错误,手动加载 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.jschrome.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 更紧凑,不用再去理睬 webpackbabel 等等层出不穷的 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 开了就不关了,直到电脑重启。

使用 ClojureScript 开发浏览器插件

IDE

Clojure 里面采用 Emacs + Cider 的开发环境非常完美,但是到了 cljs 里面,开发流程没有那么平滑,总是有些磕磕绊绊,也给 cider 提了个 issue ,貌似一直没人理,支持确实不好,不过有了 figwheel,在一定程度上能弥补这个缺陷。在 Emacs 里面配置 repl 可参考:

Cider 默认会使用 rhino 作为 repl 求值环境,这个在开发浏览器插件时功能很有限,但是对于查看函数定义还是可以的。可以根据需要换成 figwheel。

总结

ClojureScript 可以算是 Clojure 语言的一个 杀手级 应用 ,React 使得后端程序员也能快速作出美观实用的界面。ClojureScript + React,用起来不能再开心啦!

JS 社区里面层出不穷的框架每次都让跃跃欲试的我望而却步,有了 cljs,算是把 Lisp 延伸到了更宽广的“领土”。最近看到这么一句话,与大家分享:

也许 Lisp 不是解决所有问题最合适的语言,但是它鼓励你设计一种最合适的语言来解决这个难题。

出处忘记了,大体是这么个意思。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Web Application Hacker's Handbook

The Web Application Hacker's Handbook

Dafydd Stuttard、Marcus Pinto / Wiley / 2011-9-27 / USD 50.00

The highly successful security book returns with a new edition, completely updated Web applications are the front door to most organizations, exposing them to attacks that may disclose personal infor......一起来看看 《The Web Application Hacker's Handbook》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具