Сводка
Я использую Express + Jade для своего веб-приложения, и я борюсь с рендерингом частичных представлений для моей навигации AJAX.
У меня есть два разных вопроса, но они полностью связаны, поэтому я включил их в один пост. Я предполагаю, что это будет длинный пост, но я гарантирую, что он будет интересным, если вы уже боролись с теми же проблемами. Я был бы очень признателен, если бы кто-то нашел время, чтобы прочитать и предложить решение.
TL;DR : 2 вопроса
- Каков самый чистый и самый быстрый способ рендеринга фрагментов представлений для навигации AJAX с помощью Express + Jade?
- Как следует загружать файлы JavaScript для каждого представления?
Требования
- Мое веб-приложение должно быть совместимо с пользователями, у которых отключен
JavaScript. - Если JavaScript включен, с сервера на клиент должен отправляться только собственный контент страницы (а не весь макет).
- Приложение должно быть быстрым и загружать как можно меньше байтов.
Проблема №1: что я пробовал
Решение 1: наличие разных файлов для запросов AJAX и не-AJAX
Мой layout.jade:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
Мой page_full.jade:
extends layout.jade
block content
h1 Hey Welcome !
Мой page_ajax:
h1 Hey Welcome
И, наконец, в router.js (Express):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page_ajax.jade");
else res.render("page_full.jade");
});
Недостатки:
- Как вы, наверное, догадались, мне приходится редактировать свои представления дважды каждый раз, когда мне нужно что-то изменить. Довольно неприятно.
Решение 2: та же техника с `include`
Мой layout.jade остается неизменным:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
Мой page_full.jade теперь:
extends layout.jade
block content
include page.jade
Мой page.jade содержит фактический контент без какого-либо layout/block/extend/include:
h1 Hey Welcome
И теперь у меня в router.js (Express):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page.jade");
else res.render("page_full.jade");
});
Преимущества:
- Теперь мой контент определяется только один раз, это лучше.
Недостатки:
- Мне по-прежнему нужны два файла для одной страницы.
- Мне приходится использовать один и тот же метод в Express на каждом маршруте. Я только что перенес проблему повторения кода с Jade на Express. Щелчок.
Решение 3: то же, что и Решение 2, но устраняет проблему повторения кода.
Используя метод Алекса Форда, я смог определить моя собственная функция render
в middleware.js:
app.use(function (req, res, next) {
res.renderView = function (viewName, opts) {
res.render(viewName + req.xhr ? null : '_full', opts);
next();
};
});
Затем измените router.js (Express) на:
app.get("/page",function(req,res,next){
res.renderView("/page");
});
оставив остальные файлы без изменений.
Преимущества
- Это решило проблему повторения кода
Недостатки
- Мне по-прежнему нужны два файла для одной страницы.
- Определение моего собственного метода
renderView
кажется немного грязным. В конце концов, я ожидаю, что мой механизм шаблонов/фреймворк справится с этим за меня.
Решение 4. Перенос логики в Jade
Мне не нравится использовать два файла для одной страницы, так что, если я позволю Jade решать, что отображать вместо Express ? На первый взгляд, мне это кажется очень неудобным, потому что я считаю, что шаблонизатор вообще не должен обрабатывать какую-либо логику. Но давайте попробуем.
Во-первых, мне нужно передать Jade переменную, которая сообщит ему, что это за запрос:
В middleware.js (Express)
app.use(function (req, res, next) {
res.locals.xhr = req.xhr;
});
Итак, теперь мой layout.jade будет таким же, как и раньше:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
И мой page.jade будет:
if (!locals.xhr)
extends layout.jade
block content
h1 Hey Welcome !
Отлично да? За исключением того, что это не сработает, потому что условные расширения невозможны в Jade. Поэтому я мог бы перенести тест с page.jade на layout.jade:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
и page.jade вернется к:
extends layout.jade
block content
h1 Hey Welcome !
Преимущества:
- Теперь у меня есть только один файл на странице
- Мне не нужно повторять тест
req.xhr
на каждом маршруте или в каждом представлении.
Недостатки:
- В моем шаблоне есть логика. Фигово
Резюме
Все эти приемы я придумал и испробовал, но ни один из них меня не убедил. Я делаю что-то неправильно ? Существуют ли более чистые методы? Или мне следует использовать другой механизм шаблонов/фреймворк?
Проблема №2
Что происходит (с любым из этих решений), если в представлении есть собственные файлы JavaScript?
Например, используя решение № 4, если у меня есть две страницы, page_a.jade и page_b.jade, каждая из которых имеет свои собственные файлы JavaScript на стороне клиента js/ page_a.js и js/page_b.js, что происходит с ними, когда страницы загружаются в AJAX?
Во-первых, мне нужно определить блок extraJS
в layout.jade:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
// Specific JS files go there
block extraJS
else
block content
// Specific JS files go there
block extraJS
и тогда page_a.jade будет:
extends layout.jade
block content
h1 Hey Welcome !
block extraJS
script(src="js/page_a.js")
Если бы я набрал localhost/page_a
в адресной строке (запрос без AJAX), я бы получил скомпилированную версию:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome A !
script(src="js/jquery.min.js")
script(src="js/page_a.js")
Выглядит хорошо. Но что произойдет, если я перейду к page_b
с помощью навигации AJAX? Моя страница будет скомпилированной версией:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome B !
script(src="js/page_b.js")
script(src="js/jquery.min.js")
script(src="js/page_a.js")
js/page_a.js и js/page_b.js загружаются на одну и ту же страницу. Что произойдет, если возникнет конфликт (то же имя переменной и т. д.)? Кроме того, если я вернусь к localhost/page_a с помощью AJAX, у меня будет следующее:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome B !
script(src="js/page_a.js")
script(src="js/jquery.min.js")
script(src="js/page_a.js")
Один и тот же файл JavaScript (page_a.js) загружается дважды на одну и ту же страницу! Не вызовет ли это конфликтов, двойного срабатывания каждого события? Независимо от того, так это или нет, я не думаю, что это чистый код.
Таким образом, вы можете сказать, что определенные файлы JS должны быть в моем block content
, чтобы они удалялись, когда я перехожу на другую страницу. Таким образом, мой layout.jade должен быть:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
block extraJS
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
// Specific JS files go there
block extraJS
Правильно ? Эээ.... Если я перейду к localhost/page_a
, я получу скомпилированную версию:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome A !
script(src="js/page_a.js")
script(src="js/jquery.min.js")
Как вы могли заметить, js/page_a.js на самом деле загружается до jQuery, поэтому он не будет работать, потому что jQuery еще не определен... Так что я не не знаю, что делать с этой проблемой. Я думал об обработке запросов сценариев на стороне клиента, используя (например) jQuery.getScript()
, но клиент должен был бы знать имя файла сценариев, посмотреть, загружены ли они уже, возможно, удалить их. Я не думаю, что это должно быть сделано на стороне клиента.
Как мне обрабатывать файлы JavaScript, загружаемые через AJAX? На стороне сервера используется другой механизм стратегии/шаблона? На стороне клиента?
Если вы зашли так далеко, вы настоящий герой, и я благодарен, но я был бы еще более благодарен, если бы вы могли дать мне несколько советов :)