我认为答案是更多的编程,但我会告诉你我做了什么。我使用自定义中间件,让我在结束最终的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