web-dev-qa-db-ja.com

Express / Jadeを使用して部分ビューを適切にレンダリングし、JavaScriptファイルをAJAXにロードする方法は?

概要

WebアプリケーションにExpress + Jadeを使用していますが、AJAXナビゲーションの部分ビューのレンダリングに苦労しています。

私には2つの異なる質問がありますが、それらは完全にリンクされているため、同じ投稿にそれらを含めました。長い投稿になると思いますが、同じ問題に既に苦しんでいる人にとっては興味深いものになると思います。誰かが解決策を読んで提案するために時間を割いてくれたらとても感謝しています。

TL; DR:2問

  • AJAX Express + Jadeによるナビゲーションのビューのフラグメントをレンダリングするcleanestおよびfastestの方法は何ですか?
  • 各ビューに関連するJavaScriptファイルはどのようにロードする必要がありますか?

要件

  • 私のWebアプリは、無効にしたユーザーと互換性がある必要があります
    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回編集する必要があります。かなりイライラします。

解決策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");
});
_

利点

  • 現在、私のコンテンツは一度だけ定義されています。

欠点

  • one ページに two ファイルが必要です。
  • everyルートでExpressで同じテクニックを使用する必要があります。コードの繰り返しの問題をJadeからExpressに移動しました。スナップ。

解決策3:解決策2と同じですが、コードの繰り返しの問題を修正します。

Alex Fordの手法 を使用すると、middleware.jsで独自のrender関数を定義できます。

_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");
});
_

他のファイルは変更せずに残します。

利点

  • コードの繰り返しの問題を解決しました

欠点

  • one ページに two ファイルが必要です。
  • 独自のrenderViewメソッドを定義すると、少し汚い感じがします。結局のところ、テンプレートエンジン/フレームワークがこれを処理してくれることを期待しています。

解決策4:ロジックをJadeに移動する

two ファイルを one ページに使用するのは好きではないので、Expressの代わりに何をレンダリングするかをJadeに決定させるとどうなりますか?一見したところ、テンプレートエンジンはロジックを処理する必要がありますnotと思うため、非常に不快に思われます。しかし、試してみましょう。

まず、Jadeに変数を渡して、それがどのような種類の要求であるかを伝える必要があります。

middleware.js(エクスプレス)

_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では conditional extends が不可能であるため、それは機能しません。したがって、テストを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 !
_

利点

  • 今、私はページごとに1つのファイルしかない
  • すべてのルートまたはすべてのビューで_req.xhr_テストを繰り返す必要はありません

欠点

  • テンプレートにはロジックがあります。良くない

概要

これらはすべて私が考え、試したテクニックですが、どれも私を本当に納得させませんでした。私は何か間違ったことをしていますか?よりクリーンなテクニックはありますか?または、別のテンプレートエンジン/フレームワークを使用する必要がありますか?


問題#2

ビューに独自のJavaScriptファイルがある場合、(これらのソリューションのいずれかで)何が起こりますか?

たとえば、ソリューション#4を使用して、2つのページpage_a.jadeおよびpage_b.jadeがあり、両方に独自のクライアント側JavaScriptファイルがある場合js /page_a.jsおよびjs/page_b.js、AJAX?

最初に、layout.jadeextraJSブロックを定義する必要があります。

_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")
_

URLバー(非AJAXリクエスト)に_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/jquery.min.js")
               script(src="js/page_a.js")
_

それはよさそうだ。しかし、 AJAX navigation を使用して_page_b_に移動した場合、どうなりますか?私のページは、次のコンパイル済みバージョンになります。

_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.jsjs/page_b.jsは両方とも同じページにロードされます。競合(同じ変数名など)がある場合はどうなりますか?さらに、AJAXを使用して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 B !
                  script(src="js/page_a.js")
               script(src="js/jquery.min.js")
               script(src="js/page_a.js")
_

同じJavaScriptファイル(page_a.js)が同じページに2回読み込まれます!競合、各イベントのダブルファイアリングが発生しますか?そうであるかどうかにかかわらず、私はそれがきれいなコードだとは思わない。

そのため、特定の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()を使用してスクリプトリクエストを処理することを考えましたが、クライアントはスクリプトのファイル名を知っている必要があります。クライアント側で行うべきではないと思います。

AJAX?別の戦略/テンプレートエンジンを使用するサーバー側?クライアント側?でロードされたJavaScriptファイルをどのように処理すればよいですか?] ==

ここまでやってきたなら、あなたは真のヒーローであり、私は感謝していますが、アドバイスをいただければさらに感謝しています:)

38
Waldo Jeffers

いい質問ですね。完璧な選択肢はありませんが、私が気に入っているソリューション#3のバリエーションを提供します。ソリューション#3と同じ考えですが、_fullファイルのヒスイテンプレートをコードに移動します。これはボイラープレートであり、javascriptはフルページに必要なときに生成できるためです。免責事項:テストされていませんが、私は謙虚に提案します:

app.use(function (req, res, next) {
    var template = "extends layout.jade\n\nblock content\n    include ";  
    res.renderView = function (viewName, opts) {
        if (req.xhr) {
            var renderResult = jade.compile(template + viewName + ".jade\n", opts);
            res.send(renderResult);
        } else {
            res.render(viewName, opts);
        }
        next();
    };
 });

また、シナリオが複雑になるにつれて、このアイデアをより賢くすることができます。たとえば、ファイル名のプレースホルダーを使用してこのテンプレートをファイルに保存します。

もちろん、これはまだ完璧な解決策ではありません。ソリューション#3に対する元の反対と同じように、テンプレートエンジンで実際に処理する必要がある機能を実装しています。このために数十行以上のコードを書くことになった場合は、機能をJadeに適合させる方法を見つけて、それらにプルリクエストを送信してください。たとえば、jadeの「extends」キーワードが、xhrリクエストのレイアウトの拡張を無効にする可能性のある引数をとった場合...

2番目の問題については、テンプレートエンジンが役立つかどうかわかりません。 ajaxナビゲーションを使用している場合、ナビゲーションが何らかのバックエンドテンプレートマジックを介して行われたときにpage_a.jsを「アンロード」できません。このためには、従来のjavascript分離手法を使用する必要があると思います(クライアント側)。特定の懸念事項:クロージャーにページ固有のロジック(および変数)を最初に実装し、必要に応じてそれらの間で賢明な協力を行います。第二に、ダブルイベントハンドラーの接続についてあまり心配する必要はありません。メインコンテンツがajaxナビゲーションでクリアされ、それらが新しいハンドラーがDOMにロードされたときに(もちろん)get resetを呼び出すイベントハンドラーがアタッチされた要素であると仮定します。

3
Segfault

本当に役立つかどうかはわかりませんが、Expressとejsテンプレートで同じ問題を解決しました。

私のフォルダ:

  • app.js
  • views/header.ejs
  • views/page.ejs
  • views/footer.ejs

私のapp.js

app.get('/page', function(req, res) {
    if (req.xhr) {
        res.render('page',{ajax:1});
    } else {
        res.render('page',{ajax:0});
    }
});

私のpage.ejs

<% if (ajax == 0) { %> <% include header %> <% } %>

content

<% if (ajax == 0) { %> <% include footer %><% } %>

非常にシンプルですが、完璧に動作します。

1
lmaix

あなたが尋ねたことを行うにはいくつかの方法がありますが、それらはすべてアーキテクチャ上悪いです。気づかないかもしれませんが、時間の経過とともに悪化します。

この時点では奇妙に聞こえますが、本当にあなたはJS(AJAX経由でCSS)を送信したくない.

必要なのは、AJAXを介してマークアップを取得し、その後Javascriptを実行できるようにすることです。また、これを行う際に柔軟性と合理性が必要です。スケーラビリティも重要です。

一般的な方法は次のとおりです。

  1. 特定のページですべてのjsファイルをプリロードし、そのAJAX要求ハンドラーを使用できます。潜在的な名前が競合またはスクリプトを恐れる場合は browserify を使用します副作用:ユーザーを待たせないために、非同期に( script async )ロードします。

  2. 基本的にこれをクライアントで実行できるようにするアーキテクチャを開発します:loadPageAjax('page_a').then(...).catch(...)。ポイントは、すべてのJSモジュールを事前にダウンロードしているため、AJAXページ関連のコードをリクエストではなく、リクエストのcallerで実行することですcallee

したがって、あなたのSolution#4はほぼ良好です。スクリプトを使用せずにHTMLを取得するために使用します。

この手法は、あいまいなアーキテクチャを構築せずに目標を達成しますが、単一の堅牢な目標限定方法を使用します。

さらに質問に答えて、あなたがしようとしていることをしないようにあなたを説得して喜んで。

0
Kirill Rogovoy