[react] react-numeral-input

react-numeral-input

之前寫了一個 react-numeral-input,主要是可以所見即所得的處理數字的格式,例如 100000 直接顯示為 100,000。原本的想法是將所有的輸入都轉成數字,然後將輸入的數字加上數字的格式。

不過這樣的作法會造成一個問題,就是當使用者在開啟輸入法時,打的任何值都會被吃掉(看起來像是鍵盤沒用的樣子)。網路上找到的解法大部分都是去做字串取代,但這樣無法解決問題。

input type="number"

html 的 <input type="number" > 無法顯示非數字,因此在做這個 component 時就知道不能用這個 type 了。

ime-mode: disabled

於是查了一下發現看起來最簡單的方法就是 disable ime-mode,但 ime-mode 只有在 firefox 和 IE 上能動。沒辦法跨瀏覽器。

keycode 229

接著發現當使用者用輸入法輸入時,收到的 keyCode 都是 229,也就是說是可以檢查使用者目前是開啟輸入法的,也可以提醒使用者關閉輸入法已確保正確的輸入。但麻煩的是還要額外提醒使用者的 UI 或者 notice。
React-numeral-input_by_blackbing.jpg

User Feedback

轉個念一想,其實問題是在使用者輸入時,沒有對應的 user feedback 給他,才會造成誤解欄位無法輸入的情況,因此保留不正確的格式好像也不是一個問題?
螢幕快照_2015-08-19_下午4_42_28(2).jpg

這樣就不需要去考慮輸入法什麼問題,類似這種所見即所得的工具,可以改變使用者的輸入,但要避免讓使用者誤解「輸入 -> 輸出沒反應。」

結論

如此要解決的問題就顯得容易的多,當你在輸入時開啟輸入法,還是可以輸入,但程式不會將你輸入錯的值吃掉,而是返回給試用者,至於是否要顯示 error message,則交給系統自己決定即可。看似簡單的問題,卻繞了一大圈回來才想到解法。

DEMO

有興趣的人可以玩玩看 react-numeral-input,或是直接安裝

npm install react-numeral-input --save-dev

[javascript] 利用 Deferred 處理動態的順序性任務

前情提要

之前寫了一篇 利用 Deferred 處理順序性任務,主要是利用 Deferred 來避免 callback 寫法的缺點。

不過在實務上,可能會遇到更複雜一點的情況,例如:

pipe array flow (2).png

這張圖的意思是要確保任務是 A->B->C 的執行順序,而且當 A,B,C 有任一個 fail 就會停止,不會再執行下去。我們直接來看應該要怎麼處理這樣的程式。

範例

var a = $.Deferred();
var b = $.Deferred();
var c = $.Deferred();

var checkA = function() {
  console.log('checkA');
  return a.reject();
};
var checkB = function() {
  console.log('checkB');
  return b.resolve();
};
var checkC = function() {
  console.log('checkC');
  return c.reject();
};

var checkAll = function(){
  var _dfr = $.Deferred().resolve();
  _dfr = _dfr
    .pipe(checkA)
    .pipe(checkB)
    .pipe(checkC);
  return _dfr.promise();
};

checkAll().done(function() {
  return console.log('checkAll done');
}).fail(function() {
  return console.log('checkAll fail');
});

為了簡化程式,我直接在 checkA/B/C 的 function 中回傳 Deferred reject or resolve。上述的例子可以直接看結果
可以看到結果只會印出

"checkA"
"checkAll fail"

是因為 checkA 執行時就被 reject 了,因此不會再繼續執行下去。您可以自己試試看將 reject 改成 resolve 看一下執行的結果會變成怎麼樣。

Dynamic task function

實務操作上 task 也許會是動態的,我們可以很簡單的將任務指派成一個 array 的形式,例如:

var funList = [checkA, checkB, checkC];

然後將 checkAll 的任務串起來

var checkAll = function(){
  var _dfr = $.Deferred().resolve();
  var funList = [checkA, checkB, checkC];
  for(var i=0; i<funList.length; i++){
    _dfr = _dfr.pipe(funList[i]);
  }
  return _dfr.promise();
};

最後的程式碼結果請猛擊這裡

結論

要注意的是不能用 jQuery.when,when 是一次發出去執行,會等到全部做完才會回來。

Reference

[react] selection auto-complete component

最近在找一個 selection 的 react component,但希望可以支援 auto-complete,找了好幾個不是整合有問題,就是彈性不夠,試了幾個都不太好整合。後來找到這個,http://jedwatson.github.io/react-select/,蠻不錯的 UI,想要的功能也都支援。特此記錄一下。

react-select

demo

螢幕快照 2015-04-15 下午2.45.56.png
蠻喜歡這種 UI 的,兼具 input 和 select 的控制項。

以下是其他類似的,但用過都還是有缺點

  1. https://github.com/asbjornenge/react-datalist 利用 html5 datalist,用 react 的特性其實很好操作,缺點是 支援瀏覽器有限(http://caniuse.com/#search=datalist),polyfill 太醜然後某些 action 操作不太一樣。
  2. https://github.com/fmoo/react-typeahead 看到 typeahead 我以為是 twitter 的 typeahead 的 react 版本,後來看了 sourcecode 才發現作者是重頭實做了一次 typeahead ,但是很多部分跟原本的 typeahead 不太一樣。操作起來綁手綁腳的。
  3. https://github.com/eliseumds/react-autocomplete
    這個 repository 取名取得很好不過跟 npm 的 react-autocomplete 又不一樣,然後 npm 的 react-autocomplete 的命名其實是 combobox ,而且沒在維護,連 source code 也不在 github 上了。

  4. https://github.com/prometheusresearch/react-selectbox
    因為星星數不夠多,所以根本沒用過。

以上。雖然網路資源很多,但是找起來好像在查 paper 的感覺,希望有幫助到有相關需求的人。

Secrets in npm package.json

講個秘訣

在用 npm 來管理 node package 時,愚昧如我只會用 npm install 來 install package,在歷經一些痛苦之後決定好好來看一下 package.json 的秘密。

dependencies V.S. devDependencies

區分專案的開發時的模組相依性。一般來說開發時都會用 npm i $PACKAGE_NAME --save-dev 這樣就會設定成該模組的 devDependency。但如果參數是給 --save 就會是 dependency。這是用來區別 production mode 時不需要下載 devDependency 的 package。

需要注意的是,npm install 預設會載入 devDependency 的 package,若是在 production mode 時,要做
npm i --production 指定只載入 dependency 的 package,如此就不會多載入一些開發才需要用到的 package。

npm scripts

不知道有沒有人跟我一樣,看到 Caesar 大大這個討論串,多想三秒,其實可以不用 grunt & gulp.,才發現原來 npm 可以自訂 scripts,然後傻傻的打 npm watch,結果不能執行,後來查了一下才知道 npm 有預設的指令,如果要執行自訂的指令要用 npm run xxx來執行。所以可以搭配一些 shell 指令來簡單的做你想做的事情。

pre/post scripts

然後在看 npm scripts 時看到一個蠻有趣的用法。

Pre and post commands with matching names will be run for those as well (e.g. premyscript, myscript, postmyscript).

簡單來說可以分開執行 script 的順序,在做複雜的指令還蠻好用的。 例如你可以自訂:

{
  "scripts": {
    "bingo": "echo Bingo"
  }
}

所以當執行 npm bingo 就會 echo Bingo,接著加上 pre/post 的話,例如:

{
  "scripts": {
    "bingo": "echo Bingo",
    "prebingo": "echo Hello",
    "postbingo": "echo World"
  }
}

這樣執行 npm bingo 就會 echo

Hello
Bingo
World

舉例來說,我會搭配 bower 來管理前端需要用的 package,我就可以加上 postinstall 的指令來做 bower install。

{
  "scripts": {
    "postinstall": "bower install"
  }
}

當執行 npm install 之後就會接著做 bower install

所以 ReadME 就不用再寫 Step1, Step2, ...。只要 npm install 就可以搞定啦,而且 jenkins 的 job 設定也可以變的比較一致性了,專案自己該做的事情就寫在這些 scripts 即可。回過頭來說,「多想三秒,你真的可以不用 grunt & gulp.」

package version control

不知道有沒有人注意到,在執行 npm install $PACKAGE_NAME 時,他會指定成 "^1.2.3",這是 follow https://github.com/npm/node-semver 的版本號規則。

需要注意的是預設的 "^1.2.3",意思是 [ '>=1.2.3-0', '<2.0.0-0' ],所以若 package 版本有更新,會自己升上去。最近幾次有遇到 jenkins 自己莫名其妙 build fail 就是因為版本不同而造成的問題。建議可以改成 "~1.2.3" [ '>=1.2.3', '<1.3.0' ] 或是寫死版本 "=1.2.3",再視專案而定更新就好。團隊開發時版本的控制很重要,沒有指定好大家拉不同的 code 常常會造成浪費時間的 debug。其他更詳細的用法就在參考 https://github.com/npm/node-semver 囉。

npm outdated

有空可以執行 npm outdated 來檢查 package 的更新,視情況而決定是否要更新 package。建議是開 feature branch 來做 upgrade package,沒問題再 merge 回去。

以上,這些大概是開發常會遇到的一些心得與秘訣,如果想看更詳細的解釋就請直接看 doc 啦。

References:

Wedding Party Hackday

16.jpg
最近完成了終身大事,也終於有點時間記錄一下了,身為一個宅宅工程師,我堅持自己的婚禮也要充斥一點宅味,當然也要感謝我的老婆容忍我的任性(疑~)。

其實很久以前就想過婚禮時可以做哪些系統了,有鑑於大家都說到時候不會有時間做這些事情,於是我提早開始準備我要做的系統。

互動式婚紗照片

我一直覺得婚紗本又重又難收藏,所以我很早就想把婚紗照直接用螢幕輸出,但只做播放太無聊了,所以我想要加入一些互動的元素,很直覺就想到用「motion detect」來做翻頁。

gest.js

gest.js這套 Library 可以用 camera 來辨識揮手的動作,辨識上下左右。試了一下辨識度還不錯。

jssor slider

要找適合的 slider library 反而找比較久,因為我希望可以支援上下左右的滑動效果,自動播放時也有一些客製化的特效,但大部分的 slide show 都只有左右滑動,最後找到這個客製化程度蠻高的 library jssor slider ,實際使用時其實他也沒辦法直接用上下滑動,所以我有修改了他的原始碼,讓他可以支援動態改變左右滑動與上下滑動。

最後就生出來這個版本了:DEMO,有興趣玩的人只要把 /image/photos/ 底下的照片換掉即可。有空我再寫 README。

10850072_10152841915926539_3126894732238031620_n.jpg

現場是用 iMac 顯示,然後在桌上弄點布置這樣,真是頗具巧思(自己說)。不過事實證明,沒有人介紹的話其實鮮少有人會發現可以用手控制,然後應該是小孩比較有興趣而已XD。

婚禮留言板

這個是我一直想做的東西,每次參加婚禮在開場前賓客都會不知道要幹嘛,早到的朋友會有點無聊,為了增加一點互動性,應該安排一些小活動給大家玩,類似的東西就是實體的留言箱,大家發一張小卡,然後上面些一些祝福的話,最後新人抽獎出來之類,不過因為沒有辦法看到大家的留言,少了一點即時的參與感。於是我又打算 web 化(沒辦法,我只會這個XD),讓賓客用手機留言,然後即時顯示在投影布幕上。

桌卡

桌卡的範本來自於 mybigday (個人覺得不好用),不過我用了他的桌卡範本改成我要的格式,放了 QR code 和連結上去。
螢幕快照 2014-12-25 13.43.32.png

手機留言

c21c5f114d9bbcba443f0d334b36f5ed (1).jpg

留言板

螢幕快照 2014-12-25 13.20.21.png

抽獎活動

留言當然要用點誘因來讓大家留言囉,於是我做了一個透過手機抽獎,可以讓主持人邀請重要嘉賓抽獎的部分。
c21c5f114d9bbcba443f0d334b36f5ed.jpg

最後的重頭戲就是抽獎了,由於前陣子很愛玩 ranger ,而且他的抽獎動畫實在是太可愛了,所以我就把裡面的動畫擷取出來做抽獎,效果不錯(自己說)。
螢幕快照 2014-12-25 13.19.11.png

小插曲

千古不變的定律, live demo 一定會出狀況,當天果然也是,原因是我以為 facebookid 都會拿到一串數字,結果我自己白痴塞了一些預設的 string id 進去,本來是拿數字 id 來取 mod 所以就出狀況了。好在我有堅強的 HTC FE 團隊協助我當場的 debug(hug)。
1619141_10201994217896298_4293646646054564328_n.jpg

10806202_10201994262017401_6262181042209528548_n.jpg

總結

  1. QR code 很不普及。
  2. 桌卡做的太假掰,有人以為是餐廳的根本不想理。
  3. 非常非常需要 promote,下次有機會(疑?)應該要請工作人員一桌一桌介紹或施加壓力(?)留言,要讓大家更有參與感一點,最後的抽獎才會 high。
  4. 其實只是搞死自己而已,其實我程式早在兩個月前就準備好了,不過婚禮前幾個禮拜測試發現 firebase 的 auth 部分改版了,結果為了要測 auth 的部分又花了一些時間修改程式。中間一度想要放棄這個活動。下次有機會(疑?)就乖乖的走完流成就好了。
  5. 我老婆好票釀。
  6. 謝謝大家的參與,大家下次見(疑?)
  7. 我愛我老婆。

62.jpg

最後順便打個廣告,如果有人對這個留言系統和抽獎程式有興趣的話請來信聯絡,保證以友情價情義相挺。

[ReactJS] tips of Writting JSX with CoffeeScript

由於一開始我沒有認真看 JSX (而且也很懶得看)就開始使用 coffeescript 來寫 React 了,所以一直沒有去搞懂 JSX 到底要怎麼寫,直到最近才開始認真使用 JSX 來開發,詳看上集。關於 jsx 的 tip 其實官方有介紹一些技巧與注意事項:可以參考 React Tips Introduction

因為自己習慣用 coffee 的關係,所以會遇到一些小問題,這篇文章主要紀錄一下用 coffee 寫 react 需要注意的地方與心得,本篇文章的程式碼也都是 coffeescript。

browserify

強烈建議一定要使用 browserify ,官方寫的 todomvc-flux就是用 browserify 來打包程式的,用了 browserify 之後就可以用 coffeeifyreactify 來 compile 程式了。

關於 browserify 的設定可以參考我的專案設定 https://github.com/blackbing/htccs-webapp/blob/master/gulp/tasks/browserify.coffee

jsx header

若程式之中有需要做 reactify 的程式要在第一行加上這行,讓 reactify 認得要 parse jsx 語法。

`/** @jsx React.DOM */`

jsx component

jsx 特有的語法就是用 element tag name 來當語法,例如:

var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;

  }
});

用 coffee 來撰寫時就會用到``來轉成 javascript 再交給 reactify 來處理。

HelloMessage = React.createClass(
  render: ()->
    return `<div>Hello {this.props.name}</div>`
  }

this and @

coffee 用 @ 代表 this,但小心不要在 jsx 語法裡頭用 @,因為被 ``包住的部分會被當成 javascript 來處理,coffeeify不會去處理這部分。舉例來說。

HelloMessage = React.createClass(
  onClick: ->
    alert 'onClick'
  render: ()->
    `<div onClick={@onClick}>Hello {@props.name}</div>`
  }

如此@onClick 就會變成 undefined。應該要改成 this.onClick 來處理。像這樣:

HelloMessage = React.createClass(
  onClick: ->
    alert 'onClick'
  render: ()->
    `<div onClick={this.onClick}>Hello {this.props.name}</div>`
  }

every component need a root parent

每一個 component 都需要一個 parent 來包住,例如你不能這樣寫 jsx:

HelloMessage = React.createClass(
  render: ()->
    `<div>Hello foo</div>
    <div>Hello bar</div>
    <div>Hello foobar</div>`
  }

而是要用一個 element 包住:

HelloMessage = React.createClass(
  render: ()->
    `<div>
      <div>Hello foo</div>
      <div>Hello bar</div>
      <div>Hello foobar</div>
    </div>`
  }

由於這可能會影響 HTML 的結構,需要跟 Designer 確定一些實做細節。

Empty text node will wrap by <span>

例如

`<th>type{foo}</th>`

會變成這樣

這可能會跟原本預期的不太一樣,為了避免樣式問題,可能要跟 Designer 確認避免產生空的文字節點。

Sperate component if there's any logic control

用了 jsx 之後會發現遇到邏輯部分的程式會不太好寫。例如用 js/coffee 來寫的話會像是這樣。

HelloMessage = React.createClass(
  render: ()->
    R.div className="form-component"
     if type is 'image'
       R.img src:data.src
     else if type is 'input'
       if inputType is 'text'
         R.input type:"text"
       else if inputType is 'radio'
         R.input type:"radio"
    #...(skip)

而用 jsx 要在一個 render function 做這些判斷會看起來很可怕。建議盡量拆 component,拆 component 是不用錢的。由於 component 都是單一物件的設計,所以盡可能的將一個 component 的邏輯簡化。如上例,可以將裡頭的 if-else 判斷拆出去 FormItem,像這樣:

FormItem = React.createClass
  render: ->
    data = @props.data
    if data.type is 'image'
       `<img src={data.src} />`
    else if data.type is 'input'
      if data.inputType is 'text'
        `<input type="text" value={data.value} />`
      else if data.inputType is 'radio'
        `<input type="radio" value={data.value} />`
        
HelloMessage = React.createClass(
  render: ()->
    `<div className="form-component"
      <FormItem data={data} />
     </div>`
    #...(skip)

這是一個很簡單的技巧也可以將 component 快速的拆開,不要嘗試在一個 component 裡頭包入太複雜的邏輯判斷。另一個好處是可以針對單一個 component 來做 unit-test。

false in jsx

false 在 jsx 裡頭有些不同的意義,在 http://facebook.github.io/react/tips/false-in-jsx.html 有說明,不過因為很重要所以要講三次

其中最重要的是 false element

`<div>{false}</div>`

這樣會變成 <div></div>,成為一個空的節點。在做一些邏輯判斷時還蠻好用的。

Never modify DOM by yourself

如果你開始使用 React 了,請戒掉用 jquery 操作 DOM 的「壞習慣」。真的,一開始會很不習慣,例如addClass('loading')太方便了,但是摻雜使用的後果就是很難 debug。建議要做任何 DOM 的操作都交給 state/props 來決定。

如果有其他使用 React 的小技巧也歡迎分享喔~

[ReactJS] jsx or coffee style

JSX

在寫 ReactJS 時,官方建議使用 jsx 這樣的語法來處理 html,這有點像是 template engine,不知道什麼是 jsx 的同學可以在 jsx compiler 玩玩看。jsx 其實不是必要的東西,如果你開心的話也可以直接用 React.DOM.div來撰寫,實際在使用時,對 jsx 充滿了疑惑,要做一些邏輯性的操作(例如 if-else, foo-loop)等等,相對的有點麻煩。

後來看到了一篇文章,推薦用 coffeescript 的特性來直接撰寫 React component:React, JSX, and CoffeeScript。文章提到用 coffeescript 的特性來操作 component 會更加的得心應手,於是我也動手把 component 直接改成 coffee style。寫出來的程式大概長成這樣:

  R.div id: 'list-view', className: 'view tab-pane fade in active',
    if listType is 'foo'
        R.div className: 'back', '←'
            R.p className: 'alert alert-warning',
            R.i className: 'fa fa-exclamation-circle'
    if listType is 'bar'
        R.span null, 'Hello world'
    else
        R.span null, 'Magic!'

優點

利用縮排來 beautify 程式
R = React.DOM
R.p null,
    R.a href:'foo', 'bar'  # note omitted comma here
    R.a href:'foo2', rel:'nofollow', 'second link'

利用 foo-loop 來產生 template

R.ol null,
  for result in @results
    R.li key:result.id, result.text

相比之下用 jsx 就囉唆許多:http://facebook.github.io/react/docs/multiple-components.html

寫 if-else 像是在喝水的簡單

  if not @state.editing
    R.div null,
      @props.text
      ' ' 
      R.span className:'link-button mini-button', onClick:@edit, 'change'
  else
    R.div style:{position:'relative'},
      R.input
        style:{position:'absolute', top:-16, left:-7}
        type:'text', ref:'text', defaultValue:@props.text
        onKeyUp:@onKey, onBlur:@finishEdit

相比之下 jsx 的 if-else 限制很多:http://facebook.github.io/react/tips/if-else-in-JSX.html

缺點

但是一直到最近我放棄了,所以我來說說 coffee style 的缺點。

無法使用在太複雜的 DOM 結構

其實可想而知,若DOM結構複雜的話,這樣做會搞死自己,雖然我們自以為對 javascript/coffee 非常熟稔,但人眼終究敵不過 template 的複雜度。這是一段我曾經寫的 component 的程式,坦白說這個結構沒有很複雜,但我看第二次的時候我自己都想殺了我自己,大家可以笑笑:

R.fieldset(className:"form-group #{ex_className}",
  R.legend( {className:"scheduler-border"}, item.name),
  R.div( {className:"table-responsive"},
    R.table( {className:"table table-hover"},
      R.tbody(null,
        for d_items, key in  item.dynamic_item
          console.log d_items
          R.tr(null,
            for i of  d_items
              d_item = d_items[i]
              if d_item.type is 'dropdown'
                R.td(null,  DropdownItem(item:d_item))
              else
                R.td(null,  TextItem( item: d_item))
            R.td(null,
              R.button( {type:"button",className:"btn btn-default btn-sm",
              onClick: do(key)=>
                => @removeItem(key)
              },
                R.i( {className:"glyphicon glyphicon-remove"})
              )
            )
          )
      )
    ),
    R.div(null,
      R.button( {
        type:"button"
        className:"btn btn-info btn-xs extend form-add"
        onClick: @addItem
      },
      R.i( {className:"glyphicon glyphicon-plus"})
      )
    )
  )
)
難以維護

由於 coffee 終究是 javascript,用 coffee 來寫基本上就只是在寫程式,一開始我對於可以使用 underscore 之類的 library 來寫 react component 感到很興奮,最後發現這根本沒辦法維護。比方說,我們可以這樣用

React.DOM.div null, foo.bar.map( (r)-> fruit[r]).join(', ')

看起來很簡單,而且寫起來很乾淨,但是當 template 摻雜著太多的邏輯,可就不好玩了,因為你很難控制團隊中大家會怎麼去使用這些東西,其實 template 應該還是回歸單純,只能接收 data,顯示 data。邏輯等等的東西應該都不要跟 template 有太多瓜葛。

結論

在經過一段時間的實做之後,我認為這些缺點非常致命,因此我不建議使用 coffee 來寫 react component,雖然說官方也提到 jsx 不是必要,但若是團隊合作的專案之中,建議還是乖乖的使用 jsx 來處理 template,盡量將邏輯與 template 分開。

下次會分享一些使用 jsx 的小技巧。

[expressjs] request proxy

api proxy

最近使用 expressjs 要將 api proxy 到另外一個 domain 的 api server 上。查了一下最簡單的做法是用 request 來處理。

var API_HOST = 'xxx.com';
app.use('/proxy', function(req, res) {
  var request_params, url;
  url = 'http://' + API_HOST + req.url;
  request_params = {
    followAllRedirects: true,  //redirect if 301~400
    body: JSON.stringify(req.body) //POST/PUT/DELETE data 
  };
  return req.pipe(request(url, request_params)).pipe(res);
});

看起來很簡單不過為了要解決 api server 會做 301/302 redirect 的動作,追了sourcecode才知道有這個 followAllRedirects 的設定,特此記錄一下。

reference:

[PhantomJS] Function.prototype.bind

PhantomJS 1.9.7 (Mac OS X) ERROR

使用 reactjs 來寫 test case 時,用 PhantomJS 執行時就遇到這個 error,奇怪的是用其他的 browser 來執行就沒有問題。

TypeError: 'undefined' is not a function (evaluating 'RegExp.prototype.test.bind(
      /^(data|aria)-[a-z_][a-z\d_.\-]*$/
    )')

查了一下追到這個 issue Function.prototype.bind is undefined,看起來是 PhantomJS 的內核沒有支援 Function.prototype.bind。於是再找到 MDN 的 Function.prototype.bind,在載入 reactJS 之前先將這個 polyfill 加進去就可以解決了。

karma.conf.js
files: [
    'test/polyfill/*.js',
    'app/bower_components/react/react-with-addons.js',
    '.tmp/test/spec/*.js'
]
phantom_bind_polyfill.js(2014/8/3 updated)

use react official polyfill

(function() {

var Ap = Array.prototype;
var slice = Ap.slice;
var Fp = Function.prototype;

if (!Fp.bind) {
  // PhantomJS doesn't support Function.prototype.bind natively, so
  // polyfill it whenever this module is required.
  Fp.bind = function(context) {
    var func = this;
    var args = slice.call(arguments, 1);

    function bound() {
      var invokedAsConstructor = func.prototype && (this instanceof func);
      return func.apply(
        // Ignore the context parameter when invoking the bound function
        // as a constructor. Note that this includes not only constructor
        // invocations using the new keyword but also calls to base class
        // constructors such as BaseClass.call(this, ...) or super(...).
        !invokedAsConstructor && context || this,
        args.concat(slice.call(arguments))
      );
    }

    // The bound function must share the .prototype of the unbound
    // function so that any object created by one constructor will count
    // as an instance of both constructors.
    bound.prototype = func.prototype;

    return bound;
  };
}

})();

[gulp] No need to use karma-browserify

天大的 bug

前幾天才寫了一篇 browserify and karma test 的環境設定,結果今天就踩到天大的 bug:Requiring files that require other files will cause failure.,問題是發生在你的 require dependency 太深的話就會發生 error。我自己遇到是在四層以上就會遇到一模一樣的 error message。

底下有人(gah-boh)留言:"I ended up not using karma-browserify. I tried karma-browserifast and ran into another issue with it. ",然後也提供了他的解法。

var gulp = require('gulp');
var source = require('vinyl-source-stream');
var browserify = require('browserify');
var karma = require('gulp-karma');
var reactify = require('reactify');
var glob = require('glob');

gulp.task('browserify-test', function() {
    var testFiles = glob.sync('./assets/tests/**/*.js');
    var bundleStream = browserify(testFiles).transform(reactify).bundle({debug: true});
    return bundleStream
        .pipe(source('bundle-tests.js'))
        .pipe(gulp.dest('assets'));
});

browserify your test case

我才發現根本就不需要用到 karma-browserify 來處理 (而且也不需要 gulp-karma(https://github.com/karma-runner/gulp-karma)),直接用 browserify 把所有的 test script 產生出來就好了。

以下是我的 task 設定

gulp.task "browserify:test", (callback)->
  bundleApp = map( (contents, filename)->
    fname = get_name(filename)
    browserify(
      entries: [filename]
      extensions: ['.coffee']
    )
    .bundle({debug: true})
    .on('error', $.util.log )
    .pipe(source("#{fname}.js"))
    .pipe gulp.dest(compiledPath + "/test/spec")
  )

  gulp.src('test/spec/**/*.coffee')
  .pipe(bundleApp)

然後 test task 照舊但是要在 browserify:test 做完之後在執行即可

gulp.task "test", ['browserify:test'], (callback)->
  appRoot = "#{__dirname}/../../"
  karmaCommand = appRoot + './node_modules/karma/bin/karma'
  karmaConfig = appRoot + 'test/karma.conf.js'
  exec("#{karmaCommand} start #{karmaConfig}", (err, stdout, stderr)->
    $.util.log stdout
    $.util.log stderr
    callback(err)
  )

結論

我一開始把 browerify 和 karma 的整合想得太複雜了,其實只要把 test case 都經過 browserify compile 之後就可以 run karma 了。我又繞了一大圈,真想殺了我自己。