Я думаю, что ответ больше программирования, но я покажу вам, что я делаю. Я использую пользовательское промежуточное ПО, которое позволяет мне комбинировать произвольные процессы преобразования, прежде чем я доберусь до окончательного вывода 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);
});
};
Этот файл преобразует файл уценки в 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);
});
};
У меня есть под-макет, который идет в рамках моего основного макета, но вокруг каждого поста в блоге. Так что я заверну пост в блоге здесь. (Отдельный код, который не показан, создает объект res.post
из файла метаданных json).
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
позволяет мне возвращать HTML-код без дополнительного тега <script>
для нашего jquery в памяти, добавленного jsdom.
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);
});
};
Итак, вот собственное преобразование, которое я делаю. Это может заменить готовый тег DSL, такой как <flickrshow href="http://flickr.com/example"/>
, на настоящий действительный HTML, который в противном случае был бы большим неприятным шаблоном <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();
};
Теперь я могу изменить заголовок, если хочу, добавить / удалить javascripts или таблицы стилей и т. Д. Здесь я устанавливаю заголовок после того, как макет уже отрендерен.
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, я хотел бы услышать мысли и предложения об этом подходе!