Middleware Express para servir aplicaciones de una sola página con URL pushState y mayor rendimiento
![Gitter](https://badges.gitter.im/Join Chat.svg)
Linux: Windows: General:
Serve-SPA se comporta como el middleware express.static. Sin embargo, si se solicita una URL pushState, Serve-SPA no devuelve un 404 sino que muestra la página SPA correspondiente.
Supongamos que tiene este SPA muy simple servido desde esta carpeta:
spa
|-- index.html
|-- app.js
Al principio, un visitante normalmente carga su SPA a través de la URL base http://localhost:3000/
. Por lo tanto, se sirve index.html y también se carga app.js. A continuación, el visitante navega a través de su SPA, que actualiza la URL mediante pushState y, por ejemplo, llega a http://localhost:3000/profile/me
. El visitante ahora puede marcar esta página como favorita y abrirla nuevamente al día siguiente. express.static enviaría un 404 para esta URL porque la carpeta servida no contiene una carpeta de "perfil" que contenga un archivo "yo". Serve-SPA, sin embargo, reconoce http://localhost:3000/profile/me
como una URL pushState y busca en la estructura de carpetas una página que coincida mejor con la URL dada, es decir, http://localhost:3000/
.
Todo lo que necesita hacer para activar el soporte de URL pushState es cambiar el nombre de sus archivos index.html
a index.htmlt
(con una t ). Es decir:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
Así es como se carga un SPA normal (que usa Angular.js en este caso):
El tiempo hasta que el usuario ve la página aumenta significativamente con las dos solicitudes AJAX "list.html", que es una plantilla HTML y "proyectos", que son los datos JSON utilizados para completar la página.
Con Serve-SPA puedes insertar fácilmente la plantilla/datos en index.htmlt
para que las llamadas AJAX se omitan y la página se represente inmediatamente:
Serve-SPA aporta el poder de las plantillas de lodash a sus archivos index.htmlt
. El ejemplo anterior para el SPA normal utiliza 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 >
Si el visitante solicita http://localhost:3000/
este SPA necesita la plantilla list.html
para procesarse. Para omitir la llamada AJAX que de otro modo sería necesaria, la plantilla debe estar insertada. Sin embargo, dado que otras URL de pushState no necesitan esta plantilla, solo debe incluirse si se solicita http://localhost:3000/
. Esto se puede lograr con la siguiente adición a 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>
Hay formas (por ejemplo, usando compose.js
) de implementar esto de una manera más limpia y sin bloqueos, pero ya entiendes la idea.
Si ya brinda servicio a su SPA con el middleware express.static, podrá brindarlo con Serve-SPA en su lugar:
// Just replace:
app . use ( express . static ( appDir ) ) ;
// with:
serveSpa ( app , appDir ) ;
Luego cambia el nombre de tus archivos index.html
a index.htmlt
.
app
|-- blog
| |-- img
| | |-- ...
| |
- | |-- index.html
+ | |-- index.htmlt
| |-- blog.css
| |-- blog.js
|
- |-- index.html
+ |-- index.htmlt
|-- main.css
|-- main.js
Esto le brinda compatibilidad con URL pushState y funcionalidad de creación de plantillas para incorporar plantillas HTML y datos JSON en el HTML servido para cada solicitud. Si necesita, por ejemplo, recuperar los datos de su base de datos de antemano, puede agregar un archivo compose.js
para hacerlo:
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
Por cierto, Serve-SPA no hace ninguna suposición sobre cómo se implementa su SPA en el lado del cliente. Cualquier implementación debería poder funcionar con los cambios que deben realizarse en el lado del servidor.
Consulte el repositorio de demostraciones de Serve-SPA, cuyo objetivo es proporcionar versiones regulares y migradas de SPA para marcos de SPA conocidos. Para, por ejemplo, ver cómo migrar un SPA basado en Angular.js, haga una diferencia entre su implementación normal y la versión precompuesta que aprovecha al máximo Serve-SPA.
El módulo para node.js se instala mediante npm:
npm install serve-spa --save
Serve-SPA depende de una versión vagamente definida de server-static. Si desea instalar una versión específica, instale server-static de antemano.
var serveSpa = require ( 'serve-spa' ) ;
serveSpa ( appOrRouter , rootPath , options ) ;
appOrRouter
debe tomarse de var app = express();
o var router = express.Router();
dependiendo de dónde desee montar Serve-SPA. El uso de un enrutador le permite montar el SPA en una URL específica. Por ejemplo, el SPA está montado en http://localhost:3000/app/
si usa app.use('/app', router);
.rootPath
es la ruta del sistema de archivos a los recursos SPA. El parámetro es idéntico al utilizado con app.use(express.static(rootPath))
. Además, rootPath
puede ser una serie de rutas para utilizar múltiples raíces.options
es un objeto que permite los siguientes atributos:options.staticSettings
se reenvía al middleware de servicio estático que se utiliza internamente. Por cierto, estas son las mismas opciones que se pasan a express.static(rootPath, staticSettings)
. Consulte la documentación de las opciones de server-static para obtener más detalles.options.beforeAll
toma un middleware que se ejecuta antes de que se represente una plantilla. Úselo para hacer cosas generales necesarias para todas las plantillas. Si necesita hacer cosas para una plantilla en particular, utilice un archivo compose.js.options.templateSettings
se reenvía a _.template(template, templateSettings)
de lodash. Consulte la documentación de _.templateSettings
para obtener más detalles.options.require
toma una función require particular para ser utilizada dentro de la representación de la plantilla. Si no se proporciona la opción, Serve-SPA proporciona su propia función de solicitud al representar una plantilla. Esta función requerida puede tener una jerarquía de búsqueda de ruta de módulo diferente. Por lo tanto, la opción permite pasar otra función requerida que tenga una jerarquía de búsqueda de ruta de módulo preferida.La plantilla es un archivo html que puede contener combinaciones 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 >
Esto produce:
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 de la plantilla están disponibles las siguientes variables:
_
: La biblioteca lodashrequire
: función require del nodorequest
y req
: el objeto de solicitud proporcionado por Expressresponse
y res
: el objeto de respuesta proporcionado por Express Si la representación falla, se reenvía un error al siguiente middleware de manejo de errores. Puede registrar su propio middleware de manejo de errores como este:
// 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 un archivo compose.js
en la misma carpeta que su index.htmlt
y se ejecutará justo antes de que se procese la plantilla. Esto permite, por ejemplo, recuperar datos de la base de datos para usarlos al representar la plantilla.
compose.js
debe exportar un 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 ( ) ;
} ) ;
} ;
El middleware exportado a través de compose.js
se utiliza como un middleware normal. Por tanto, el manejo de errores funciona como de costumbre.
Se puede pasar un middleware a través de options.beforeAll
que se ejecuta para todas las solicitudes que dan como resultado la representación de una plantilla. Si solo se solicita un archivo estático, este middleware no se ejecuta.
El orden general de ejecución es el siguiente:
El middleware proporcionado se utiliza como un middleware normal. Por tanto, el manejo de errores funciona como de costumbre.
Al igual que express.static(...)
Serve-SPA solo procesa solicitudes GET y HEAD. De forma predeterminada, para una solicitud HEAD, no se ejecutan los middlewares beforeAll y compose.js ni se representa la plantilla index.htmlt. Sin embargo, la ejecución del middleware se puede activar explícitamente agregando callForHEAD = true
a la función del middleware:
function middlewareForGETandHEAD ( req , res , next ) {
res . set ( 'my-header' , 'yay' ) ;
if ( req . method === 'GET' ) {
db . load ( ... ) ;
}
}
middlewareForGETandHEAD . callForHEAD = true ;
El archivo README de Serve-Static explica el uso de múltiples raíces como esta:
app . use ( serveStatic ( path . join ( __dirname , '/public-optimized' ) ) ) ;
app . use ( serveStatic ( path . join ( __dirname , '/public' ) ) ) ;
Sin embargo, con Serve-SPA, ya no es posible utilizar múltiples middlewares como este. Porque manejar adecuadamente las URL de pushState lo hace más complejo.
Para brindar el mismo soporte para múltiples raíces, Serve-SPA toma una variedad de rutas de raíz. El siguiente código es equivalente al ejemplo de Serve-Static anterior:
serveSpa ( app , [
path . join ( __dirname , '/public-optimized' ) ,
path . join ( __dirname , '/public' )
] ) ;
Una URL solo se identifica como URL pushState si ningún recurso estático en todas las rutas dadas coincide con la URL. Y se aplica una URL pushState al SPA que mejor coincida, como de costumbre, como si los directorios dados estuvieran fusionados.
Para configurar su entorno de desarrollo para Serve-SPA:
cd
de shell a la carpeta principal,npm install
,npm install gulp -g
si aún no ha instalado gulp globalmente, ygulp dev
. (O ejecute node ./node_modules/.bin/gulp dev
si no desea instalar gulp globalmente). gulp dev
observa todos los archivos fuente y, si guarda algunos cambios, borrará el código y ejecutará todas las pruebas. El informe de cobertura de la prueba se puede ver en ./coverage/lcov-report/index.html
.
Si desea depurar una prueba, debe utilizar gulp test-without-coverage
para ejecutar todas las pruebas sin oscurecer el código con la instrumentación de cobertura de la prueba.
minimatch
según lo recomendado por Node Security Platformexpress.Router()
en compose.jsexpress.Router()
En caso de que nunca haya oído hablar de la licencia ISC, es funcionalmente equivalente a la licencia MIT.
Consulte el archivo de LICENCIA para obtener más detalles.