我认为答案更多的是编程,但我会向您展示我的工作。我使用自定义中间件,让我在获得最终 HTML 文档输出之前组合任意转换过程。因此,例如,我的 middleware.js 模块中有以下过滤器,我将依次解释。
如此简单的视图只需使用普通的翡翠及其各种过滤器,用于 Markdown、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> 标记的情况下为我们的 jsdom 添加的内存中的 jquery 获取 HTML。
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 替换像 <flickrshow href="http://flickr.com/example"/> 这样的虚构 DSL 标签,否则这将是一个很大的讨厌的 <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 是您可以做的更重量级的事情之一,但我将其用作静态站点生成器,因此速度无关紧要。当然,如果它比相应的 Markdown 页面正文内容文件更新,那么在中间件链的开头和结尾添加另一个函数将最终的 HTML 写入静态文件并直接提供它是微不足道的。 Stackoverflowers,我很想听听关于这种方法的想法和建议!