本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,博客地址為http://www.cnblogs.com/jasonnode/ 。網站上有對應每一小節的在線練習大家可以去試試。
全棧開發平台 - 不僅僅是前端
Meteor和那些名聲如雷貫耳的前端框架,比如Angular, React等都不一樣,它是一個 采用單一開發語言的全棧開發的平台:開發者可以使用JavaScript同時 進行前端和后端的開發,然后交給Meteor運行這個包含了前后端的完整應用:
從圖中你可以看到,Meteo在前端使用瀏覽器作為基礎運行環境,在后端則是以NodeJS作為 基礎運行環境,以MongoDB作為數據持久化系統。
Meteor提供了一個橫跨前端和后端的中間層平台,預置封裝了很多功能庫,簡化了 Web應用的開發:使用單一語言快速開發Web應用,這是Meteor的最重要訴求。
初識Meteor
從構成來講,可以認為Meteor開發平台由兩部分構成:
-
Meteor庫 - 以功能包的形式存在,封裝了諸如實時通信、反應式編程之類的核心功能。當 一個Meteor應用啟動時,Meteor會自動加載這些庫,為應用提供了一個基礎環境。
-
Meteor工具 - 可以理解為命令行方式的開發環境,它使我們可以輕松地管理整個應用 開發流程:從創建應用、調試應用、自動化測試到打包、部署、熱升級。
現在,讓我們讓我們使用Meteor命令行工具meteor創建並啟動第一個Meteor應用吧。
≡ 創建應用 —— meteor create [project]
在終端中輸入meteor create test,然后按回車:
~$ meteor create test↵
這個命令將在當前目錄下創建一個子文件夾test,Meteor將使用內置的應用模板 作為這個文件夾的內容。我們可以進入test文件夾,執行ls命令查看一下內容:
~$ cd test↵
~/test$ ls -al↵
你可以看到Meteor創建了3個文件和1個目錄。
- test.css - 前端的樣式表文件
- test.html -前端的HTML文件
- test.js - 前端/后端共用的JavaScript文件。全棧,對吧O(∩_∩)O~
- .meteor - 這個子目錄是Meteor應用必須的特殊子目錄,由Meteor平台維護,我們不要動
先略過3個文件的具體內容,我們運行起來看看。
≡ 運行應用 —— meteor run
執行meteor命令啟動應用,在終端中輸入meteor,這等價於運行meteor run:
~/test$ meteor↵
當你看到終端中出現下面的提示信息:
...
App running at: http://localhost:3000/
恭喜!我們的第一個Meteor應用已經運行起來了!
≡ 停止應用運行 —— Ctrl+C
用鼠標左鍵點擊一下終端區域,確保它獲得鍵盤輸入焦點(你應該可以看到一個 閃爍的光標),然后同時按Ctrl鍵和C鍵,即可停止應用運行:
^C
~/test$
≡ 復位應用數據 —— meteor reset
Meteor應用運行時會生成打包文件、創建應用數據庫。可以使用下面命令 清理這些生成的文件和應用數據:
~/test$ meteor rest↵
meteor reset命令不影響你的源代碼文件。
模板文件 - test.html
打開test.html,你可能會略有不適:
它不是一個標准的HTML文件:沒有html頂層標簽,奇怪的符號{{> hello}}... 不過,在Metoer中這樣的文件卻是合法的文件 —— 模板文件。
≡ 模板頂層標簽 —— head/body/template
Meteor規定,在一個模板文件里,只能出現三種頂層標簽:head、body和template。 也就是說,模板文件只能包含以這三種標簽為頂層標簽的HTML片段。
這是因為,Meteor在運行應用之前有一個打包/bundle的過程,此時Meteor會提取所有 模板文件(一個應用中可以有多個模板文件)中的head、body和template片段,分別進行 合並、編譯后才呈現給用戶:
上圖中,a.html和b.html中的head片段合並后作為最終的head內容,b.html和c.html中 的body片段合並后作為最終的body內容,至於c.html中的template的內容,則最終替換了 b.html中的{{> hello}}。
≡ 模板語言 —— Spacebars
Meteor的模板使用的語言是私有的spacebars語言,它基於流行的handlebars,通過 在HTML片段中嵌入模板標簽(以兩對大括號為邊界)實現模板化。因此,Meteor的模板 其實就是HTML標簽和模板標簽的混合體。
{{> hello}}模板標簽用來調用一個子模板,Meteor將在最終呈現給用戶的HTML文檔中, 使用子模板hello的內容進行原地替換。
特殊的template標簽用來定義一個子模板。
{{counter}}模板標簽執行插值工作,Meteror將在最終呈現給用戶的HTML文檔中,使用 標識符counter對應的值進行原地替換。
樣式文件 - test.css
和模板文件類似,Meteor在打包過程中,會將所有的樣式文件合並成一個大的樣式文件, 然后在呈現給用戶的HTML文檔中引用這個樣式文件:
上圖中,a.css和b.css的內容將被合並為一個文件,並在最終呈現給用戶的HTML文檔中, 使用link標簽引用這個文件。
代碼文件 - test.js
test.js是最有趣的文件,Meteor將在前端和后端同時運行這個文件。可以這樣理解:
- 前端 - Meteor將在最終呈現給用戶的HTML文檔中使用script標簽引用test.js
- 后端 - Meteor將通過NodeJS讀入並運行test.js
毫無疑問,如果不做任何處理,誰也沒法保證一段JS代碼既可以在前端瀏覽器環境中運行, 也可以在后端NodeJS中運行。在test.js中,我們需要判斷當前的具體運行環境,以便 執行相應的代碼。
≡ 判斷代碼執行環境 —— Meteor.isClient/Meteor.isServer
讓同一個js文件即可以跑在前端,也可以跑在后端(比如NodeJS),已經有很多 應用了,只需要判斷下在某個特定環境才存在的變量就可以了(比如,NodeJS有global,而 瀏覽器有window)。Meteor提供了一組更加清晰的API來實現這個判斷:
- Meteor.isClient - 為真時,表示當前運行環境為前端
- Meteor.isServer - 為真時,表示當前運行環境為后端
你可以看到,在test.js中也是這么做的:
//test.js
if(Meteor.isClient){
//僅在前端執行的代碼塊
}
if(Meteor.isServer){
//僅在后端執行的代碼塊
}
≡ 前后端都執行的代碼
很顯然,如果在test.js中,不判斷執行環境的代碼將同時在前端和后端運行。比如:
//test.js
console.log("Hello,Meteor!");
if(Meteor.isClient){...}
if(Meteor.isServer){...}
運行應用后,你將在后台的終端中看到Hello,Meteor!,也將在前台的調試台 中看到相同的輸出。
前端代碼 - 模板實例對象
回憶下,在模板文件test.html中,我們定義了一個模板:
<!--test.html--> <template name="hello"> <button>Click Me</button> <p>You've pressed the button {{counter}} times.</p> </template>
當Meteor運行這個應用時,將自動創建一個對應的模板實例對象:Template.hello。 對模板的數據綁定和事件綁定,這些通常需要使用JavaScript實現的功能,就通過這 個對象來實現:
在hello模板中,{{counter}}模板標簽中的標識符couter的值,將由對應模板實例 對象的counter函數返回值決定,這個函數被稱為模板的helper函數,使用模板實例的 helpers()方法聲明模板標簽中標識符對應的helper函數。
而通過模板實例對象的events方法,則為模板中的button元素掛接了click事件監聽處理 函數。
前端代碼 - 模板標簽標識符解析/helper
使用Template.hello.helpers(helpers)方法聲明hello模板中模板標簽標識符的解析函數。參數helpers是一個JS對象,屬性表示應用在模板標簽中的標識符,值 通常是一個函數,被稱為helper,大致是幫助Meteor解析模板中的標識符的值 這樣的意思。
比如,在test.js中我們為hello模板中出現在{{counter}}模板標簽中的counter表達 式聲明其對應的helper函數:
//test.js
Template.hello.helpers({
'counter':function(){
return Session.get('counter');
}
});
每次當Meteor需要對模板標簽{{counter}}進行計算時,都將調用其counter標識符 對應的helper函數進行計算:它簡單地返回Session變量counter的當前值。
≡ 為helper函數設定參數
helper函數可以接受參數,比如對於模板test中的displayName標識符:
<template name="test"> <h1>Hello,{{displayName "Jason" "Mr."}}!</h1> </template>
聲明如下的helper函數:
Template.test.helpers({
'displayName' : function(name,title){
return title + ' ' + name;
}
});
那么Meteor渲染后將獲得如下的HTML結果:
<h1>Hello,Mr. Jason!</h1>
≡ 使用常量helper
當然,也可以將helper定義為一個常量:
Template.test.helpers({
displayName : "Mr. WHOAMI"
})
這時,模板標簽{{displayName}}將永遠地被設定為固定的值了。
后端代碼
在test.js中,后端代碼幾乎是空的,除了使用Meteor.startup(func)聲明 了一個空的初始化函數。
Meteor.startup(func)方法在前端和后端都是可用的。參數func指定了 一個初始化函數,當平台就緒時,將調用這個初始化函數。
當在前端調用這個函數時,平台的就緒意味着DOM已經就緒,這時你可以進行DOM操作了:
//client side Meteor.startup(function(){ //jQuery對象在Meteor前端總是可用的 $("<h1>Hello,world</h1>").appendTo("body"); });
當在后端調用這個函數時,平台的就緒意味着Meteor平台的服務進程已經正常啟動,可以 進行應用層級的初始化工作了,比如,為空的后端數據集填充一些數據:
//server side
Meteor.startup(function(){
var persons = new Mongo.Collection("persons");
if(persons.find().count() === 0){
persons.insert({...});
}
});
應用目錄結構
一個Meteor應用由目錄下的前端文件(HTML、CSS、腳本、資源文件等)和后端文件組成。 由於Meteor存在一個打包/bundle的階段,因此,Meteor對應用的目錄結構進行了約定, 以便其打包過程能夠順利完成。
默認情況下,Meteor會搜索應用目錄(包括子目錄)中的所有JavaScript文件,打包后分別 送往前端和后端運行,文件和子目錄的名稱會影響打包結果。Meteor對以下的子目錄將特別處理:
≡ client —— 參與打包的前端代碼文件夾
任何名稱為client的目錄,其文件都不會被送往后端運行。這類似於使用if(Meter.isClient){...} 進行前端代碼隔離的效果。
在生產模式下,client目錄里的文件將被自動合並、壓縮,但在開發模式下,JavaScript 文件和CSS文件並不進行壓縮處理(CSS還是會合並,但JavaScript文件不進行合並處理),以便於 開發過程中的調試和錯誤定位。
Meteor會搜索目錄中所有的模板文件中的三個頂層元素:head, body, template,其中所有 的head段會合並,body段和template段的聲明都將轉換為模板操作的JavaScript代碼。
≡ server —— 參與打包的后端代碼文件夾
任何名稱為server的目錄,其文件都不會送往前端運行。這類似於使用if(Meteor.isServer){...} 進行后端代碼隔離的效果,但不同的是,server目錄內的部分代碼不會發送到前端,因此一些敏感的 代碼(比如身份驗證等)應當放在這個目錄。
Meteor會收集除了client, public和private子目錄之外的所有JS文件,然后交給后端NodeJS 運行。后端的JavaScript代碼對每個請求將使用一個單獨的線程來處理,這不同於通常NodeJS的異步 代碼模式,因為Meter認為這種模式更適合Meteor應用場景。
≡ public —— 不參與打包的前端資源文件夾
應用根目錄下public子目錄中的文件僅用於前端,這些文件不會被打包,可視為http訪問的虛擬根目錄。 例如,public目錄下的logo.jpg文件,可以通過url:"/log.jpg"來訪問。這個目錄可以用來放置favicon.ico, robots.txt,或者一些靜態的HTM文件。
≡ client/compatibility —— 不參與打包的前端兼容庫目錄
有些前端JavaScript庫依賴於使用var定義的全局變量。為了兼容這些庫,這個目錄里的文件在被送 往前端時,不會被嵌套在一個作用域中,並且會在其他前端JavaScript文件之前先執行。
≡ tests —— 僅用於測試的文件夾
任何名為test的目錄內的文件僅用於測試,前端和后端都不會使用。
內容小結
讓我們簡單回顧這個示例Meteor應用的開發過程(假裝沒有使用meteor工具):
- 創建模板文件test.html,定義文檔結構
- 創建樣式文件test.css,定義文檔樣式
- 編寫前端腳本test.js,實現模板、數據、事件的綁定
- 在這個示例中,我們基本不需要編寫后端腳本
這些就緒后,扔給Meteor運行就可以了。
這有點像Apache+PHP或者IIS+ASP這些東西,Meteor提供一個基本的運行環境,你往 里面扔一個模板,立馬就可以看到效果,你編寫一段后端腳本,立馬增強了服務器的 能力。那么,Meteor和它們的區別在哪里呢?
≡ 橫跨前后端的中間層平台
在前面的課程中,我們看到,前端和后端同時都有一個Meteor對象作為API入口(當然, 前端的Meteor對象和后端的Meteor對象不是完全相同的實現),可以使用Meteor.isClient或 Meteor.isServer判斷代碼運行環境,可以使用同樣的Meteor.startup()方法分別在 前端和后端注入初始化代碼。這意味着什么?
讓我把最開始的那張示意圖中的Meteor層再細化一下:
很顯然,Meteor最與眾不同的是,使用同一套代碼,同時在前端和后端提供了應用運行的 基礎環境。不得不提的是,Meteor平台在前端基礎環境和后端基礎環境之間,甚至已經基於 websocket建立了一個雙向實時的通道!