PushState URL を使用してシングル ページ アプリケーションを提供し、パフォーマンスを向上させる Express ミドルウェア
![Gitter](https://badges.gitter.im/チャットに参加.svg)
Linux: Windows: 一般:
Serve-SPA は、express.static ミドルウェアのように動作します。ただし、pushState URL が要求された場合、Serve-SPA は 404 を返さず、代わりに一致する SPA ページを提供します。
この非常に単純な SPA がこのフォルダーから提供されていると仮定します。
spa
|-- index.html
|-- app.js
最初に、訪問者は通常、ベース URL http://localhost:3000/
を介して SPA をロードします。したがって、index.html が提供され、app.js もロードされます。次に、訪問者は SPA を経由して、pushState を使用して URL を更新し、たとえばhttp://localhost:3000/profile/me
に到着します。訪問者はこのページをブックマークして、翌日再び開く可能性があります。提供されたフォルダーには「me」ファイルを含む「profile」フォルダーが含まれていないため、express.static はこの URL に対して 404 を送信します。ただし、Serve-SPA はhttp://localhost:3000/profile/me
PushState URL として認識し、フォルダー構造で指定された URL に最もよく一致するページ (つまりhttp://localhost:3000/
) を検索します。
PushState URL サポートを有効にするために必要なのは、 index.html
ファイルの名前を ( tを含む) index.htmlt
に変更することだけです。つまり:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
通常の SPA (この場合は Angular.js を使用) がロードされる方法は次のとおりです。
ユーザーにページが表示されるまでの時間は、2 つの AJAX リクエスト「list.html」(HTML テンプレート)と「projects」(ページの設定に使用される JSON データ)によって大幅に増加します。
Serve-SPA を使用すると、AJAX 呼び出しがスキップされてページがすぐにレンダリングされるように、テンプレート/データをindex.htmlt
に簡単にインライン化できます。
Serve-SPA は、lodash のテンプレート機能をindex.htmlt
ファイルにもたらします。通常の SPA の上記の例では、次の HTML ページを使用します。
<!doctype html >
< html ng-app =" project " >
< head >
< title > Pure Angular.js SPA </ title >
< script src =" /bower_components/angular/angular.min.js " > </ script >
< script src =" /bower_components/angular-resource/angular-resource.min.js " > </ script >
< script src =" /bower_components/angular-route/angular-route.min.js " > </ script >
< link rel =" stylesheet " href =" /bower_components/bootstrap/dist/css/bootstrap.min.css " >
< script src =" /scripts/app.js " > </ script >
< base href =" / " >
</ head >
< body >
< div class =" container " >
< h1 > JavaScript Projects </ h1 >
< div ng-view > </ div >
</ div >
</ body >
</ html >
訪問者がhttp://localhost:3000/
リクエストした場合、この SPA はlist.html
テンプレートをレンダリングする必要があります。必要な AJAX 呼び出しをスキップするには、テンプレートをインライン化する必要があります。ただし、他の PushState URL にはこのテンプレートが必要ないためhttp://localhost:3000/
が要求された場合にのみインライン化する必要があります。これは、 index.htmlt
に次の追加を行うことで実現できます。
<body>
+ <% if (req.path === '/') { %>
+ <script type="text/ng-template" id="partials/list.html">
+ <%= require('fs').readFileSync('app/partials/list.html') %>
+ </script>
+ <% } %>
<div class="container">
<h1>JavaScript Projects</h1>
<div ng-view></div>
</div>
</body>
これをよりクリーンでブロックしない方法で実装する方法 (例: compose.js
の使用) がありますが、アイデアはわかります。
すでに Express.static ミドルウェアを使用して SPA を提供している場合は、代わりに Serve-SPA を使用して SPA を提供できます。
// Just replace:
app . use ( express . static ( appDir ) ) ;
// with:
serveSpa ( app , appDir ) ;
次に、 index.html
ファイルの名前をindex.htmlt
に変更します。
app
|-- blog
| |-- img
| | |-- ...
| |
- | |-- index.html
+ | |-- index.htmlt
| |-- blog.css
| |-- blog.js
|
- |-- index.html
+ |-- index.htmlt
|-- main.css
|-- main.js
これにより、pushState URL サポートと、HTML テンプレートと JSON データを各リクエストで提供される HTML にインライン化するテンプレート機能が提供されます。たとえば、事前にデータベースからデータをフェッチする必要がある場合は、それを行うためにcompose.js
ファイルを追加できます。
app
|-- blog
| |-- img
| | |-- ...
| |
+ | |-- compose.js // May load latest article headlines from the database
| |-- index.htmlt // May inline article headline so the browser spares an AJAX call
| |-- blog.css
| |-- blog.js
|
|-- index.htmlt
|-- main.css
|-- main.js
ところで、Serve-SPA は、SPA がクライアント側でどのように実装されるかについていかなる仮定も行いません。どの実装でも、サーバー側で行う必要がある変更を処理できる必要があります。
Serve-SPA デモ リポジトリをチェックアウトしてください。このリポジトリは、よく知られた SPA フレームワーク用の SPA の通常バージョンと移行バージョンを提供することを目的としています。たとえば、Angular.js ベースの SPA を移行する方法を確認するには、その通常の実装と、Serve-SPA を最大限に活用する事前に作成されたバージョンとの差分を作成します。
node.js のモジュールは npm 経由でインストールされます。
npm install serve-spa --save
Serve-SPA は、大まかに定義されたバージョンのserve-static に依存します。特定のバージョンをインストールしたい場合は、事前にserve-staticをインストールしてください。
var serveSpa = require ( 'serve-spa' ) ;
serveSpa ( appOrRouter , rootPath , options ) ;
appOrRouter
var app = express();
またはvar router = express.Router();
Serve-SPA を取り付ける場所に応じて異なります。ルーターを使用すると、特定の URL に SPA をマウントできます。たとえば、 app.use('/app', router);
を使用する場合、SPA はhttp://localhost:3000/app/
にマウントされます。 。rootPath
、SPA リソースへのファイル システム パスです。このパラメーターはapp.use(express.static(rootPath))
で使用されるものと同じです。また、 rootPath
複数のルートを使用するためのパスの配列である場合もあります。options
次の属性を許可するオブジェクトです。options.staticSettings
は、内部で使用されるserve-static ミドルウェアに転送されます。ところで、これらはexpress.static(rootPath, staticSettings)
に渡されるオプションと同じです。詳細については、serve-static のオプションのドキュメントを参照してください。options.beforeAll
テンプレートがレンダリングされる前に実行されるミドルウェアを受け取ります。これを使用して、すべてのテンプレートに必要な一般的な処理を実行します。特定のテンプレートを処理する必要がある場合は、代わりに compose.js ファイルを使用してください。options.templateSettings
、lodash の_.template(template, templateSettings)
に転送されます。詳細については、 _.templateSettings
のドキュメントを参照してください。options.require
テンプレートのレンダリング内で使用される特定の require 関数を受け取ります。このオプションが指定されていない場合、Serve-SPA はテンプレートのレンダリング時に独自の require 関数を提供します。この require 関数には、異なるモジュール パス検索階層が含まれる場合があります。したがって、このオプションを使用すると、優先モジュール パス検索階層を持つ別の require 関数を渡すことができます。テンプレートは HTML ファイルであり、JavaScript ミックスインが含まれる場合があります。
<!doctype html >
< html >
< head >
< title > Example template </ title >
</ head >
< body >
Today is: < %= (new Date()).toString() % > < br />
< ul >
< % _.forEach(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], function (day, i) { % >
< li < %= (new Date()).getDay() === i ? ' style="color: red;"' : '' % > >
< %= day % >
</ li >
< % }); % >
</ ul >
</ body >
</ html >
これにより、以下が生成されます。
Today is: Tue Oct 20 2015 16:09:40 GMT+0200 (CEST) < br />
< ul >
< li > Sunday </ li >
< li > Monday </ li >
< li style =" color: red; " > Tuesday </ li >
< li > Wednesday </ li >
< li > Thursday </ li >
< li > Friday </ li >
< li > Saturday </ li >
</ ul >
テンプレート内では次の変数を使用できます。
_
: lodash ライブラリrequire
: ノードの require 関数request
とreq
: Express によって提供されるリクエスト オブジェクトresponse
とres
: Express によって提供される応答オブジェクトレンダリングが失敗すると、エラーが次のエラー処理ミドルウェアに転送されます。次のように、独自のエラー処理ミドルウェアを登録できます。
// First Serve-SPA
serveSpa ( app , rootPath ) ;
// Then this middleware with the additional err parameter
app . use ( function ( err , req , res , next ) {
// If headers were already sent it gets complicated...
if ( res . headersSent ) {
return next ( err ) ; // ...just leave it to Express' default error handling middleware.
}
// Do your error handling:
console . error ( err ) ;
res . redirect ( '/guru-meditation/' ) ;
} ) ;
compose.js
ファイルをindex.htmlt
と同じフォルダーに置くと、テンプレートがレンダリングされる直前に実行されます。これにより、たとえばデータベースからデータを取得して、テンプレートをレンダリングするときにそれを使用することができます。
compose.js
ミドルウェアをエクスポートする必要があります。
var db = require ( '../lib/db.js' ) ;
module . exports = function ( req , res , next ) {
db . loadData ( { for : 'me' } , function ( err , data ) {
if ( err ) {
return next ( err ) ;
}
req . data = data ; // You can access the data through req.data in the template now.
next ( ) ;
} ) ;
} ;
compose.js
を通じてエクスポートされたミドルウェアは、通常のミドルウェアと同様に使用されます。したがって、エラー処理は通常どおりに機能します。
ミドルウェアは、テンプレートをレンダリングするすべてのリクエストに対して実行されるoptions.beforeAll
を介して渡すことができます。静的ファイルのみが要求された場合、このミドルウェアは実行されません。
全体的な実行順序は次のとおりです。
提供されたミドルウェアは通常のミドルウェアと同様に使用できます。したがって、エラー処理は通常どおりに機能します。
express.static(...)
と同様、Serve-SPA は GET リクエストと HEAD リクエストのみを処理します。デフォルトでは、HEAD リクエストの場合、beforeAll および compose.js ミドルウェアは実行されず、index.htmlt テンプレートもレンダリングされません。ただし、ミドルウェア関数にcallForHEAD = true
追加することで、ミドルウェアの実行を明示的にアクティブ化できます。
function middlewareForGETandHEAD ( req , res , next ) {
res . set ( 'my-header' , 'yay' ) ;
if ( req . method === 'GET' ) {
db . load ( ... ) ;
}
}
middlewareForGETandHEAD . callForHEAD = true ;
Serve-Static README では、次のように複数のルートを使用することが説明されています。
app . use ( serveStatic ( path . join ( __dirname , '/public-optimized' ) ) ) ;
app . use ( serveStatic ( path . join ( __dirname , '/public' ) ) ) ;
ただし、Serve-SPA では、このように複数のミドルウェアを使用することはできなくなりました。これは、pushState URL を適切に処理すると、処理がより複雑になるためです。
複数のルートの同じサポートを提供するために、Serve-SPA はルート パスの配列を取得します。次のコードは、上記の Serve-Static の例と同等です。
serveSpa ( app , [
path . join ( __dirname , '/public-optimized' ) ,
path . join ( __dirname , '/public' )
] ) ;
URL は、指定されたすべてのパスにその URL と一致する静的リソースがない場合にのみ、pushState URL として識別されます。そして、pushState URL は、通常どおり、指定されたディレクトリがマージされたかのように、最も一致する SPA に適用されます。
Serve-SPA の開発環境をセットアップするには、次の手順を実行します。
cd
、npm install
ヒットし、npm install gulp -g
実行します。gulp dev
を実行します。 (または、gulp をグローバルにインストールしたくない場合は、 node ./node_modules/.bin/gulp dev
を実行します。) gulp dev
すべてのソース ファイルを監視し、変更を保存するとコードを lint してすべてのテストを実行します。テスト カバレッジ レポートは./coverage/lcov-report/index.html
から参照できます。
テストをデバッグしたい場合は、 gulp test-without-coverage
使用して、テスト カバレッジ インスツルメンテーションによってコードを隠さずにすべてのテストを実行する必要があります。
minimatch
をインストールするための依存関係を更新しました。express.Router()
の使用を修正しました。express.Router()
をエクスポートできるようにするISC ライセンスについて聞いたことがない方のために付け加えておきますが、ISC ライセンスは機能的には MIT ライセンスと同等です。
詳細については、LICENSE ファイルを参照してください。