Express-Middleware zur Bereitstellung von Single-Page-Anwendungen mit pushState-URLs und erhöhter Leistung
![Gitter](https://badges.gitter.im/Join Chat.svg)
Linux: Windows: Allgemein:
Serve-SPA verhält sich wie die express.static-Middleware. Wenn jedoch eine pushState-URL angefordert wird, gibt Serve-SPA keinen 404 zurück, sondern stellt stattdessen die passende SPA-Seite bereit.
Angenommen, Sie haben dieses sehr einfache SPA aus diesem Ordner bereitgestellt:
spa
|-- index.html
|-- app.js
Zunächst lädt ein Besucher Ihr SPA normalerweise über die Basis-URL http://localhost:3000/
. Somit wird index.html bereitgestellt und auch app.js geladen. Als nächstes navigiert der Besucher durch Ihr SPA, das die URL mithilfe von pushState aktualisiert und beispielsweise zu http://localhost:3000/profile/me
gelangt. Der Besucher kann diese Seite nun zu einem Lesezeichen hinzufügen und sie am nächsten Tag erneut öffnen. express.static würde für diese URL eine 404 senden, da der bereitgestellte Ordner keinen „profile“-Ordner mit einer „me“-Datei enthält. Serve-SPA erkennt jedoch http://localhost:3000/profile/me
als pushState-URL und durchsucht die Ordnerstruktur nach einer Seite, die am besten mit der angegebenen URL übereinstimmt, z. B. http://localhost:3000/
.
Um die pushState-URL-Unterstützung zu aktivieren, müssen Sie lediglich Ihre index.html
Dateien in index.htmlt
umbenennen (mit einem t ). Dh:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
So wird ein reguläres SPA (in diesem Fall mit Angular.js) geladen:
Die Zeit, bis der Benutzer die Seite sieht, wird durch die beiden AJAX-Anfragen „list.html“, einer HTML-Vorlage, und „projects“, den JSON-Daten, die zum Füllen der Seite verwendet werden, erheblich verlängert.
Mit Serve-SPA können Sie die Vorlage/Daten einfach in die index.htmlt
integrieren, sodass die AJAX-Aufrufe übersprungen werden und die Seite sofort gerendert wird:
Serve-SPA bringt die Leistungsfähigkeit der Vorlagen von lodash in Ihre index.htmlt
-Dateien. Das obige Beispiel für das reguläre SPA verwendet diese HTML-Seite:
<!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 >
Wenn der Besucher http://localhost:3000/
anfordert, benötigt dieses SPA zum Rendern die list.html
Vorlage. Um den ansonsten notwendigen AJAX-Aufruf zu überspringen, sollte die Vorlage inline sein. Da andere pushState-URLs diese Vorlage jedoch nicht benötigen, sollte sie nur eingebunden werden, wenn http://localhost:3000/
angefordert wird. Dies kann mit dem folgenden Zusatz zu index.htmlt
erreicht werden:
<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>
Es gibt Möglichkeiten (z. B. mithilfe von compose.js
), dies auf sauberere und nicht blockierende Weise zu implementieren, aber Sie verstehen, worauf es ankommt.
Wenn Sie Ihr SPA bereits mit der express.static-Middleware bereitstellen, können Sie es stattdessen mit Serve-SPA bereitstellen:
// Just replace:
app . use ( express . static ( appDir ) ) ;
// with:
serveSpa ( app , appDir ) ;
Anschließend benennen Sie Ihre index.html
Dateien in index.htmlt
um.
app
|-- blog
| |-- img
| | |-- ...
| |
- | |-- index.html
+ | |-- index.htmlt
| |-- blog.css
| |-- blog.js
|
- |-- index.html
+ |-- index.htmlt
|-- main.css
|-- main.js
Dadurch erhalten Sie pushState-URL-Unterstützung und Vorlagenfunktionalität, um HTML-Vorlagen und JSON-Daten in den für jede Anfrage bereitgestellten HTML-Code zu integrieren. Wenn Sie beispielsweise vorher die Daten aus Ihrer Datenbank abrufen müssen, können Sie dazu eine compose.js
Datei hinzufügen:
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
Übrigens macht Serve-SPA keine Annahmen darüber, wie Ihr SPA clientseitig implementiert wird. Jede Implementierung sollte in der Lage sein, mit den Änderungen zu arbeiten, die serverseitig vorgenommen werden müssen.
Schauen Sie sich das Serve-SPA-Demos-Repo an, das darauf abzielt, reguläre und migrierte Versionen von SPA für bekannte SPA-Frameworks bereitzustellen. Um beispielsweise zu sehen, wie man ein Angular.js-basiertes SPA migriert, machen Sie einen Unterschied zwischen seiner regulären Implementierung und der vorkomponierten Version, die Serve-SPA voll ausnutzt.
Das Modul für node.js wird über npm installiert:
npm install serve-spa --save
Serve-SPA hängt von einer lose definierten Version von Serve-Static ab. Wenn Sie eine bestimmte Version installieren möchten, installieren Sie bitte vorher servo-static.
var serveSpa = require ( 'serve-spa' ) ;
serveSpa ( appOrRouter , rootPath , options ) ;
appOrRouter
sollte entweder von var app = express();
oder var router = express.Router();
je nachdem, wo Sie Serve-SPA montieren möchten. Durch die Verwendung eines Routers können Sie das SPA unter einer bestimmten URL bereitstellen. Beispielsweise wird die SPA auf http://localhost:3000/app/
gemountet, wenn Sie app.use('/app', router);
.rootPath
ist der Dateisystempfad zu den SPA-Ressourcen. Der Parameter ist identisch mit dem, der mit app.use(express.static(rootPath))
verwendet wird. Außerdem kann rootPath
ein Array von Pfaden sein, um mehrere Roots zu verwenden.options
ist ein Objekt, das die folgenden Attribute zulässt:options.staticSettings
wird an die Serve-Static-Middleware weitergeleitet, die intern verwendet wird. Übrigens sind dies dieselben Optionen, die an express.static(rootPath, staticSettings)
übergeben werden. Weitere Informationen finden Sie in der Dokumentation der Optionen von Serve-Static.options.beforeAll
benötigt eine Middleware, die ausgeführt wird, bevor eine Vorlage gerendert wird. Verwenden Sie es, um allgemeine Aufgaben zu erledigen, die für alle Vorlagen erforderlich sind. Wenn Sie Dinge für eine bestimmte Vorlage tun müssen, verwenden Sie stattdessen eine compose.js-Datei.options.templateSettings
wird an _.template(template, templateSettings)
von lodash weitergeleitet. Weitere Informationen finden Sie in der Dokumentation von _.templateSettings
.options.require
benötigt eine bestimmte Anforderungsfunktion für die Verwendung im Vorlagenrendering. Wenn die Option nicht angegeben ist, stellt Serve-SPA beim Rendern einer Vorlage eine eigene Anforderungsfunktion bereit. Diese Anforderungsfunktion verfügt möglicherweise über eine andere Modulpfad-Suchhierarchie. Somit ermöglicht die Option die Übergabe einer weiteren Anforderungsfunktion, die über eine bevorzugte Modulpfad-Suchhierarchie verfügt.Die Vorlage ist eine HTML-Datei, die JavaScript-Mix-Ins enthalten kann:
<!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 >
Dadurch entsteht:
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 >
Innerhalb der Vorlage stehen folgende Variablen zur Verfügung:
_
: Die Lodash-Bibliothekrequire
: Anforderungsfunktion des Knotensrequest
und req
: Das von Express bereitgestellte Anforderungsobjektresponse
und res
: Das von Express bereitgestellte Antwortobjekt Wenn das Rendern fehlschlägt, wird ein Fehler an die nächste Fehlerbehandlungs-Middleware weitergeleitet. Sie können Ihre eigene Fehlerbehandlungs-Middleware wie folgt registrieren:
// 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/' ) ;
} ) ;
Legen Sie eine compose.js
Datei im selben Ordner wie Ihre index.htmlt
ab und sie wird direkt vor dem Rendern der Vorlage ausgeführt. Dies ermöglicht es beispielsweise, Daten aus der Datenbank abzurufen, um sie beim Rendern der Vorlage zu verwenden.
compose.js
muss eine Middleware exportieren:
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 ( ) ;
} ) ;
} ;
Die über compose.js
exportierte Middleware wird wie eine normale Middleware verwendet. Somit funktioniert die Fehlerbehandlung wie gewohnt.
Über options.beforeAll
kann eine Middleware übergeben werden, die für alle Anforderungen ausgeführt wird, die zum Rendern einer Vorlage führen. Wenn nur eine statische Datei angefordert wird, wird diese Middleware nicht ausgeführt.
Die allgemeine Ausführungsreihenfolge ist die folgende:
Die bereitgestellte Middleware wird wie eine normale Middleware verwendet. Somit funktioniert die Fehlerbehandlung wie gewohnt.
Wie express.static(...)
verarbeitet Serve-SPA nur GET- und HEAD-Anfragen. Standardmäßig werden für eine HEAD-Anfrage weder die Middlewares beforeAll und compose.js ausgeführt noch die Vorlage index.htmlt gerendert. Die Ausführung der Middlewares kann jedoch explizit aktiviert werden, indem callForHEAD = true
zur Middleware-Funktion hinzugefügt wird:
function middlewareForGETandHEAD ( req , res , next ) {
res . set ( 'my-header' , 'yay' ) ;
if ( req . method === 'GET' ) {
db . load ( ... ) ;
}
}
middlewareForGETandHEAD . callForHEAD = true ;
Die Serve-Static README erklärt die Verwendung mehrerer Roots wie folgt:
app . use ( serveStatic ( path . join ( __dirname , '/public-optimized' ) ) ) ;
app . use ( serveStatic ( path . join ( __dirname , '/public' ) ) ) ;
Mit Serve-SPA ist die Verwendung mehrerer Middlewares wie dieser jedoch nicht mehr möglich. Denn der richtige Umgang mit den pushState-URLs macht es komplexer.
Um die gleiche Unterstützung mehrerer Roots bereitzustellen, verwendet Serve-SPA ein Array von Root-Pfaden. Der folgende Code entspricht dem Serve-Static-Beispiel oben:
serveSpa ( app , [
path . join ( __dirname , '/public-optimized' ) ,
path . join ( __dirname , '/public' )
] ) ;
Eine URL wird nur dann als pushState-URL identifiziert, wenn keine statische Ressource in allen angegebenen Pfaden mit der URL übereinstimmt. Und wie üblich wird eine pushState-URL auf die SPA angewendet, die am besten übereinstimmt – als ob die angegebenen Verzeichnisse zusammengeführt würden.
So richten Sie Ihre Entwicklungsumgebung für Serve-SPA ein:
cd
in den Hauptordner,npm install
,npm install gulp -g
wenn Sie gulp noch nicht global installiert haben, undgulp dev
aus. (Oder führen Sie node ./node_modules/.bin/gulp dev
aus, wenn Sie gulp nicht global installieren möchten.) gulp dev
überwacht alle Quelldateien und wenn Sie einige Änderungen speichern, wird der Code gelintet und alle Tests ausgeführt. Der Testabdeckungsbericht kann unter ./coverage/lcov-report/index.html
eingesehen werden.
Wenn Sie einen Test debuggen möchten, sollten Sie gulp test-without-coverage
verwenden, um alle Tests auszuführen, ohne den Code durch die Testabdeckungsinstrumentierung zu verdecken.
minimatch
zu installieren, wie von der Node Security Platform empfohlenexpress.Router()
in compose.jsexpress.Router()
exportiertFalls Sie noch nie von der ISC-Lizenz gehört haben, entspricht sie funktional der MIT-Lizenz.
Einzelheiten finden Sie in der LICENSE-Datei.