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

以上;

相關文章
相關標籤/搜索