/ NodeJS

[NodeJS] Frex.js First Look

這次是試用了 Frex Chien 所開發的 frex.js,也是 Stem OS 的開發者之一。依照慣例,我當然都是拿別人開發的工具來不務正業,像是之前 Ben Lin 寫的 coke 也是被我用來做網站,這次也不例外,frex.js 當然也是拿來做網站開發。

前言

跟 coke 一樣,frex.js 也是一款基於 express.js 來開發的一套 Framework,只是跟 coke 比較不一樣的地方是,他沒有 Ben 那樣絢麗的開發除錯功能(笑)。所以,如果以開發環境的友善程度來說,可能 coke 會好很多。那至於為什麼不用 express.js 呢?可能跟自己天生反骨有關吧(欸

frex.js 有一個亮點,就是用另外一種方式來詮釋所謂 real-time 的操作概念,他並沒有依賴 socket.io 這類套件,而是直接使用 NodeJS 原生的 Event 來達到這件事情。這一點 Frex 把它稱之為 Engine 確實是相當貼切的。

你可以把它想像成,它是一個存在於後端 ( Server-side ) 的接口,這樣的接口可以監聽由前端 ( Client-side ) 所發出的 Long Pulling,或者是主動由後端的 EventEmitter 來 push (emit) 資料給前端。同時,這樣的後端接口,也理所當然的可以直接由後端的程式來呼叫。

由於這樣方便的溝通方式,使得所有的資料都可以利用這種 Engine 的方式來傳遞,也就是說,其實這樣的模式更適合搭配非同步的方式,來做前端開發。

例如全站導入 AngularJS 就相當好用。

快速入門

首先,可以從 NPM 上面直接安裝。

npm install frex.js

裝完之後呢,你可以自己手動建立資料結構,或是直接去拷貝樣板出來用。

cp node_modules/frex.js/data/template/* ./

拷貝出來的樣板其實就是一個簡易的網站了,可以直接用 node 執行來得到結果,不過有個小地方要改一下,就是 app.js 的第一行,

// 請把 frex 改成 frex.js 不知道這是不是彩蛋(欸
var Frex = require('frex.js');

撿完彩蛋 (?) 之後,就可以運行他了,

node app.js

然後就可以連線到 http://localhost:8000 看到 frex.js 運行起來的樣子。

深入淺出

既然要充板面,就得多寫一點免得被人嫌棄(欸

app.js 設定

由於全部都是基於 express.js 來作開發,所以,其實所有的設定方式基本上都跟 express.js 大同小異。以一般的開發環境來說,可以這樣設定,

"use strict";

var Frex = require('frex.js'),
    http = require('http');

var app = new Frex();

app.configure(function() {
    app.set('views', __dirname + '/views');
    app.set('view engine', 'jade');
    app.use(Frex.compress());
    app.use(Frex.bodyParser());
    app.use(Frex.methodOverride());
    app.use(Frex.cookieParser());
});

app.configure('development', function() {
    app.use(Frex.logger('dev'));

    // 使用 cookieSession
    app.use(Frex.cookieSession({ 
        secret: "OIAUSDLN:;vasopdfihyaposr2-303456789oKJHGFDcvbnmkiuytrjr8u7890adsfbv", 
        cookie: { maxAge: 3600*1000 }
    }));

    // CSRF
    app.use(Frex.csrf());

    // Hack CSRF middleware
    // 第一次啓動會沒有數值,強迫寫入一個新的 csrf token
    app.use(function(req, res, next) {
        if (req.session._csrf) {
            res.locals.token = req.session._csrf;
        }
        next();
    });

    app.use(app.router);
    app.use(Frex.static( __dirname + '/public' ));
    app.use(Frex.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function() { /* 這邊省略一萬行 */ });

// 啓動 Server
http.createServer(app).listen(process.env.PORT || 8000, function() {
        console.log("Server start in " + process.env.NODE_ENV + " mode listen on port " + process.env.PORT);
});

之所以我把啟動作的那麼麻煩,是因為我有使用 SPDY,但是上面這個例子我就不提了,基本上 NodeJS 要使用 SPDY 也是相當方便的。

routing 設定

Routing 通常是整個網站最討人厭的地方,預設的 Frex.js 的做法是這樣,

"use strict";

module.exports = {
    "/": function(req, res) {
    },

    "/mypath": function(req, res) {
    },

    "/middleware": [
        function(req, res, next) {
            next();
        },
        function(req, res) {
        }
    ]
};

它是可以使用 middleware 的,使用的方式其實跟 express.js 雷同,只是說,這樣寫起來有個缺點是,整個 index.js 會越寫越多,所以,我個人是這樣做的,

"use strict"

var router = {
    homepage: require( __dirname + '/../homepage.js' ),
    mypath: require( __dirname + '/../mypath.js' ),
    middleware: require( __dirname + '/../middleware.js' )
};

module.exports = {

    "/": router.homepage.index,

    "/mypath": router.mypath.index,

    "/middleware": [ router.middleware.first_filter, router.middleware.result ]

};

這樣把所有的 Routing 檔案拆開,那麼 index.js 只要專心定義路徑就好,每個路徑就分配到不同的檔案去做事情即可。這樣拆開來做有一個缺點是,每個 scope 都是獨立的,所以可能本來在 index.js 只要宣告或是設定一次的事情,拆散到各別檔案去的話,就得重複做很多次,但是,這種事情就見仁見智了。

engine 設定

Engine 的做法其實比想像中的容易很多,你就照著 github 上面的 README 寫就可以,我這邊舉個 flickr 的例子來看,

"use strict";

var flickrAPI = require('flickr').Flickr;

var MyFlickr = function() {
    var self = this;
    self.consumer_key = '/* Your consumer_key */';
    self.consumer_secret = '/* Your consumer_secret */';
};

MyFlickr.prototype.setToken = function(token, secret) {
    var self = this;

    self.token = token;
    self.token_secret = secret;
};

MyFlickr.prototype.myPhotos = function(per_page, page, callback) {
    var self = this;

    var flickr = new flickrAPI(self.consumer_key, self.consumer_secret, {
        "oauth_token": self.token,
        "oauth_token_secret": self.token_secret
    });

    flickr.executeAPIRequest( 'flickr.people.getPhotos', {
        "user_id": 'me',
        "extras": 'url_l,path_alias,o_dims',
        "per_page": per_page || 10,
        "page": page || 1
    }, true, function(err, res) {
        if (err) callback(err, null);

        callback(null, res);
    });
};

module.exports = {
    type: 'engine',
    engine_name: 'MyFlickr',
    prototype: MyFlickr
};

這個簡單的例子需要用到 flickr 這個 NPM 套件,另外,也需要用到 OAuth,這些我就不詳細說明了。這樣的 Engine 我是由前端來呼叫 myPhotos 來拿照片用的,前端的寫法大概就是這樣,

App.setConnectionParams({
    "_csrf": '#{token}'
});

App.require(['MyFlickr'], function() {
    var flickr = App.Engine('MyFlickr');

    flickr.myPhotos(10, 1, function(err, res) {
        if (err) return;

        console.log(res);
    });
});

由於我有使用 CSRF,所以,如果你要用的話,原本的 Frex.js 是會失效的,這個部分我已經有送 pull request 給 Frex Chien,等他 merge 就可以動了(笑

如果你發現你的 Frex.js 沒有 setConnectionParams 的話,就不要怪我啦(欸(我在用的版本是我自己 Hack 過的啦啦啦啦啦啦啦啦啦啦啦啦(飄走

結論

總結來說,雖然沒有方便的除錯跟 test 可用(其實測試的部分,可能就是得自己想辦法整合,會比較好些。Frex.js 確實也提供了一個方便的開發方式,特別是 Engine 的構想,我真的覺得相當方便!

當然啦,我絕對沒有打廣告喔(只是為了太久沒寫部落格需要充版面而已(欸