برنامج وسيط سريع لخدمة تطبيقات الصفحة الواحدة باستخدام عناوين url الخاصة بـ PushState وزيادة الأداء
![Gitter](https://badges.gitter.im/Join Chat.svg)
لينكس: ويندوز: عام:
يتصرف برنامجserv-SPA مثل البرنامج الوسيط Express.static. ومع ذلك، إذا تم طلب عنوان url الخاص بـ PushState، فإن خدمة 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 هذا لأن المجلد المقدم لا يحتوي على مجلد "ملف تعريف" يحتوي على ملف "أنا". ومع ذلك، يتعرف Service-SPA على http://localhost:3000/profile/me
كعنوان URL لـ PushState ويبحث في بنية المجلد عن صفحة تتطابق مع عنوان url المحدد بشكل أفضل ، على سبيل المثال http://localhost:3000/
.
كل ما عليك فعله لتنشيط دعم عنوان URL الخاص بـ PushState هو إعادة تسمية ملفات 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 المستخدمة لملء الصفحة.
باستخدام خدمة-SPA، يمكنك بسهولة تضمين القالب/البيانات في ملف index.htmlt
بحيث يتم تخطي مكالمات AJAX ويتم عرض الصفحة على الفور:
يقدم برنامجserv-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، فستتمكن من خدمته باستخدام Service-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
راجع للشغل، لا تقدم خدمة SPA أي افتراضات حول كيفية تنفيذ SPA الخاص بك من جانب العميل. يجب أن يكون أي تطبيق قادرًا على العمل مع التغييرات التي يجب إجراؤها من جانب الخادم.
تحقق من مستودع خدمة-SPA التجريبي الذي يهدف إلى توفير إصدارات منتظمة ومنقولة من SPA لأطر عمل SPA المعروفة. لمعرفة على سبيل المثال كيفية ترحيل SPA المستند إلى Angular.js، قم بإجراء فرق بين التنفيذ المنتظم والإصدار المُركب مسبقًا والذي يستفيد بشكل كامل من Service-SPA.
يتم تثبيت وحدة Node.js عبر npm:
npm install serve-spa --save
تعتمد خدمة SPA على نسخة محددة بشكل فضفاض من خدمة ثابتة. إذا كنت ترغب في تثبيت إصدار معين، فيرجى تثبيتserv-static مسبقًا.
var serveSpa = require ( 'serve-spa' ) ;
serveSpa ( appOrRouter , rootPath , options ) ;
appOrRouter
من var app = express();
أو var router = express.Router();
اعتمادًا على المكان الذي تريد تركيب Service-SPA فيه. يتيح لك استخدام جهاز التوجيه تثبيت SPA على عنوان URL محدد. على سبيل المثال، تم تثبيت SPA على http://localhost:3000/app/
إذا كنت تستخدم app.use('/app', router);
.rootPath
هو مسار نظام الملفات إلى موارد SPA. المعلمة مطابقة لتلك المستخدمة مع app.use(express.static(rootPath))
. أيضًا، قد يكون rootPath
عبارة عن مجموعة من المسارات لاستخدام جذور متعددة.options
هي كائن يسمح بالسمات التالية:options.staticSettings
إلى البرنامج الوسيط للخدمة الثابتة الذي يتم استخدامه داخليًا. راجع للشغل، هذه هي نفس الخيارات التي تم تمريرها إلى express.static(rootPath, staticSettings)
. راجع وثائق خيارات خدمة ثابتة للحصول على التفاصيل.options.beforeAll
برنامجًا وسيطًا يتم تنفيذه قبل عرض القالب. استخدمه للقيام بالأشياء العامة المطلوبة لجميع القوالب. إذا كنت بحاجة إلى القيام بأشياء تتعلق بقالب معين، فاستخدم ملف compose.js بدلاً من ذلك.options.templateSettings
إلى _.template(template, templateSettings)
الخاص بـ lodash. راجع وثائق _.templateSettings
للحصول على التفاصيل.options.require
دالة طلب معينة لاستخدامها في عرض القالب. إذا لم يتم إعطاء الخيار، فإن خدمة-SPA توفر وظيفة الطلب الخاصة بها عند عرض القالب. قد تحتوي هذه الوظيفة المطلوبة على تسلسل هرمي مختلف للبحث عن مسار الوحدة النمطية. وبالتالي يسمح الخيار بتمرير وظيفة أخرى تتطلب التسلسل الهرمي المفضل للبحث عن مسار الوحدة النمطية.القالب عبارة عن ملف 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 >
تتوفر المتغيرات التالية داخل القالب:
_
: مكتبة لوداشrequire
: وظيفة تتطلب العقدةrequest
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 ;
يوضح الملف README الخاص بـserve-Static كيفية استخدام جذور متعددة مثل هذا:
app . use ( serveStatic ( path . join ( __dirname , '/public-optimized' ) ) ) ;
app . use ( serveStatic ( path . join ( __dirname , '/public' ) ) ) ;
ومع ذلك، مع خدمة-SPA، لم يعد من الممكن استخدام برامج وسيطة متعددة مثل هذه. لأن التعامل بشكل صحيح مع عناوين URL الخاصة بـ PushState يجعلها أكثر تعقيدًا.
لتوفير نفس الدعم لجذور متعددة، يأخذserv-SPA مجموعة من مسارات الجذر. التعليمة البرمجية التالية تعادل مثال الخدمة الثابتة أعلاه:
serveSpa ( app , [
path . join ( __dirname , '/public-optimized' ) ,
path . join ( __dirname , '/public' )
] ) ;
يتم تعريف عنوان url فقط على أنه عنوان URL الخاص بـ PushState إذا لم يتطابق أي مورد ثابت في جميع المسارات المحددة مع عنوان url. ويتم تطبيق عنوان URL الخاص بـ PushState على SPA الذي يتطابق بشكل أفضل كالمعتاد - كما لو تم دمج الدلائل المحددة.
لإعداد بيئة التطوير الخاصة بك لـ Service-SPA:
cd
المضغوط Shell إلى المجلد الرئيسي،npm install
npm install gulp -g
إذا لم تكن قد قمت بتثبيت gulp عالميًا بعد، وgulp dev
. (أو قم بتشغيل node ./node_modules/.bin/gulp dev
إذا كنت لا تريد تثبيت gulp عالميًا.) يراقب gulp dev
جميع الملفات المصدر وإذا قمت بحفظ بعض التغييرات، فسوف يقوم بمسح الكود وتنفيذ جميع الاختبارات. يمكن الاطلاع على تقرير تغطية الاختبار من ./coverage/lcov-report/index.html
.
إذا كنت تريد تصحيح أحد الاختبارات، فيجب عليك استخدام gulp test-without-coverage
لتشغيل جميع الاختبارات دون حجب الكود بواسطة أدوات تغطية الاختبار.
minimatch
وفقًا لما نصحت به منصة Node Security Platformexpress.Router()
في compose.jsexpress.Router()
في حال لم تسمع مطلقًا عن ترخيص مركز الدراسات الدولي (ISC)، فهو يعادل وظيفيًا ترخيص MIT.
راجع ملف الترخيص للحصول على التفاصيل.