Middleware expresso para atender aplicativos de página única com URLs pushState e maior desempenho

Linux: Windows: Geral:
Serve-SPA se comporta como o middleware express.static. No entanto, se um URL pushState for solicitado, o Serve-SPA não retornará um 404, mas exibirá a página SPA correspondente.
Suponha que você tenha este SPA muito simples servido nesta pasta:
spa
|-- index.html
|-- app.js
A princípio, um visitante geralmente carrega seu SPA através da url base http://localhost:3000/
. Assim, index.html é servido e também app.js é carregado. Em seguida, o visitante navega pelo seu SPA que atualiza a url usando pushState e, por exemplo, chega em http://localhost:3000/profile/me
. O visitante pode agora marcar esta página e abri-la novamente no dia seguinte. express.static enviaria um 404 para este URL porque a pasta servida não contém uma pasta "perfil" contendo um arquivo "me". O Serve-SPA, no entanto, reconhece http://localhost:3000/profile/me
como um URL pushState e pesquisa na estrutura de pastas uma página que corresponda melhor ao URL fornecido, ou seja, http://localhost:3000/
.
Tudo o que você precisa fazer para ativar o suporte a url pushState é renomear seus arquivos index.html
para index.htmlt
(com um t ). Ou seja:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
É assim que um SPA normal (usando Angular.js neste caso) é carregado:
O tempo até que o usuário veja a página é aumentado significativamente pelas duas solicitações AJAX "list.html", que é um modelo HTML, e "projetos", que são os dados JSON usados para preencher a página.
Com o Serve-SPA você pode facilmente incorporar o modelo/dados no index.htmlt
para que as chamadas AJAX sejam ignoradas e a página seja renderizada imediatamente:
Serve-SPA traz o poder da modelagem do lodash para seus arquivos index.htmlt
. O exemplo acima para o SPA regular usa esta página 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 >
Se o visitante solicitar http://localhost:3000/
este SPA precisará do modelo list.html
para renderizar. Para pular a chamada AJAX necessária, o modelo deve ser embutido. No entanto, como outros URLs pushState não precisam deste modelo, ele só deverá ser incorporado se http://localhost:3000/
for solicitado. Isso pode ser feito com a seguinte adição ao 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>
Existem maneiras (por exemplo, usando compose.js
) de implementar isso de uma forma mais limpa e sem bloqueios, mas essa é a ideia.
Se você já atende seu SPA com o middleware express.static, você poderá servi-lo com Serve-SPA:
// Just replace:
app . use ( express . static ( appDir ) ) ;
// with:
serveSpa ( app , appDir ) ;
Então você renomeia seus arquivos index.html
para index.htmlt
.
app
|-- blog
| |-- img
| | |-- ...
| |
- | |-- index.html
+ | |-- index.htmlt
| |-- blog.css
| |-- blog.js
|
- |-- index.html
+ |-- index.htmlt
|-- main.css
|-- main.js
Isso fornece suporte a url pushState e funcionalidade de modelagem para modelos HTML embutidos e dados JSON no HTML servido para cada solicitação. Se você precisar, por exemplo, buscar os dados do seu banco de dados com antecedência, você pode adicionar um arquivo compose.js
ao lado para fazer isso:
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
Aliás, o Serve-SPA não faz nenhuma suposição sobre como seu SPA é implementado no lado do cliente. Qualquer implementação deve ser capaz de funcionar com as alterações que precisam ser feitas no servidor.
Confira o repositório Serve-SPA Demos, que visa fornecer versões regulares e migradas de SPA para estruturas de SPA conhecidas. Por exemplo, para ver como migrar um SPA baseado em Angular.js, faça uma diferença entre sua implementação regular e a versão pré-composta que aproveita ao máximo o Serve-SPA.
O módulo para node.js é instalado via npm:
npm install serve-spa --save
Serve-SPA depende de uma versão vagamente definida de serve-static. Se você deseja instalar uma versão específica, instale serve-static com antecedência.
var serveSpa = require ( 'serve-spa' ) ;
serveSpa ( appOrRouter , rootPath , options ) ;
appOrRouter
deve ser obtido de var app = express();
ou var router = express.Router();
dependendo de onde você deseja montar o Serve-SPA. Usar um roteador permite montar o SPA em uma URL específica. Por exemplo, o SPA é montado em http://localhost:3000/app/
se você usar app.use('/app', router);
.rootPath
é o caminho do sistema de arquivos para os recursos SPA. O parâmetro é idêntico ao usado com app.use(express.static(rootPath))
. Além disso, rootPath
pode ser uma matriz de caminhos para usar múltiplas raízes.options
é um objeto que permite os seguintes atributos:options.staticSettings
é encaminhado para o middleware serve-static que é usado internamente. Aliás, essas são as mesmas opções passadas para express.static(rootPath, staticSettings)
. Consulte a documentação das opções do serve-static para obter detalhes.options.beforeAll
usa um middleware que é executado antes de um modelo ser renderizado. Use-o para fazer coisas gerais necessárias para todos os modelos. Se você precisar fazer coisas para um modelo específico, use um arquivo compose.js.options.templateSettings
é encaminhado para _.template(template, templateSettings)
do lodash. Consulte a documentação de _.templateSettings
para obter detalhes.options.require
usa uma função require específica para ser usada na renderização do modelo. Se a opção não for fornecida, o Serve-SPA fornece sua própria função require ao renderizar um modelo. Esta função require pode ter uma hierarquia de pesquisa de caminho de módulo diferente. Assim, a opção permite passar outra função require que possui uma hierarquia de pesquisa de caminho de módulo preferencial.O modelo é um arquivo html que pode conter mix-ins de 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 >
Isso produz:
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 >
Dentro do modelo estão disponíveis as seguintes variáveis:
_
: A biblioteca lodashrequire
: função require do nórequest
e req
: O objeto de solicitação fornecido pelo Expressresponse
e res
: o objeto de resposta fornecido pelo Express Se a renderização falhar, um erro será encaminhado para o próximo middleware de tratamento de erros. Você pode registrar seu próprio middleware de tratamento de erros assim:
// 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/' ) ;
} ) ;
Coloque um arquivo compose.js
na mesma pasta que seu index.htmlt
e ele será executado logo antes do modelo ser renderizado. Isto permite, por exemplo, buscar dados do banco de dados para usá-los ao renderizar o modelo.
compose.js
deve exportar um middleware:
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 ( ) ;
} ) ;
} ;
O middleware exportado por meio compose.js
é usado como um middleware normal. Assim, o tratamento de erros funciona normalmente.
Um middleware pode ser passado por options.beforeAll
que é executado para todas as solicitações que resultam na renderização de um modelo. Se apenas um arquivo estático for solicitado, este middleware não será executado.
A ordem geral de execução é a seguinte:
O middleware fornecido é usado como um middleware normal. Assim, o tratamento de erros funciona normalmente.
Assim como express.static(...)
Serve-SPA processa apenas solicitações GET e HEAD. Por padrão, para uma solicitação HEAD, nem os middlewares beforeAll e compose.js são executados nem o modelo index.htmlt é renderizado. No entanto, a execução dos middlewares pode ser ativada explicitamente adicionando callForHEAD = true
à função de middleware:
function middlewareForGETandHEAD ( req , res , next ) {
res . set ( 'my-header' , 'yay' ) ;
if ( req . method === 'GET' ) {
db . load ( ... ) ;
}
}
middlewareForGETandHEAD . callForHEAD = true ;
O README Serve-Static explica como usar múltiplas raízes como esta:
app . use ( serveStatic ( path . join ( __dirname , '/public-optimized' ) ) ) ;
app . use ( serveStatic ( path . join ( __dirname , '/public' ) ) ) ;
Com o Serve-SPA, entretanto, o uso de vários middlewares como esse não é mais possível. Porque o manuseio adequado dos URLs pushState torna tudo mais complexo.
Para fornecer o mesmo suporte a múltiplas raízes, o Serve-SPA utiliza uma série de caminhos de raiz. O código a seguir é equivalente ao exemplo Serve-Static acima:
serveSpa ( app , [
path . join ( __dirname , '/public-optimized' ) ,
path . join ( __dirname , '/public' )
] ) ;
Um URL só é identificado como um URL pushState se nenhum recurso estático em todos os caminhos fornecidos corresponder ao URL. E um url pushState é aplicado ao SPA que corresponde melhor , como de costume – como se os diretórios fornecidos fossem mesclados.
Para configurar seu ambiente de desenvolvimento para Serve-SPA:
cd
para a pasta principal,npm install
,npm install gulp -g
se você ainda não instalou o gulp globalmente egulp dev
. (Ou execute node ./node_modules/.bin/gulp dev
se não quiser instalar o gulp globalmente.) gulp dev
monitora todos os arquivos fonte e se você salvar algumas alterações ele irá limpar o código e executar todos os testes. O relatório de cobertura de teste pode ser visualizado em ./coverage/lcov-report/index.html
.
Se você deseja depurar um teste, você deve usar gulp test-without-coverage
para executar todos os testes sem obscurecer o código pela instrumentação de cobertura de teste.
minimatch
conforme recomendado pela Node Security Platformexpress.Router()
em compose.jsexpress.Router()
Caso você nunca tenha ouvido falar da licença ISC, ela é funcionalmente equivalente à licença MIT.
Consulte o arquivo LICENSE para obter detalhes.