Промежуточное программное обеспечение Express для обслуживания одностраничных приложений с URL-адресами pushState и повышенной производительностью.
![Гиттер](https://badges.gitter.im/Join Chat.svg)
Linux: Windows: Общие:
Serve-SPA ведет себя как промежуточное ПО express.static. Однако если запрашивается URL-адрес pushState, Serve-SPA не возвращает 404, а вместо этого обслуживает соответствующую страницу SPA.
Предположим, у вас есть очень простой SPA, обслуживаемый из этой папки:
spa
|-- index.html
|-- app.js
Сначала посетитель обычно загружает ваш SPA через базовый URL http://localhost:3000/
. Таким образом, обслуживается index.html, а также загружается app.js. Затем посетитель перемещается по вашему SPA, который обновляет URL-адрес с помощью pushState и, например, попадает на http://localhost:3000/profile/me
. Теперь посетитель может добавить эту страницу в закладки и открыть ее снова на следующий день. express.static отправит ошибку 404 для этого URL-адреса, поскольку обслуживаемая папка не содержит папки «профиль», содержащей файл «я». Однако Serve-SPA распознает http://localhost:3000/profile/me
как URL-адрес pushState и ищет в структуре папок страницу, которая лучше всего соответствует данному URL-адресу, то есть http://localhost:3000/
.
Все, что вам нужно сделать, чтобы активировать поддержку URL-адресов pushState, — это переименовать файлы index.html
в index.htmlt
(с символом t ). Т.е.:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
Вот как загружается обычный SPA (в данном случае с использованием Angular.js):
Время, пока пользователь не увидит страницу, значительно увеличивается за счет двух запросов AJAX «list.html», которые представляют собой шаблон HTML, и «проектов», которые представляют собой данные JSON, используемые для заполнения страницы.
С помощью Serve-SPA вы можете легко встроить шаблон/данные в index.htmlt
, чтобы вызовы AJAX были пропущены и страница отображалась немедленно:
Serve-SPA привносит в ваши файлы index.htmlt
возможности шаблонов lodash. В приведенном выше примере обычного 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, шаблон должен быть встроен. Однако, поскольку другим URL-адресам pushState этот шаблон не нужен, его следует вставлять только в том случае, если запрашивается 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
) реализовать это более чистым и неблокирующим способом, но суть вы поняли.
Если вы уже обслуживаете свой SPA с помощью промежуточного программного обеспечения express.static, вы сможете вместо этого обслуживать его с помощью 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
Это дает вам поддержку URL-адресов pushState и функции создания шаблонов для встраивания шаблонов 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. Например, чтобы увидеть, как мигрировать SPA на основе Angular.js, определите разницу между его обычной реализацией и предварительно составленной версией, которая в полной мере использует преимущества Serve-SPA.
Модуль для node.js устанавливается через npm:
npm install serve-spa --save
Serve-SPA зависит от слабо определенной версии serve-static. Если вы хотите установить конкретную версию, пожалуйста, установите require-static заранее.
var serveSpa = require ( 'serve-spa' ) ;
serveSpa ( appOrRouter , rootPath , options ) ;
appOrRouter
должен быть взят из var app = express();
или var router = express.Router();
в зависимости от того, где вы хотите установить Serve-SPA. Использование маршрутизатора позволяет смонтировать SPA по определенному URL-адресу. Например, SPA монтируется по http://localhost:3000/app/
если вы используете app.use('/app', router);
.rootPath
— это путь файловой системы к ресурсам SPA. Параметр идентичен тому, который используется с app.use(express.static(rootPath))
. Кроме того, rootPath
может представлять собой массив путей для использования нескольких корней.options
— это объект, который допускает следующие атрибуты:options.staticSettings
пересылается в промежуточное программное обеспечение, которое используется внутри. Кстати, это те же параметры, которые передаются в express.static(rootPath, staticSettings)
. Подробности смотрите в документации по опциям Serve-Static.options.beforeAll
принимает промежуточное программное обеспечение, которое выполняется перед визуализацией шаблона. Используйте его для выполнения общих действий, необходимых для всех шаблонов. Если вам нужно что-то для определенного шаблона, используйте вместо этого файл compose.js.options.templateSettings
пересылается в _.template(template, templateSettings)
lodash. Подробности смотрите в документации _.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 использование нескольких промежуточных программ больше невозможно. Потому что правильная обработка URL-адресов pushState усложняет задачу.
Чтобы обеспечить такую же поддержку нескольких корней, Serve-SPA использует массив корневых путей. Следующий код эквивалентен приведенному выше примеру Serve-Static:
serveSpa ( app , [
path . join ( __dirname , '/public-optimized' ) ,
path . join ( __dirname , '/public' )
] ) ;
URL-адрес идентифицируется как URL-адрес pushState только в том случае, если ни один статический ресурс во всех заданных путях не соответствует URL-адресу. И URL-адрес pushState применяется к SPA, который лучше всего соответствует обычному — как если бы данные каталоги были объединены.
Чтобы настроить среду разработки для Serve-SPA:
cd
в основную папку,npm install
,npm install gulp -g
если вы еще не установили gulp глобально, иgulp dev
. (Или запустите node ./node_modules/.bin/gulp dev
если вы не хотите устанавливать gulp глобально.) gulp dev
просматривает все исходные файлы, и если вы сохраните некоторые изменения, он проверит код и выполнит все тесты. Отчет о покрытии тестами можно просмотреть по адресу ./coverage/lcov-report/index.html
.
Если вы хотите отладить тест, вам следует использовать gulp test-without-coverage
для запуска всех тестов, не скрывая код инструментами тестового покрытия.
minimatch
в соответствии с рекомендациями Node Security Platform.express.Router()
в compose.js.express.Router()
Если вы никогда не слышали о лицензии ISC, она функционально эквивалентна лицензии MIT.
Подробности смотрите в файле ЛИЦЕНЗИИ.