英文原文:Introduction to Realtime Web with Meteor and Node.js,編譯:oschina
最近,有許多關於 Derby.js的激動人心的討論湧現在了我的Twitter Timeline. 我從未體驗過一個框架能夠幫你做這麼多–實時同步客戶端和服務端. 從本質上講, 這使得我們可以自己編寫一個程式碼量很少的應用可以讓兩個人編寫同一個 text field–live. 而 Derby幫你處理了在 models 和 views 之間所有的同步. 就如 Google Docs 的協作編輯那樣.
這非常的強大, 但經過深入的研究, 發現 Derby.js 並沒有我想象中的那麼成熟–目前還沒有到1.0版本. 當然, Node.js 和 Meteor.js 也同樣沒有. 但相比起來, 似乎Derby缺少的更多一些. 比如, 據我目前知道的, 還沒有一個好使的方法來處理sessions. 或許是因為缺乏文件的原因吧, 但是, 據說Derby的團隊目前正在開發authentication. 如果有誰有一些關於Derby.js 處理sessions的新手指引, 我會很樂意去研究的.
另外一個我經常見到被拿來與Derby.js做比較的框架是Meteor.js. 與Derby相似的是,它也能在多個客戶端下實時更新views, 儘管做法上可能跟Derby有點不同. Derby可以較容易的使用多種資料庫, 而Meteor則只親近於MongoDB. 事實上, 通過如Mongoose客戶端接入資料庫的API與你在服務端所期望的已經非常接近了.
雖然現在meteor是個有一些缺點和爭議的框架, 但Meteor看起來是非常有趣的選擇用來建立有實時需求的應用. 個人還是喜歡Derby基於傳統回撥的程式設計形式更吸引我, 但在Derby的強大背後,卻缺乏健壯的文件和一個大的開發者社群, 這無疑是個很大的打擊. 或許這會隨著時間推移而有所改變吧, 但比起Meteor來說還是會慢很多, 因為後者最近獲得了1100萬美元的資金. 這筆財政資金確保了Meteor的存在以及得到持續的支援. 對於那些需要財政與發展穩定的框架的開發者而言, 這筆資金只會讓Meteor更加優勝. 今天,讓我們一起來看看如何新建一個真實的但又簡單的Meteor應用. 本質上說, 這是基於Tom的 Vimeo screencast的一個新手指引. 與Tom的 Vimeo screencast最大的不同是處理事件的方式. 比起復制貼上一個Meteor示例的程式碼, 我會一步一步的通過自己的方式來處理使用Enter鍵來提交一則訊息. 讓我們開始吧.
建立一個 Meteor應用
Derby和Meteor 他們共有的一個大加分是他們各自的命令列工具. 與Derby使用Node的內建的 npm 工具所不同的是, Meteor使用的是它自己的.
在終端(Mac OS X 和 Linux),執行如下的命令. (在這之前請確保你已經安裝了Node)
1 |
$curl https://install.meteor.com | /bin/sh |
Metror會自己搞定,並安裝命令列工具.
要新建一個專案, 先轉到你的工作目錄然後執行下邊的程式碼. 這會建立一個目錄, 裡邊包括有Meteor和一個最基本模板程式.
1 |
$meteor create chat |
現在, 你可以轉到該目錄並執行下面的程式碼讓它跑起來
1 2 |
$cdchat$meteor Running on: http://localhost:3000/ |
想要看到這個最基礎的應用程式, 你只需要在任意一款不過時的瀏覽器下開啟http://localhost:3000/
只要你想, 你就可以使用Meteor內建的meteor deploy命令來部署你的應用到Meteor自己的伺服器上
1 |
$meteor deploy my-app-name.meteor.com |
只要你更新儲存了你的程式碼, 所有連線上的瀏覽器都會實時更新其頁面.
開發聊天應用
在Meteor Create指令產生的資料夾中,你可以看見不同的檔案。如果你知道怎麼檢視隱藏檔案的話,你還可以看見個.meteor這個資料夾。這個資料夾包含了Meteor它本身,以及MongoDB的資料檔案。
在你App的根目錄資料夾下,你應該可以看到這三個檔案:chat.html, chat.css和chat.js。這三個檔案都是自帶說明部分的。HTML檔案包含了App的模型以及外觀,他們都是被chat.css定義的。Javascript檔案包含了在client和server端要執行的指令碼。有一點很重要,不要把任何東西放進這個指令碼檔案,比如說配置引數和密碼,因為任何人都可以通過檢視你應用程式的程式碼看到這些。
用你喜歡的文字編輯軟體開啟chat.js這個檔案。就個人而言,我喜歡用Sublime Text2,因為這個工具簡潔還有多種滑鼠狀態提示。
你可以在chat.js檔案中檢視到下面這樣一段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
if (Meteor.is_client) { Template.hello.greeting = function () { return "Welcome to chat."; }; Template.hello.events = { 'click input' : function () { // template data, if any, is available in 'this' if (typeof console !== 'undefined') console.log("You pressed the button"); } }; } if (Meteor.is_server) { Meteor.startup(function () { // code to run on server at startup }); } |
在Meteor.js中注意if段落中Meteor.is_client和Meteor.is_server的兩個部分。在這些區塊中的程式碼會分開執行,當執行這段程式碼的機器是client端則只執行clint塊中的程式碼,server同理。這就說明了Meteor在實際運用中的程式碼共享能力。
刪除掉if中所有Meteor.is_client和Meteor.is_server段的程式碼,最後只剩下一段:
1 |
if (Meteor.is_client) { } |
注意,當你儲存了 指令碼檔案之後,你的瀏覽器會立刻重新整理載入這段新的程式碼。
建立檢視(View)
在我們正式對這個指令碼檔案動工之前, 我們需要先新建一個檢視用來展示聊天記錄. 在編輯器裡開啟chat.html並刪除body標籤裡邊的程式碼. 包括名為hello的template標籤.只留如下部分
1 2 3 4 5 6 7 |
<head> <title>chat</title> </head> <body> </body> |
接著在body標籤裡新增下面這句
1 |
{{> entryfield}} |
Meteor使用的模板系統與Mustache很相似.大括號{% raw %}{{}}{% endraw %}表示要呈現的內容. 通過簡單地在兩對大括號裡新增內容如{% raw %}{{hello}}{% endraw %}, 模板系統會用hello這個變數的值來替換它. 後面會更詳細的介紹.
注意到了在entryfield這個詞前面有個大於號>了嗎? 使用該符號來指定渲染哪一個模板.
1 2 3 |
<template name="entryfield"> <input type="text" id="name" placeholder="Name" /> <input type="text" id="message" placeholder="Your Message" /> </template> |
在這個例子中,template標籤有單個屬性, 即模板的名字, 這就是我們要渲染的模板, 注意, 模板的名字要和body裡的程式碼指定的模板名字一樣 ({{> entryfield}})
檢視瀏覽器, 你會發現頁面已經重新整理了, 輸入框已經呈現出來了.
接下來, 在body裡邊新增另外的一個mutache標籤用以渲染訊息列表
1 |
{{> messages}} |
最後, 我們還需要新建一個名叫messages的模板. 在entryfield模板下面新增下面這段程式碼
1 2 3 4 5 6 7 |
<template name="messages"> <p> {{#each messages}} <strong>{{name}}</strong>- {{message}} {{/each}} </p> </template> |
注意到each子句. 在Meteor中你可以使用如下的語法來遍歷一個陣列模板
1 2 |
{{#each [name of array]}} {{/each}} |
使用each迴圈時,上下文會有所改變. 當引用變數的時候, 實際上你引用的是每一個陣列元素的值.
例如,在我們的chat應用中, 我們遍歷了陣列模板”messages”裡邊的每個元素, 該陣列可以像下面這樣,
1 2 3 4 5 6 7 8 9 10 |
[ { "name": "Andrew", "message": "Hello world!" }, { "name": "Bob", "message": "Hey, Andrew!"" } ] |
然後, 在each迴圈中, 你可以看到{% raw %}{{message}}{% endraw %}{% raw %}{{name}}{% endraw %}, 這會引用 每一個陣列元素的值來替代(Andrew 和 Bob 替換 name, 以及各自的問候資訊.)
當返回到你的瀏覽器, 你還看不到任何的改變. 因為訊息陣列還沒被傳送到模板, 所以Meteor遍歷不到任何東西來呈現.
你的chat.html最後應該是這樣的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<head> <title>chat</title> </head> <body> {{> entryfield}} {{> messages}} </body> <template name="entryfield"> <input type="text" id="name" placeholder="Name" /> <input type="text" id="message" placeholder="Your Message" /> </template> <template name="messages"> <p> {{#each messages}} <strong>{{name}}</strong>- {{message}}<br/> {{/each}} </p> </template> |
Javascript
從現在開始, 我們處理的大部分程式碼都是客戶端程式碼, 所以, 除非特別說明, 以下的程式碼都是在if (Meteor.is_client)程式碼塊中.
在我們編寫展示訊息的程式碼之前,讓我們先新建一個Collection. 從本質上講, 這是一組Models. 換句話說, 在這個chat應用的環境下, Messages collection儲存著整個聊天記錄, 而每條訊息記錄是一個Model.
在if語句前, 新增如下程式碼來初始化Collection:
1 |
Messages = new Meteor.Collection('messages'); |
因為我們希望這個Collection可以在客戶端和服務端被建立, 所以我們把它寫在了客戶端程式碼塊之外.
由於Meteor為我們做了大部分的工作, 要展示聊天記錄是非常容易的. 只需要把下面的程式碼新增進if語句裡邊.
1 2 3 |
Template.messages.messages = function(){ return Messages.find({}, { sort: { time: -1 }}); } |
讓我們拆開來分析這段程式碼:
Template.messages.messages = function(){ … }
第一部分Template表示我們正在修改一個模板的行為.
Template.messages.messages = function(){ … }
第二部分messages是模板的名字, 表示是在修改哪一個模板. 例如,如果我們想要對”entryfield”模板做些什麼, 只需把程式碼改成Template.entryfields.variable = function(){ … }(在這裡, 請別這麼做)
Template.messages.messages = function(){ … }
第三部分的這個messages代表的是一個這個模板裡的一個變數. 還記得我們的each迴圈遍歷messages嗎? 這就是那個mesaages.
當你開啟瀏覽器時, 頁面還是沒有什麼改變. 這是意料之中的事, 因為我們只抓取的訊息, 而沒有展示出來.
此時,你的chat.js應該是這樣的. 是否很驚訝就只需在伺服器寫這麼些程式碼我們就能展示一個實時的聊天記錄應用.
1 2 3 4 5 6 7 |
Messages = new Meteor.Collection('messages'); if (Meteor.is_client) { Template.messages.messages = function(){ return Messages.find({}, { sort: { time: -1 }}); } } |
在console裡新增Message
這部分的內容是可選的, 當然它有助於你除錯程式. 你可以直接跳過往下學習建立form來響應鍵盤事件(key press).
如果你想要測試你的訊息顯示程式碼, 你可以手動插入一條記錄到資料庫. 開啟你的瀏覽器控制檯, 並輸入如下:
1 |
Messages.insert({ name: 'Andrew', message: 'Hello world!', time: 0 }) |
這將會在資料庫中新建一條記錄, 如果正確的操作了的話,那瀏覽器就會即刻更新這條訊息在頁面上.
訊息表單
回到chat.js檔案當中,我們會將供輸入的form和資料庫連結起來以接收使用者聊天資料的提交。在底部新增下面的程式碼,不過注意要在if語句塊中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Template.entryfield.events = { "keydown #message": function(event){ if(event.which == 13){ // Submit the form var name = document.getElementById('name'); var message = document.getElementById('message'); if(name.value != '' && message.value != ''){ Messages.insert({ name: name.value, message: message.value, time: Date.now() }); name.value = ''; message.value = ''; } } } } |
程式碼有點多,讓我們再回顧一遍。你也許還記得,在Template後面的第二個單詞決定了我們正在修改的是哪個模板。不過跟之前不同的是,我們寫的程式碼是用來繫結資料庫和messages模板的,我們正在修改的模板是entryfield。
這個模板中events的屬性包含了一個object,events的屬性按照下面的格式呈現:
1 |
"[eventname] [selector]" |
例如,如果我們想為一個ID為hello的button繫結一個點選事件的話,我們會把下面的程式碼加入到events的個結構體當中。
1 |
"click #hello": function(event){ … } |
在我們的例子當中,我們是將一個函式繫結到了ID為“message”的一個keydown事件當中。如果你還記得,這段程式碼早在我們在chat.html檔案中建立模板的時候就已經設定好了。
在事件物件中,每個key都有一個函式作為它的值。這個函式在事件被呼叫時執行,其中事件物件作為第一個引數傳遞給該函式。在我們的app裡,每當ID帶有“message”的輸入欄中有任意鍵被按下(keydown)時,該函式就被呼叫了。
函式內的程式碼相當簡單。首先,我們檢查Enter鍵是否被按下(輸入中有13的關鍵程式碼)。第二,我們通過ID取得兩個輸入欄的DOM元素。第三,我們檢查並確保輸入值不為空,以防止使用者提交一個空的名字或資訊(name or message)。
注意下面的程式碼很重要。這段程式碼是將message插入資料庫。
1 2 3 4 5 |
Messages.insert({ name: name.value, message: message.value, time: Date.now() }); |
正如你看到的,這和我們插入到控制檯的程式碼類似,但不是硬編碼的數值,我們用的是DOM元素的值。此外,我們加入了當前時間,以保證聊天日誌被正確的按時間排序。
最後,我們將兩個輸入的值簡單的設為”以清空輸入欄。
現在,如果你進入瀏覽器,你可以試著輸入一個名字與資訊到兩個輸入欄。按下回車以後,輸入欄將被清除,一個新的訊息會出現在你的輸入欄位的正下方。開啟另一個瀏覽器視窗,導航到同一個URL(http://localhost:3000/)。試著鍵入另一個資訊,而
正如你看到的,Meteor非常強大。不需要寫一行明確更新訊息日誌的程式碼,新的資訊顯示出來並同步到多個瀏覽器和客戶端。
總結
雖然Meteor工作起來非常酷,而且也有一些非常有用的應用支援它,比如Derby.js,但它是不成熟的。一個說明這一點例子就是,瀏覽文件並找找紅色的引文。例如,關於MongoDB集合該文件做了如下陳述:
目前客戶端被給予集合的完全寫訪問許可權。它們可以執行任意的更新命令。一旦我們建立鑑權認證,你將能夠限制客戶端的直接插入,更新和刪除。我們也在考慮校驗器或者其他類似ORM的功能。
任何使用者擁有完全的寫訪問許可權是一個非常大的問題,因為對任何一個app產品——如果一個使用者對你的整個資料庫有寫訪問許可權,這是一個相當大的安全問題。
看到Meteor(和Derby.js!)在像哪個方向前進是令人激動的,但是除非它成熟一點,它可能不是一個產品級應用的最好選擇。期待那1100萬美元資金能很好的利用起來。