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 许可证。
有关详细信息,请参阅许可证文件。