我認爲答案是更多的編程,但我會告訴你我做了什麼。我使用自定義中間件,讓我在結束最終的HTML文檔輸出之前結合任意的轉換過程。因此,例如,我在middleware.js模塊中有以下過濾器,我將依次進行解釋。
如此簡單的視圖只需使用普通的玉器及其各種濾鏡進行降價,javascript,coffeescript。一些觀點,例如博客文章,需要更復雜的中間件鏈,就像這樣。
首先,根據請求,我建立了包含此響應核心內容的文件,並將其設置爲res.viewPath
上的屬性。這可能是一個原始HTML片段文件或降價文件。然後我通過一系列中間件轉換髮送響應。我使用res.html
和res.dom
來存儲響應正在建立時的中間表示。
這只是存儲原始HTML(只是一個沒有頭或佈局的文檔主體片段)。
html = function(req, res, next) {
if (!/\.html$/.test(res.viewPath)) return next();
return fs.readFile(res.viewPath, "utf8", function(error, htmlText) {
res.html = htmlText;
return next(error);
});
};
這一個將markdown文件轉換爲HTML(使用markdown-js模塊)。
markdownToHTML = function(req, res, next) {
if (!/\.md$/.test(res.viewPath)) return next();
return fs.readFile(res.viewPath, "utf8", function(error, markdownText) {
res.html = markdown(markdownText);
return next(error);
});
};
我有一個子佈局,在我的主佈局內,但圍繞每個博客文章。所以我把這篇博文貼在這裏的子文件中。 (單獨的代碼未顯示從json元數據文件生成res.post
對象)。
blogArticle = function(req, res, next) {
var footerPath, post;
post = res.post;
footerPath = path.join(__dirname, "..", "templates", "blog_layout.jade");
return fs.readFile(footerPath, "utf8", function(error, jadeText) {
var footerFunc;
if (error) return next(error);
footerFunc = jade.compile(jadeText);
res.html = footerFunc({
post: post,
body: res.html
});
return next();
});
};
現在我圍繞主要內容HTML包裝我的佈局。請注意,我可以在這裏設置頁面標題等內容,或者稍後再等待,因爲我可以在此之後通過jsdom操作響應。我做body: res.html || ""
,所以我可以渲染一個空的佈局,並在稍後插入主體,如果更方便。
exports.layout = function(req, res, next) {
var layoutPath;
layoutPath = path.join(__dirname, "..", "templates", "layout.jade");
return fs.readFile(layoutPath, "utf8", function(error, jadeText) {
var layoutFunc, locals;
layoutFunc = jade.compile(jadeText, {
filename: layoutPath
});
locals = {
config: config,
title: "",
body: res.html || ""
};
res.html = layoutFunc(locals);
return next(error);
});
};
這裏是真正強大的東西來。我將HTML字符串轉換爲jsdom文檔對象模型,該模型允許在服務器端進行基於jQuery的轉換。下面的toMarkup
函數只是允許我在不添加額外的<script>
標記的情況下獲取HTML,這是我們在jsdom中添加的內存中的jquery。
exports.domify = function(req, res, next) {
return jsdom.env(res.html, [jqueryPath], function(error, dom) {
if (error) return next(error);
res.dom = dom;
dom.toMarkup = function() {
this.window.$("script").last().remove();
return this.window.document.doctype + this.window.document.innerHTML;
};
return next(error);
});
};
所以這裏是我自定義的轉換。這可以用一個真正有效的HTML代替一個製作好的DSL標籤,如<flickrshow href="http://flickr.com/example"/>
,否則將是一個非常討厭的<object>
樣板文件,我不得不在每個博客文章中複製,如果flickr曾經更改過他們使用的樣板標記,那將是一個維護痛苦去修復它在許多個人博客文章降價文件。他們當前使用的樣板文件位於flickrshowTemplate
變量中,幷包含一個小鬍子佔位符{URLs}
。
exports.flickr = function(req, res, next) {
var $ = res.dom.window.$;
$("flickrshow").each(function(index, elem) {
var $elem, URLs;
$elem = $(elem);
URLs = $elem.attr("href");
return $elem.replaceWith(flickrshowTemplate.replace(/\{URLs\}/g, URLs));
});
return next();
};
同上嵌入youtube視頻。 <youtube href="http://youtube.com/example"/>
。
exports.youtube = function(req, res, next) {
var $ = res.dom.window.$;
$("youtube").each(function(index, elem) {
var $elem, URL;
$elem = $(elem);
URL = $elem.attr("href");
return $elem.replaceWith(youtubeTemplate.replace(/\{URL\}/, URL));
});
return next();
};
現在我可以更改標題,如果我想,添加/刪除的JavaScript或樣式表等。在這裏我設置標題的佈局已經呈現後。
postTitle = function(req, res, next) {
var $;
$ = res.dom.window.$;
$("title").text(res.post.title + " | Peter Lyons");
return next();
};
好吧,回到最終的HTML的時候了。
exports.undomify = function(req, res, next) {
res.html = res.dom.toMarkup();
return next();
};
現在我們推出它!
exports.send = function(req, res) {
return res.send(res.html);
};
,以便綁在一起,這一切,並有明確的使用它,我們做
postMiddleware = [
loadPost,
html,
markdownToHTML,
blogArticle,
layout,
domify,
postTitle,
flickr,
youtube,
undomify,
send
]
app.get("/your/uri", postMiddleware);
簡明?沒有清潔?我想是這樣。靈活?非常。非常快速?可能不會很快,因爲我相信jsdom是你可以做的更重量級的事情之一,但是我將它用作靜態站點生成器,因此速度無關緊要。當然,在中間件鏈的開始和結尾處添加另一個函數以將最終的HTML寫入靜態文件並在其比對應的降格頁面主體內容文件更新時直接提供它將是微不足道的。 Stackoverflowers,我很樂意聽到關於這種方法的想法和建議!
這很有趣,謝謝。我實際上並沒有考慮如何讓它達到這個目標,但是如果我的「節點實驗」使它超過了原型階段,這是非常好的思考。性能問題(如果有的話)可以通過將整個東西編譯成一個函數來緩解,如Jade所做的那樣,並緩存該函數。 – 2012-02-06 18:55:32