pushState URL과 향상된 성능으로 단일 페이지 애플리케이션을 제공하는 Express 미들웨어
![기터](https://badges.gitter.im/Join Chat.svg)
리눅스: 윈도우: 일반:
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도 로드됩니다. 다음으로 방문자는 pushState를 사용하여 URL을 업데이트하는 SPA를 탐색하고 예를 들어 http://localhost:3000/profile/me
에 도착합니다. 이제 방문자는 이 페이지를 북마크에 추가하고 다음 날 다시 열 수 있습니다. express.static은 제공된 폴더에 "me" 파일이 포함된 "profile" 폴더가 없기 때문에 이 URL에 대해 404를 보냅니다. 그러나 Serve-SPA는 http://localhost:3000/profile/me
pushState URL로 인식하고 폴더 구조에서 주어진 URL과 가장 일치하는 페이지(예: http://localhost:3000/
)를 검색합니다.
pushState URL 지원을 활성화하기 위해 해야 할 일은 index.html
파일의 이름을 index.htmlt
( t 포함)로 바꾸는 것뿐입니다. 즉:
spa
|-- index.htmlt <-- Just renamed and pushState urls are supported
|-- app.js
다음은 일반 SPA(이 경우 Angular.js 사용)가 로드되는 방법입니다.
사용자가 페이지를 볼 때까지의 시간은 HTML 템플릿인 "list.html"과 페이지를 채우는 데 사용되는 JSON 데이터인 "projects"라는 두 가지 AJAX 요청으로 인해 크게 늘어납니다.
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 호출을 건너뛰려면 템플릿을 인라인해야 합니다. 그러나 다른 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
이는 각 요청에 대해 제공되는 HTML에 HTML 템플릿과 JSON 데이터를 인라인하기 위한 pushState URL 지원 및 템플릿 기능을 제공합니다. 예를 들어 데이터베이스에서 미리 데이터를 가져와야 하는 경우 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가 클라이언트 측에서 구현되는 방식에 대해 어떠한 가정도 하지 않습니다. 모든 구현은 서버 측에서 수행해야 하는 변경 사항과 함께 작동할 수 있어야 합니다.
잘 알려진 SPA 프레임워크에 대한 SPA의 일반 버전과 마이그레이션된 버전을 제공하는 것을 목표로 하는 Serve-SPA Demos 저장소를 확인하세요. 예를 들어 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를 마운트하려는 위치에 따라 다릅니다. 라우터를 사용하면 특정 URL에 SPA를 마운트할 수 있습니다. 예를 들어 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
템플릿 렌더링 내에서 사용하기 위해 특정 요구 기능을 사용합니다. 옵션이 주어지지 않으면 Serve-SPA는 템플릿을 렌더링할 때 자체적인 require 기능을 제공합니다. 이 require 함수는 다른 모듈 경로 조회 계층 구조를 가질 수 있습니다. 따라서 이 옵션을 사용하면 선호하는 모듈 경로 조회 계층 구조가 있는 다른 require 함수를 전달할 수 있습니다.템플릿은 JavaScript 믹스인을 포함할 수 있는 HTML 파일입니다.
<!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를 사용하면 이와 같은 여러 미들웨어를 더 이상 사용할 수 없습니다. 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 라이센스와 동일합니다.
자세한 내용은 LICENSE 파일을 참조하세요.