A Cup of CoffeeScript

coffee_logo.png

這個週末因緣際會之下看到了這個 - Web Spreadsheet in 99 Lines。進去逛逛之後發現作者的 code 竟然是使用 ES6 JavaScript 來寫的,當然同時也有 compile 出 ES5 的版本供瀏覽器跑。

加上日前觀看某 React.js 的影片裡面小小的秀了一下 CoffeeScript,喚醒了我內心對偷懶(?)的渴望,開始到處查找目前上手 ES6 開發的文章,最後輾轉回到了 CoffeeScript (WTF)。

怎麼會回到 CoffeeScript!? 當下的幾個考量如下:

  • ES6 其實感覺相對還是少人用,而且真的要走入各瀏覽器還需要一些時間
  • 先用 CoffeeScript,到時候還是可以 compile to ES6
  • 其實我只是想偷懶而已,不是真的想試 ES6

那為什麼不是更先進一點的 LiveScript 呢?

  • 生理上有點不能接受 LiveScript
  • 用的人比 CoffeeScript 少

說到生理上不能接受 LiveScript,其實對 CoffeeScript 我也是抱持一樣的想法。雖然 CoffeeScript 一副就是覺得用自己寫起來程式碼會簡單清楚又好讀的樣子,可能由於我本身是一個比較 old-fashioned 的人,我覺得完全不是這麼一回事… 還是傳統的 JavaScript 比較一目瞭然。

當然現在為了偷懶就必須要有所妥協。相較於 LiveScript,CoffeeScript 還是比較和藹可親一點(果然是一個越先進越不和藹的概念),可能以後有機會才會比較能接受 LiveScript 吧!

於是我週末花了一些時間把目前手上某個專案的部分 code 轉成 .coffee,並且把 build flow 用 grunt 建立起來。

是說練習寫了 1000 行以上的 CoffeeScript 果然就比較適應了。

本文主要是想整理上述過程中獲得的一些情報,大致上可以分為三個部分:

  • Grunt tasks
  • Sublime Text 3 packages
  • My style guide

Grunt tasks

目前來講如果某部分的 code 已經整塊全轉成 .coffee ,我會將它們 compile 到暫存資料夾再來做 uglify。

而對需要混合寫的 code,目前是採用 compile 到對應資料夾裡跟一般的 .js 擺一起,不過附檔名用 .coffee.js 來做區隔。這樣做的好處是:

  • 可以完全融入原本純 JavaScript 環境的 build flow
  • 可以用 .gitignore 避掉 .coffee.js 檔案們

grunt-contrib-coffee

用來 compile CoffeeScript 成 JavaScript。

grunt-coffeelint

透過 CoffeeLint 做 CoffeeScript 語法檢查。

Sublime Text 3 packages

想要快樂的寫 CoffeeScript,當然要把刀子磨亮啦!

Better CoffeeScript

除了提供程式碼上色以及一些小 snippet 之外還有一些運用 CoffeeScript 的功能 這邊列出預設快捷鍵來 preview 一下這個 package 所提供的功能

alt+shift+t - Run a Cake task
alt+shift+r - Run some CoffeeScript (prints output to a panel on the bottom)
alt+shift+s - Run a syntax check
alt+shift+c - Compile a file
alt+shift+d - Display compiled JavaScript
alt+shift+l - Display lexer tokens
alt+shift+n - Display parser nodes
alt+shift+w - Toggle watch mode
alt+shift+p - Toggle output panel

CoffeeCompile

可以直接在程式碼選取一個片段直接 compile 成 JavaScript,並且在類似 console panel 的地方顯示出結果。

個人覺得使用 CoffeeScript 的初期還蠻適合用來做一些小實驗。

coffee_compile.png

SublimeLinter-coffeelint

SublimeLinter3 搭配 CoffeeLint 的 package。

My style guide

這邊列出一些經過實驗之後我決定的寫法,基本上看個人偏號使用囉!

Invoke function 的時候盡可能不要帶括號

這項可能待驗證,生理上我比較能接受帶括號的寫法,還是比較清楚。

各種 invoke 法:

# 無參數
it = -> console.log 'No just?'
do it

# 最一般的 (with object literal)
chrome.alarms.create THROTTLE_ALARM_NAME, periodInMinutes : 1

# 有一個 callback
chrome.alarms.onAlarm.addListener (alarm) ->
  throttled = false if alarm.name is THROTTLE_ALARM_NAME

# 有兩個 callback Part1
# 此時為了表達晰以及寫法整齊可以不用 anonymous function
success = (data, status) ->
  "success"
error = (data, status) ->
  "error"
xhrGet "some url", success, error

# 有兩個 callback Part2
# 如果硬要寫 anonymous function 就會變這成這樣
xhrGet "some url",
  (data, status) ->
    "success"
  ,  # 會覺得這個逗號很有事
  (data, status) ->
    "error"

盡量不要使用內建的 return 最後一行功能來 return

想 return 什麼就寫出來。

genEvtHandler = (key) ->
  return (evt) ->
    console.log "event handler"

判斷式內的 function call 寫法

getRealNumber = (number) ->
  return number * 10
  
# 地雷式寫法
if getRealNumber 5 >= 10
  # compiles to if (getRealNumber(5 >= 10))
  console.log '這是地雷'

# 改良式寫法
if getRealNumber(5) >= 10
  # compiles to if (getRealNumber(5) >= 10)
  console.log '好像不錯'

# 看起來更 coffee 一點
if (getRealNumber 5) >= 10
  # compiles to if ((getRealNumber(5)) >= 10)
  console.log '就決定是你了'

如果判斷式內容過長或著內容需要寫多行,避免使用後置 if

好的 case:

if 200 <= request.status < 400
  success data, request.status if success
else
  error data, request.status if error

不好的 case 及修正:

# Bad
chrome.tabs.sendMessage(tab_id,
  message   : mapped_message
  key       : key
  charIndex : evt.charIndex) if mapped_message

# Good
if mapped_message
  chrome.tabs.sendMessage tab_id,
    message   : mapped_message
    key       : key
    charIndex : evt.charIndex

使用 and, or, is, isnt 取代 &&, ||, ==/===, !=/!==

使用 andor 讓 expression 看起來語感好一些。

另外由於在 CoffeeScript 中,== 會直接 compile 成 ===!= 會直接 compile 成 !==,讓我覺得豈不是直接用 isisnt 就好了嗎…

老實說有時候還是想依靠一下 JavaScript 那混亂的 ==!= 就是了。

使用 object literal 的時候給它一點空間

其實是因為在 CoffeeLint 的 options 裡有看到一項 colon_assignment_spacing,考慮遵守一下。

chrome.tabs.sendMessage tab_id,
  message   : mapped_message
  key       : key
  charIndex : evt.charIndex

平常字串都使用單引號,除非有變數內插的需求

# Normal
a = '平常字串'

# Interpolation need
foo = 'Coffee'
b = "#{foo}Script"

使用 global 內容的 pattern

這邊以 angular 為例。

#global angular

root = exports ? this

angular = root.angular

支援 ngmin 的 angular DI 寫法

為了整合進原本的 JavaScript flow,並且不想手動作 dependency annotation,要生出 ngmin 認得的格式才行。

這邊用 factory 做範例。

(angular.module "myApp").factory 'thisIsAService',
  ($rootScope, $http, $cacheFactory) ->
    # Explicit return
    return ->
      # Factory function

後記

是說用本文標題去 Google 真的可以搜到一堆 CoffeeScript 相關的文章、影片、投影片之類的…

看來爛梗大家都… (誤)

adventure_time_brush_teeth.gif