มิดเดิลแวร์ Express เพื่อให้บริการแอปพลิเคชันหน้าเดียวด้วย URL pushState และประสิทธิภาพที่เพิ่มขึ้น
![Gitter](https://badges.gitter.im/Join Chat.svg)
ลินุกซ์: 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 นี้เนื่องจากโฟลเดอร์ที่ให้บริการไม่มีโฟลเดอร์ "profile" ที่มีไฟล์ "me" อย่างไรก็ตาม 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 ในกรณีนี้):
เวลาที่ผู้ใช้เห็นหน้าเว็บจะเพิ่มขึ้นอย่างมากโดยคำขอ AJAX ทั้งสองรายการ "list.html" ซึ่งเป็นเทมเพลต HTML และ "โครงการ" ซึ่งเป็นข้อมูล JSON ที่ใช้ในการเติมข้อมูลหน้าเว็บ
ด้วย Serve-SPA คุณสามารถอินไลน์เทมเพลต/ข้อมูลลงใน index.htmlt
ได้อย่างง่ายดาย เพื่อที่จะข้ามการเรียก AJAX และเพจจะแสดงผลทันที:
Serve-SPA นำพลังของการสร้างเทมเพลตของ Lodash มาสู่ไฟล์ index.htmlt
ของคุณ ตัวอย่างข้างต้นสำหรับ 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
BTW, Serve-SPA ไม่ได้ตั้งสมมติฐานใดๆ เกี่ยวกับวิธีการใช้งาน SPA ของคุณฝั่งไคลเอ็นต์ การใช้งานใดๆ ควรจะสามารถทำงานร่วมกับการเปลี่ยนแปลงที่ต้องทำฝั่งเซิร์ฟเวอร์ได้
ชำระเงิน repo สาธิต Serve-SPA ซึ่งมีจุดมุ่งหมายเพื่อมอบ SPA เวอร์ชันปกติและแบบย้ายข้อมูลสำหรับเฟรมเวิร์ก SPA ที่รู้จักกันดี หากต้องการดูวิธีการโยกย้าย SPA ที่ใช้ Angular.js ให้สร้างความแตกต่างระหว่างการใช้งานปกติและเวอร์ชันที่เขียนไว้ล่วงหน้าซึ่งใช้ประโยชน์จาก Serve-SPA อย่างเต็มที่
โมดูลสำหรับ node.js ได้รับการติดตั้งผ่านทาง npm:
npm install serve-spa --save
Serve-SPA ขึ้นอยู่กับเวอร์ชันของ service-static ที่กำหนดไว้อย่างหลวมๆ หากคุณต้องการติดตั้งเวอร์ชันใดเวอร์ชันหนึ่ง โปรดติดตั้ง server-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
จะถูกส่งต่อไปยังมิดเดิลแวร์ที่ให้บริการแบบคงที่ที่ใช้ภายใน BTW เหล่านี้เป็นตัวเลือกเดียวกับที่ส่งผ่านไปยัง express.static(rootPath, staticSettings)
ดูเอกสารประกอบตัวเลือกของserv-staticเพื่อดูรายละเอียดoptions.beforeAll
รับมิดเดิลแวร์ที่ดำเนินการก่อนที่จะเรนเดอร์เทมเพลต ใช้เพื่อทำสิ่งทั่วไปที่จำเป็นสำหรับเทมเพลตทั้งหมด หากคุณต้องการทำสิ่งต่างๆ สำหรับเทมเพลตใดเทมเพลตหนึ่ง ให้ใช้ไฟล์ compose.js แทนoptions.templateSettings
จะถูกส่งต่อไปยัง _.template(template, templateSettings)
ของ lodash ดูเอกสารประกอบของ _.templateSettings
สำหรับรายละเอียดoptions.require
ใช้ฟังก์ชัน need เฉพาะเพื่อใช้ภายในการแสดงผลเทมเพลต หากไม่ได้ระบุตัวเลือก Serve-SPA จะมีฟังก์ชันความต้องการของตัวเองเมื่อสร้างการแสดงผลเทมเพลต ฟังก์ชัน need นี้อาจมีลำดับชั้นการค้นหาพาธโมดูลที่แตกต่างกัน ดังนั้นตัวเลือกนี้จึงอนุญาตให้ส่งผ่านฟังก์ชัน need อื่นที่มีลำดับชั้นการค้นหาเส้นทางโมดูลที่ต้องการเทมเพลตเป็นไฟล์ 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 >
ภายในเทมเพลตมีตัวแปรต่อไปนี้:
_
: ห้องสมุด Lodashrequire
: ฟังก์ชัน need ของโหนดrequest
and req
: วัตถุคำขอที่จัดทำโดย Expressresponse
และ 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
จะเฝ้าดูไฟล์ต้นฉบับทั้งหมด และหากคุณบันทึกการเปลี่ยนแปลงบางอย่าง มันจะเป็นการ Lint โค้ดและดำเนินการทดสอบทั้งหมด สามารถดูรายงานความครอบคลุมการทดสอบได้จาก ./coverage/lcov-report/index.html
หากคุณต้องการดีบักการทดสอบ คุณควรใช้ gulp test-without-coverage
เพื่อรันการทดสอบทั้งหมดโดยไม่ปิดบังโค้ดด้วยเครื่องมือวัดความครอบคลุมของการทดสอบ
minimatch
เวอร์ชันแพตช์ตามคำแนะนำของ Node Security Platformexpress.Router()
ใน compose.jsexpress.Router()
ในกรณีที่คุณไม่เคยได้ยินเกี่ยวกับใบอนุญาต ISC มาก่อน ใบอนุญาตนี้จะเทียบเท่ากับใบอนุญาต MIT
ดูไฟล์ใบอนุญาตสำหรับรายละเอียด