Express 中間件可透過 PushState url 和更高的效能為單頁應用程式提供服務
![Gitter](https://badges.gitter.im/Join Chat.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,該 SPA 使用 PushState 更新 url,例如到達http://localhost:3000/profile/me
。訪客現在可以為此頁面添加書籤並在第二天再次打開它。 express.static 會為此 url 發送 404,因為所提供的資料夾不包含包含「me」檔案的「profile」資料夾。然而,Serve-SPA 將http://localhost:3000/profile/me
識別為 PushState url,並在資料夾結構中搜尋與給定 url最佳匹配的頁面,即http://localhost:3000/
。
要啟動pushState url 支持,您所需要做的就是將index.html
檔案重新命名為index.htmlt
(帶有t )。 IE:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
這是載入常規 SPA(在本例中使用 Angular.js)的方式:
由於兩個 AJAX 請求“list.html”(HTML 模板)和“projects”(用於填充頁面的 JSON 資料),用戶看到頁面的時間顯著增加。
使用 Serve-SPA,您可以輕鬆地將模板/資料內聯到index.htmlt
中,以便跳過 AJAX 呼叫並立即呈現頁面:
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來提供它:
// 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 Demos 儲存庫,該儲存庫旨在為知名 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 的位置。使用路由器可讓您將 SPA 安裝在特定的 url 上。例如,如果您使用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 mix-ins:
<!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 自述文件解釋如何使用多個根,如下所示:
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
會監視所有來源文件,如果您儲存一些更改,它將檢查程式碼並執行所有測試。測試覆蓋率報告可以從./coverage/lcov-report/index.html
查看。
如果您想偵錯測試,您應該使用gulp test-without-coverage
來執行所有測試,而不會透過測試覆蓋率工具來模糊程式碼。
minimatch
的修補版本express.Router()
問題express.Router()
如果您從未聽說過 ISC 許可證,它在功能上等同於 MIT 許可證。
有關詳細信息,請參閱許可證文件。