上次介紹的 Coke 所使用的樣板引擎 thunder,這次稍微簡單的介紹一下這一套樣板引擎。雖然說是 Ben 自行開發,但是我不得不說,這速度真的相當的快!
雖然我也寫過 jade 或是 EJS,相較之下 thunder 就沒那麼多花招。
Template Engine
樣板引擎流行好一陣子了,雖然說不能跟義大利麵比執行速度,但是對於可維護性上卻是相當加分的東西。thunder 這一套並不是一定得搭配 Coke 來使用,只是 Coke 他內建了這一套樣板引擎而已。
當然,如果你是 ExpressJS 的愛用者,你也可以使用這一套樣板引擎,這是沒有問題的。然後呢,你也可以把他放在 client 端去執行,很可愛吧(疑
#Installation
如果你使用 Coke 就不必額外安裝了。不過,你倘若是要給 ExpressJS 或是要用在 client 端的話,就得額外安裝,client 端必須得 -g
安裝才行。
$ npm install thunder [-g]
#Startup
用法相當簡單,我底下皆以搭配 Coke 為主來作示範,
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?- it.title ?></title>
<?= it.css( it.styles ); ?>
</head>
<body>
<h1><?= it.title ?></h1>
<? if( it.is_nav ) { ?>
<?= it.partial( 'common/_nav' ) ?>
<? } ?>
<?= it.body ?>
<?= it.js( it.scripts ); ?>
</body>
</html>
是的,這個樣板內建功能都以 it
開頭,裡面有好多符號,我簡單說明一下,
<? ... ?>
直接使用 JavaScript 運算(Evaluation)<?- ... ?>
內容跳脫(Escaping)<?= ... ?>
內容不做跳脫(No escape)
如果你是習慣寫 PHP 的人,應該很眼熟。至於你問我效能?這些都是在 Coke 啟動之前,就會將之編譯成一般的 html 檔案,所以嚴格來說他並不是動態產生的,所以你不必擔心前端的效能問題。
#Functional
在 Coke 裡面,有一些自帶與沿用自 ExpressJS 的方法,
it.styles
這是一個陣列,裡面是放你所需要的 css 檔案it.scripts
也是一個陣列,裡面是放你所需要的 js 檔案it.css( ... )
這是個函式,用來壓縮跟打包你的 css 檔案it.js( ... )
同上,但是他是用來打包 js 檔案it.body
這是在layout.html
中使用,他會把你的 Controller/Action 對應的 View 放進來it.partial
這是沿用自 ExpressJS,幫你編譯單獨的 View
如果你要自己寫一些方法呢?在 Coke 裡面,你只要編輯 app/helpers/application.js
就好了,
var moment = require( 'moment' );
module.exports = function ( app ){
app.helpers({
selected : function ( target, current ){
return target === current ? 'selected' : '';
},
val : function ( obj, prop ){
return obj === undefined ? '' : obj[ prop ];
},
date : function ( date, format ){
return moment( date ).format( format || 'MMM Do YYYY, h:m:s' );
}
});
app.dynamicHelpers({
messages : require( 'express-messages' )
});
};
Controller
我們再次回到 Controller 這個地方,輸出嘛,所以我們還是得回到這裡來作一些事情。但是我這次不只會講輸出,另外順便提一下運作模式。
這是一個 Coke 剛產生出來的 Controller,
var Application = require( CONTROLLER_DIR + 'application' );
module.exports = Application.extend({
index : function ( req, res, next ){
res.render( 'hellos/index' );
}
});
如果我們要傳一些東西到 View 裡面去,那很簡單,
var Application = require( CONTROLLER_DIR + 'application' );
module.exports = Application.extend({
index : function ( req, res, next ){
res.render( 'hellos/index', { title: 'Hello World' } );
}
});
然後你就可以在 View 裡面使用 it.title
來拿到你從 Controller 傳過來的東西。當然,如果你傳的是 Object
或是 Array
的話,也可以,但是 View 就得用 forEach
來拿東西囉。
var Application = require( CONTROLLER_DIR + 'application' );
module.exports = Application.extend({
index : function ( req, res, next ){
res.render( 'hellos/index', { packages: [1,2,3,4,5] } );
}
});
<? it.packages.forEach( function( package ) { ?>
<p><?= package ?></p>
<? } ?>
這樣應該相當容易理解吧!所以 Ben 才說,跟寫 PHP 很像啊(笑
Middleware
再來聊一下這個功能很強大的東西,由於一套 Framework 不可能盡善盡美,所以有些時候我們總是需要一些洋人其他的東西來輔助。像是 OAuth 這件事情,自己做很煩,所以用別人做好的套件是相當合理的。
然後 Ben 推薦這一套 Passport,自從我用了他之後考試都考一百分呢,這裡我們就使用 Facebook 來作說明。
首先你需要安裝在你的 Project 裡面,
$ npm install passport
$ npm install passport-facebook
喔,為什麼要裝兩個?第二個是 Passport 所提供的 Providers 套件,相當多元!
接著我們要在 app/middleware
底下建立一個 passport.js
這個檔案,
var passport = require( 'passport' );
var Strategy = require( 'passport-facebook' ).Strategy;
var User = require( 'mongoose' ).model( 'User' );
var config = CONF.passport;
passport.serializeUser( function( user, next ) {
var id = user.id ?
user.id : user.facebook_id;
next( null, id );
});
passport.deserializeUser( function( id, next ) {
User.findOne({
facebook_id : id
}, function ( err, user ) {
if ( user ) return next( null, user );
next( null, id );
});
});
passport.use( new Strategy({
clientID : config.facebook_api_id,
clientSecret : config.facebook_api_secret,
callbackURL : config.facebook_callback_url
}, function( accessToken, refreshToken, profile, next ) {
process.nextTick( function() {
return next( null, profile );
});
}
));
module.exports = function() {
return function( req, res, next ) {
passport.initialize()( req, res, function() {
passport.session()( req, res, next );
});
};
};
喔,裡面有用到了 Model
與 CONF
,這兩個東西暫時先跳過沒關係(喂)。接著我們要在 Application 的地方做設定,讓他可以使用。
app.use( middleware.passport());
加在哪?放在 app.use( app.router );
之前就可以了。然後,關於 CONF
的部份,由於我們設定了三組數值,我又不想寫在 Middleware 裡面,所以,放在 config.yml
也是很合理的,
passport:
facebook_api_id: '361188863952843'
facebook_api_secret: 'bc44c095050c902ef7565bcd3c2a94be'
facebook_callback_url: 'http://localhost:4000/passport/callback'
那剛剛用到的 Model
呢?這個我們最後再說,先來看看 Controller 這個部份,
var Application = require( CONTROLLER_DIR + 'application' );
var passport = require ( 'passport' );
var mongoose = require ( 'mongoose' );
var User = mongoose.model ( 'User' );
module.exports = Application.extend({
referer : function ( req, res, next ) {
var referer = req.headers.referer ?
req.headers.referer : '/';
res.cookie( 'referer', referer );
next();
},
facebook : passport.authenticate( 'facebook', {
scope: [ 'email', 'read_friendlists', 'read_stream', 'publish_stream' ]
}),
failure_redirect : passport.authenticate( 'facebook', {
failureRedirect : '/'
}),
callback : function ( req, res, next ) {
var referer = req.cookies.referer ?
req.cookies.referer : '/';
User.create( 'facebook', req.user,
function( err ) {
LOG.error( 500, res, err );
res.redirect( '/logout' );
},
function() {
res.redirect( referer );
}
);
},
logout : function ( req, res ) {
req.logout();
res.render( 'passport/logout' );
}
});
這個 Controller 幫我們做幾件事情,
- 使用者連入
passport/facebook
做 Facebook 的登入驗證 - 然後 Facebook 導回我們所設定的
callback
位址 - 我們把 Facebook 帶回來的資訊儲存起來
- 再次導回到真正的
referer
,如果沒有就回到首頁 - 登出功能
然後這裡又用到了 Model
了!關於 Model
建立的方式我想我之前有講過,我們就直接建立一個 User
的 Model 來用吧。
$ coke g model user
然後我們就開始編輯 schema.js
囉,
/**
* Module dependencies.
* @private
*/
var mongoose = require( 'mongoose' );
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var Model = {
};
Model.User = new Schema({
facebook_id : { type : String, required : true, index : true },
name : { type : String, required : true },
email : { type : String },
created_at : { type : Number, 'default' : Date.now },
updated_at : { type : Number }
});
// auto update `updated_at` on save
Object.keys( Model ).forEach( function ( model ){
if( Model[ model ].tree.updated_at !== undefined ){
Model[ model ].pre( 'save', function ( next ){
this.updated_at = this.isNew?
this.created_at :
Date.now();
next();
});
}
});
/**
* Exports module.
*/
module.exports = Model;
然後就可以用了嗎?當然不是囉,我們要再去 models/User.js
建立一下我們剛剛用到的方法,
var User = require( BASE_DIR + 'db/schema' ).User;
User.statics = {
create : function( provider, src, error, success ) {
var self = this;
this.findOne({
facebook_id : src.id
}, function( err, user ) {
if( err ) return error( err );
if( user ) return success();
new self({
facebook_id : src.id,
name : src._json.name,
email : src._json.email
}).save( function( err, user, count ) {
if( err ) return error( err );
return success();
});
});
}
};
require( 'mongoose' ).model( 'User', User );
嗯,裡面詳細的情況我就不解釋了,在這個 Model 裡面就是建立一個靜態方法,讓我們剛剛在 Controller 裡面可以呼叫,由這個靜態方法來對資料庫做操作動作。
小結
由於辦公室冷氣壞掉,然後筆者早上七點半就到辦公室,現在熱到靠北,所以不想寫小結惹~