TypeScript, AtScript, ES Decorator

AtScript

前陣子花了些時間研究了 TypeScript 和一些相關的發展,包括了 Google Angular Team 的 AtScript 和推進 ES 標準的部分,會開始感興趣深入研究主要是因為Angular 2 說改用 TypeScript 寫,好奇為什麼會有這樣的發展才下去搜尋資料的,這篇文章算是記錄用的,不過其實離寫好已經一陣子了,因為剛好遇到 Modern Web Conf,想說拿這題目去分享,就讓文章晚點上線了,後來投影片還有補充些內容,這篇文章就沒再更新了,所以兩邊會有些差異就是~

ECMAScript 標準一直以來都是動態型別的,雖然資料有不同的型別,但是變數本身是沒限制型別的,而在 ECMAScript 發展的過程中,靜態型別第一次出現是在已經被廢棄的 ECMAScript 4 裡,網路上還可以找到一些資料,可以看看當時設計的語法,和現在常看到的:type的寫法很接近,後來這個設計也在 ActionScript 3 中被使用,微軟現在的 TypeScript 也是用這種寫法。那加入靜態型別的特性會有什麼好處呢,我認為有兩個主要的優點,第一個是可以讓程式碼更可靠,減少一些 bug 發生的機會,對於大型專案來說,多了這個限制的差距是蠻大的,另外一個優點則是 JS Engine 更好最佳化,以前也有提過現在的 V8 引擎就已經會判斷變數的型別會不會有變化來做最佳化了。

或許是因為微軟對於大型專案開發的關注比較多吧,他們於 2012 年推出了TypeScript,為 JavaScript 加入了靜態型別,用的語法很簡潔:

var i:int;
var message:string;

另外還提供了當時沒有的 class 和之前提過的定義檔等東西,TypeScript 一開始是基於 ECMAScript 5 設計的,不過在 ECMAScript 6 差不多定案後,微軟也開始著手把 ES5 based 改成 ES6 based,像是 class 就會改用 ES6 原生的,而 TypeScript 所提供的靜態型別檢查功能其實是靜態分析而已,也就是只有在把 .ts 檔案編譯成 .js 檔案時會做檢查,而由於 JavaScript 還沒有 type 的特性,所以這些型別的資訊其實在編譯過後都會被拿掉。目前除了 AngularJS 2 改用 TypeScript 之外,還有像Asana和 Mozilla 的Shumway都是用 TypeScript。

Google Angular Team 似乎對此還不夠滿足,因此他們開始發展AtScript,在 TypeScript 上再加入 annotation 的功能,名稱的 At 代表的是@這個符號,因為這個符號是很多語言寫 annotation 用的符號,自然 AtScript 也是用這個符號來標記 Annotation:

@Component({selector: 'foo'})
class MyComponent {
  @Inject()
  constructor(server:Server) {}
}

Annotation 簡單翻起來也是註解,不過他和 comment 不一樣,不是給人看,而是要給 compiler 和 JS engine 看的,而且實際上也會影響程式的一些運作,annotation 應該是一種完全沒有也不影響程式執行的 metadata,不過細分下去應該可以分為兩類,第一種是 Java 的 annotation,以 metadata 為主,像是物件的角色、物件間關係等,另外一種則是 decorator annotation,可以讓函數加上各種不同特性,其實就是 decorator pattern 的簡易語法,看到一些範例當中,最讓我覺得厲害的就是 memorize 了吧,如果程式引擎支援,加上一行 memorize 的 annotation 就可以讓那個函數自動有 memorize 特性,如果使用不支援此特性的引擎來執行程式,函數的輸出也不會有錯,就是沒有 memorize 的效果,效率會比較差,Python 中就有lru_cache這個 decorator 可以做到這樣的效果(Python 的 decorator 語法是提供 syntax sugar,不過寫法和其它語言的 annotation 很像):

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

AtScript 一個很重要的原則是這些附加的資訊,都要在 runtime 可以使用,所以就不像 TypeScript 那樣只是把不支援的東西拿掉而已,像上面費氏數列的程式碼如果改用 AtScript 寫會變成:

@lru_cache()
function fib(n) {
  if (n < 2) { return n; }
  return fib(n - 1) + fib(n - 2);
}

然後用 AtScript compiler 編譯過後會多上一段程式碼做類似下面的事情:

fib.annotations = [
  new lru_cache(),
];

這個annotations屬性在 runtime 時就是可以取用的資訊,目前 AtScript 的 annotation 就是比較偏重於 metadata 而不是 decorator,所以這些資料並不會直接讓函數有不同特性,而 AtScript 另外一個新東西 introspection 也是和 runtime 有關,是 TypeScript 所沒有的 runtime 時的型別檢查,JavaScript 要怎樣做執行階段的型別檢查呢?沒錯,基本上就是土法煉鋼,不過 AtScript 是引入一個 rtts(run time type assertion) 的 library 來做這件事,目前主要也是用 Angular Team 維護的assert.js,本來的 fib 再改寫一下:

function fib(n:number):number {
    if (n < 2) { return n; }
    return fib(n - 1) + fib(n - 2);
}

然後編譯過後大概會變成:

function fib(n) {
  assert.argumentTypes(n, number);
  if (n < 2) {
    return assert.returnType((n), number);
  }
  return assert.returnType((fib(n - 1) + fib(n - 2)), number);
}

可以看到不管是在函數開頭還是要回傳之前,都會多了用 assert.js 做型別檢查的程式碼,當然,多做的這些型別檢查是會造成效能影響的,所以 AtScript 把 runtime 的型別檢查分成兩個階段,開發階段和成品階段,成品階段,要上線的時候,就輸出不包含型別檢查的 js 程式碼,這樣就不會影響效能。AtScript 其實目前沒有自己的編譯器,而是使用 Google 的Traceur,Traceur 基本上是個 ES6 to ES5 compiler,不過實際上他還多一些非 ES6 標準的語法支援,包括了前面提到的 Type、Annotation,不過使用時要加些參數:

traceur --annotations true --type-assertions --types true fib.ats --out fib.js

ng-europe研討會,就有一場關於 AtScript 的演講:

裡面除了基本的介紹,為什麼會發展 AtScript 之外,還有很重要的未來發展,Angular Team 是有打算把 Type、Annotation 等等特性推回 ECMAScript 未來的標準之中的。在 ECMAScript 標準的發展上,其實早在之前就有一些變數型別相關的功能在討論,包括了typeguard,不過都沒有進到目前的 ECMAScript 6(2015),目前 AtScript 和 TypeScript 兩者正在逐漸互相同步,也有共同合作,而且 AtScript 還沒有嚴謹的 spec 文件,所以會看到官方發佈說 AngularJS 2 用 TypeScript 開發,而不是用 AtScript,目前看到 TC39 討論裡面,除了 type 之外,幫其它新東西提出 proposal 的,很令人意外,竟然是 Yehuda Katz,可以看到去年四月的會議記錄就有他提出 decorator 特性的討論,另外 TypeScript 的Issue 1557是關於在 TypeScript 中加入 AtScript 的 annotation 支援,Yehuda Katz 也有提到他正在整理相關資料,幾週後會在 TC39 會議提出,在他的 github 帳號上也可以找到相關的資訊,我個人對 Yehuda Katz 評價很高,不過實在是想不太到為什麼會是他跑出來推動這部分的發展,不過總之 Yehuda Katz 打算提出的是比現在 metadata 為主更進一步的 annotation,也就是包含像 Python decorator 特性的 annotation,如果真的順利成案,其實也不知道是好是壞,好的是一些程式碼可以更簡潔,壞的是 JavaScript 語法越來越多,入門要學的東西也變多很多。



Aster 與 PostCSS

台南

前端為了 performance 需求,把網站推上 server 時會需要把 JavaScript、CSS 之類的文字檔案合併和最小化,如果開發時寫的是 CoffeeScript 或是 SASS 之類的還需要先轉成 JavaScript 和 CSS 這些主流格式,要做這些動作其實第一個想到的是可以用 Makefile,優點是常見、各平台都有,不過寫起來並不像這幾年流行的 build tool 那樣直覺,而前端領域流行的主要是GruntGulp這兩個,兩者之中我個人是比較喜歡後起的 Gulp,不過前陣子意外發現一個看起來超正確的 build tool,叫aster,是 Ingvar Stepanyan 做的,他在 Zurich 的 Frontend Conference 2014 的演講算是比較大規模的發佈:

閱讀「Aster 與 PostCSS」全文

➡ 前一個月的文章