Sweet.js 用 Readtables 编译 JSX

from http://jlongster.com/Compiling-JSX-with-Sweet.js-using-Readtableshtml

JSX http://facebook.github.io/react/docs/jsx-in-depth.html 是一个 Facebook 项目,给 js 嵌入了 xml-like 语言,它是 React http://facebook.github.io/react/ 中标志性的使用特点。不少人喜欢它而且发现它很是好用。不幸的是他须要独立的编译器,而且不能和其余语言混合或者扩展。我用 sweet.js 宏 http://sweetjs.org/ 实现了一个 JSX “编译器” https://github.com/jlongster/jsx-reader,于是你能够把 jsx 和其余任何宏语言扩展一块儿放手边随时供使用。node

我预见,预见有人能给 js 语言添加一些难懂的特性,好比模式匹配 https://github.com/natefaubion/sparkler ,而后我只须要安装模块而后使用它。在 js 影响深远的今天,我认为这种语言扩展能力是很是重要滴。react

这很重要,不只仅是由于我或者你能拥有原生 goroutines https://gobyexample.com/goroutines 或者 原生持久数据解构 http://swannodette.github.io/mori/ 语法 。它更使人难以置信的是,咱们用的野生的特性,可能成为 ES 标准的一部分,甚至成为标准的 JS 的一部分。将来的 js 会因为咱们的反馈变得更好。咱们须要模块化的方式来扩展 js,一但这个可行,会无缝的与无数的扩展共享成果。webpack

我不许备解释为啥 sweet.js 宏是这个目标的答案。若是你想听更多,看个人 JSConf 2014 演讲 https://www.youtube.com/watch?v=wTkcGprt5rU 。若是你准备写负面的评论,请先阅读这篇 http://jlongster.com/Compiling-JSX-with-Sweet.js-using-Readtables#concernedgit

我为啥在 sweet.js 中实现一个 JSX 呢?若是你使用 JSX,如今你能够在任何其余的可用宏 https://www.npmjs.org/search?q=sweet-macros 边上使用 jsx-reader https://github.com/jlongster/jsx-reader。想要原生语法给持久化数据结构?继续读下去。。。es6

遇到的问题

jsx 这么工做:xml 元素转换为简单的 js 对象。github

var div = <div>
  <h1>{ header }</h1>
</div>;

这转化为:web

var div = React.Dom.div(null, React.DOM.h1(null, header));

http://sweetjs.org/ 直到这周 jsx 都没法把它实现,是什么缘由让这作到了? 可读表 Readtables http://sweetjs.org/doc/main/sweet.html#reader-extensions正则表达式

我简单解释下 sweet.js 的一些技术环境。Sweet.js 主要工做在口令层面,不是 AST,AST是惟一能扩展语言的组成部分的方式 见 *。其中的算法大量的来自 10 多年的 Lisp Scheme 社区尤为是 Racket http://docs.racket-lang.org/ 的工做基础之上算法

咱们的想法是,你在一个轻量的口令树之上提供语言的宏定义,而后展开这些口令。定制的模式匹配 http://sweetjs.org/doc/main/sweet.html#rule-macros 和 全自动卫生 http://sweetjs.org/doc/main/sweet.html#hygiene 让它能够作很是复杂的扩展,而且你能够从 sourcemaps 上得到更多。

生成的管道看起来像这样:

  • read 读一段字符串代码,产生一个口令树。它会消除歧义的正则表达式语法,分类,以及其余的有趣的东西。你会获得一个很漂亮的口令树,上面是原子的语法片断。它是一颗用 {}()[] 做为叶子来做为分隔符口令的树。
  • expand 遍历口令树,展开任何找到的宏。它用名字查找宏,用剩下的语法来调用宏。这个阶段还会作一个轻快的解析,好比指出一些不合法的表达式
  • parse 处理展开后的树,生成一些真正的 js AST。目前 sweet.js 使用了一个打了一些补丁的 esparima 版原本作这个工做
  • generate 从 AST 生成最后的 js 代码。 sweet.js 使用 escodegen 来作这个工做

expand 阶段本质上是给语言解析器添加了展开性。这对不少特性都很好。好比 类型 模块 等那些须要整个程序的信息,还有那些在解析 parse 阶段或者生成阶段最好有 AST 支持的特性。如今咱们尚未能展开到那个阶段。

不多有特性须要扩展到 read 阶段, JSX 就是其中之一。 Lisp 早就有一个叫作 readtables 的东西了,我最近意识到咱们的 js 须要相似的东西,以支持 JSX 的实现。所以我实现了它 https://github.com/mozilla/sweet.js/pull/340

有几个缘由 jsx 须要做为一个 reader 生效而不是做为宏:

  1. 最重要的,闭合标签 是彻底无效的 js 语法。默认的 reader 认为这开始了一个正则表达式,可是他不能找到它的结束符号。所以他直接在 read 阶段抛出错误
  2. jsx 有很是特殊的关于空白的处理规则。空白元素须要被保存,例如 兄弟表达式之间插入了一个空格。宏不知道任何空白的故事。

Reader 扩展容许你安装一个自定义的 reader 当在源码中遇到一个特殊的单词的时候调用。你能读取你须要的足够多的源码,而后返回一个口令组。reader 扩展只能被用标点的 punctuators 触发。(好比 < % 等符号),所以你不能作好比改变引号起做用的方式这类可怕的事情。

一个可读表是一个给 reader 扩展用的字符表。在这里 http://sweetjs.org/doc/main/sweet.html#reader-extensions 的这个文档中阅读更多来深刻了解。

npm 上可用的: jsx-reader

如今咱们有了可读表,咱们能够实现 jsx 啦!我已经用 jsx-reader https://github.com/jlongster/jsx-reader 作到了。他是一个文字的 jsx 编译器端口,机智的作到了全部的空白规则以及其余的边缘状况。(但愿如此)

加载一个 reader 扩展,传入模块名给 sweet.js 用 -l 编译 sjs 。这是全部的步骤:

$ npm install sweet.js
$ npm install jsx-reader
$ sjs -l jsx-reader file.js

固然你也能够加载任何其余的宏,一块儿做用在你的文件上。这是一个组合的很漂亮的语言扩展(试试 es6-macroshttps://github.com/jlongster/es6-macros)

我已经建立了一个 webpack loader https://github.com/jlongster/sweetjs-loader 和一个 gulp loader https://github.com/jlongster/gulp-sweetjs 并且是最新的支持 readtable 加载的。

这是测试版的软件我在一些小的原生的 jsx 编译器的测试用例上测试经过了,此外它也在一些大文件上工做良好。However,依然有一些小 bugs 和边缘状况须要被人们发现他以后修补。

你不只仅能得到可靠的 sourcemaps (传入 -c 给 sjs),并且你也会比原生 jsx complier 有更好的错误提示。例如:若是你忘了关闭一个标签:

var div = <div>
    <h1>Title</h1>
    <p>
</div>

你会获得一个漂亮的,感受的错误信息,明确的指出你的代码中的错误

SyntaxError: [JSX] Expected corresponding closing tag for p
4: </div>
     ^
    at Object.readtables.readerAPI.throwSyntaxError (/Users/james/tmp/poop/node_modules/sweet.js/lib/parser.js:5075:23)

有一个退步就是,这比原生的 jsx compiler 慢。一个 2000 行代码的大文件会花费 ~.7s 来编译(排序了预热时间,由于你会在大多数项目中使用监听器 watcher),原生的只要 ~0.4s。实际上,这不是特别明显,由于大多数文件是不少的更小的东西,大部分在几百毫秒就编译完了。固然,sweet.js 会更努力滴优化这个时间滴。

例子: 持久化数据结构

React 和持久化数据结构一块儿工做会表现得更好。问题是 js 原生没有它们,可是幸运的是有 mori http://swannodette.github.io/mori/ 这些库可用。只是你不再能用对象字面值了;你不得不 mori.vector(1,2,3) 来代替 [1,2,3];

若是咱们给 mori 实现一个字面的语法会怎样?可能你就用 #[1,2,3] 来建立持久的矢量,而 #{x: 1, y: 2} 来建立持久的表 map 。那也太棒了吧!(不幸的是我如今还没作到,但我很是渴望作到这点。。。)

如今每一个用 jsx 的人均可以在 React 中用个人字面语法来持久化数据结构。这真的是一个给力的工具包。

jsx 的 read 算法

给 js 添加新的语法,特别拥有大块展现区域的好比 jsx。必须仔细的实现。它必须 100% 向后兼容,而且和 ES.next 特性同样在脑中完成。

jsx-reader 和 原生 jsx 编译器都查找 < 口令并触发一个 jsx 表达式的解析。虽然这个有一个关键的不一样。你可能注意到 jsx-reader 做为一个 reader,被源代码调用的时候是没有环境的。原生的 jsx 编译器用被猴子们打了补丁 monkeypatches 的 esprima 来调用 < ,当解析一个表达式的时候,所以它更容易保证正确的解析。 < 在 js 表达式中从不会有效,所以它能这么用它。

jsx-reader 也是用 < 调用,当它在源码的任何地方出现的时候,甚至是比较大小的操做符的时候。这让人提心吊胆的;咱们须要更仔细。可是我想到了一个可行的算法。你不会一直须要一个 ast。

jsx-reader 开始解析 < 而后任何它后面的东西做为 jsx 表达式,若是它遇到一些肯定不指望的点,它停下来。大多数时候,它能很早的发觉 < 以后是或者不是 jsx 表达式。算法来了:

  1. Read <
  2. Read 一个标识符 失败了 bail
  3. If 接下来的不是 > :

    1. 跳过空白
    2. 读取一个标识符。 失败了 bail
    3. if 下一个是 > 跳转到 4.1
    4. 读取 =
    5. if 下一个是 { 读取 js 表达式 相似 4.3.1
    6. if 下一个是 < 跳转到 1
    7. 不然,读取一个字符串
    8. if 下一个是 > 跳转到 4.1
    9. 不然 跳转到 3.1
  4. 不然:

    1. 读取 >
    2. 读取任何原始文本直到 { 或者 <
    3. if 下一个是 {
    4. 读取 {
    5. 读取全部的 js 口令直到 }
    6. 读取 }
    7. 跳转到 4.2
    8. if 下一个是 <:
    9. if 下一个是 < 和 / 跳转到 5
    10. 不然 跳转到 1
    11. if 是文件的底部, bail
  5. 读取 <
  6. 企图读取一个正则。若是成功了 bail
  7. 读取 /
  8. 读取一个标识符 确保他匹配了打开的标识符
  9. 读取>

我很快的打出了这些,可能有更好的方式啦,可是你应该了解了这思路。主要是通常状况下很容易消除歧义,可是全部的边缘状况都须要生效。边缘状况不是特别高性能,由于咱们的 reader 可能要作不少工做而后丢弃它,可是 99.9% 的状况下不会发生这种事情。

在咱们的算法中,若是咱们说 read 而不说相应的的 “若是失败了 bail”,它会抛出一个错误。咱们依然能够给出好的错误提示,当遇到二义性的 js 边缘状况的时候。

这儿有几个咱们的 reader bails的地儿:

  • if (x < y) {} 它 bails 由于它查找一个 y 后面的属性 以及 ) 不是一个合法的标识符字符
  • if (x < y > z) {} 它 bails 是由于它 reads 全部的到文件末尾的方式但没有找到一个闭合的标签。这只发生在顶级的元素上,并且是个性能的最差的状况。可是 x < y > z 不会作你想要的,并且没人会这么用
  • if (x < div > y < /foo> /) {} 这是最难懂的状况,他是彻底有效的 js 。它 bails 由于它最后读取了一个正则

咱们获取这个实际的优势:表达式像 x < y foo 在 js 中不会存在。这儿,它既不找 = 来解析属性,也不找 > 来关闭元素,而是直接报错,若是它没找到的话。

你担忧吗?

宏调用,弄混了一些人,还有不少迟疑的认为它们是好用的东西。你可能也这么想,并且争论一件事情:像 readtables 这样的东西标志着咱们进入了很深的地方。

我请大家仔细想一想 sweet.js。给它 5 分钟。也许几个小时。玩玩它:设置一个 gulp 监控 wathcer,从 npm 安装一些宏而且用它。别直接反对它你实际上除非你理解了咱们尝试用它来解决的一些问题。许多人们给出的争论都没有很容易体会到那种场景(但有一些有!)

无论那些,甚至你认为这是错误的方法。他固然是有效的。软件工业对我老说最困惑的一件事情就是:咱们对其余人到底能有多恶毒,因此请作些建设性的事情。

今天就试试 jsx-reader https://github.com/jlongster/jsx-reader 若是发现 bugs https://github.com/jlongster/jsx-reader/issues

以上;

相关文章
相关标签/搜索