تطبيق الصفحة الواحدة (ويب)

تطبيق الصفحة الواحدة  اختصاراٌ (SPA)، هو تطبيق أو موقع ويب، يميزه عن غيره من التطبيقات طريقة تفاعل الصفحة مع نشاط المستخدم، فعندما يقوم المستخدم بنشاط معين على الصفحة يستدعي على أثره حدوث بعض التغييرات، يقوم تطبيق الصفحة الواحدة بتعديل محتوى الصفحة دون الحاجة إلى تحميل صفحة جديدة من الخادم، ما يميّز هذه الطريقة أنها مريحة للمستخدم ولا تزعجه بتحميل صفحات ويب متتالية، مما يجعل التطبيق أشبه ما يكون بتطبيقات سطح المكتب، أو حتى تطبيقات الأجهزة الذكية.

آلية العمل 
أما عن الآلية التي يتم بها تعديل محتوى الصفحة، فهي تتم بناءاً على النشاطات التي يقوم بها المستخدم، فمع كل نشاط جديد يتم إضافة المحتوى المناسب للصفحة، سواءا أكان محتوى HTML أو CSS أو حتى Javascript، ويمكن أن يتم ذلك كتحميل صفحة مرّة واحدة (كل المحتوى) ، أو يُضاف المحتوى بشكل متفاعل مع المستخدم وبحسب ما هو ضروري، لكن خلال هذه العملية لا يتم إطلاقا إعادة تحميل الصفحة أو ينتقل التحكم بالمحتوى إلى صفحة أخرى، من ناحية أخرى، يمكن استعمال ما يُسمّى خاصية موقع التجزئة Location Hash أو خاصية History API المدعومة من HTML5 والتي تُستخدم لتوفير تصوّر عن الصفحات المنفصلة داخل التطبيق وطريقة الوصول إليها.

يجدر الإشارة إلى أنه أحيانا أثناء تفاعل المستخدم مع تطبيق الصفحة الواحدة فإنه يجري تواصل مستمر ومتفاعل مع خادم الويب لكن كما يقال خلف الكواليس.

التاريخ 
أصل مصطلح single-page application غير واضح، وعلى الأرجح أن المفهوم تم مناقشنته في وقت مبكر آوئل الآلفية الجديدة مطلع 2003.

التقنيات والآليات المستعملة 
في الغالب ما تستعمل بعض التنيكات والإعدادات، والآدوات كأطارات العمل، لبرمجة تطبيقات ويب الصفحة الواحدة.

هاش الموقع (Location hash)

التعريف 
Location Hash هاش الموقع أو هاش التجزئة، ترمز إلى علامة المربع # التي تكون في رابط الصفحة URL على سبيل المثال: https://ar.wikipedia.org/wiki/شبكة_عنكبوتية_عالمية#تعريف

وتُعرف بشكل رسمي بـfragment identifier of the URL أي (جزء معرف الـ URL)

الرابط أعلاه يحتوي على مسار مؤدي إلى صفحة شبكة_عنكبوتية_عالمية في ويكيبيديا وفي نهايته هناك علامة # تحوي بعدها كلمة تعريف، ولو قمت بزيارة هذا الرابط سيقوم المتصفح بشكل تلقائي بالنزول والتركيزعلى قسم التعريف. تساعد هذه الخاصية على التصفح الداخلي لملفات HTML، حيث يتم إضافة ارتباط تشعبي في ملف الـ HTML يُشير إلى مُعرف ID معين في لأحد العناصر في الصفحة، وسيقوم المتصفح بشكل تلقائي بالتركيز إلى ذلك الجزء، تستخدم هذه الميزة في الكثير من المواقع منها ويكيبيديا لتسهيل الوصول إلى أجزاء معينة في الصفحة.

مثال:<a href="#something">Go To Something Section</a>
            ...
       <section id="something">
           <p>Some Content Here</p>
       </section>
       حين الضغط على Go To Something Section سيقوم المتصفح بنقلك إلى مكان العنصر نفسه بالصفحة ذاتها ويقوم بالتركيز عليه، يمكن استخدام pseudo-class :target في CSS لتنسيق العنصر بطريقة معينة في حال تم الانتقال إليه باستخدام Location hash.

الاستخدام في تطبيقات الويب ذات الصفحة الواحدة 
تستخدم هذا الخاصية كتكنيك لعمل روابط تؤدي إلى محتويات أخرى تحاكي الروابط العادية بدون قيام المتصفح بتحديث الصفحة لأن المتصفح سوف يظن أنها مجرد تنقلات داخلية.

مثال: https://example.com/#/somepage

في هذه الحالة سيكون المتصفح في الصفحة الرئيسية أي / ولكن من خلال كود جافاسكربت، يمكن برمجة الصفحة أن تقوم بتحميل أو عرض مُحتوى آخر بحسب الجزئية التي تلي ما بعد علامة المربع #.

العيوب 
لو تم استخدام هذا المفهوم في الموقع، قد لا يتم فهرسة روابط وصفحات موقعك بشكل جيد في محركات البحث، على سبيل المثال محرك بحث قوقل ومع أنه يفهم ويحلل أكواد جافاسكربت إلا أنه قد لا يقوم بفهرسة الإرتباطات التشعبية للصفحات وقد لا تظهر صفحات الموقع ضمن ناتج البحث وذلك لأن الهاش في الرابط لا يُعد صفحة صالحة بحد ذاتها، وينصح عوضًا عن ذلك استخدام History API، وكذلك حلول من جانب الخادم لضمان عرض الصفحة نفسها مهما تغير المسار في الرابط، بحيث تتمكن جافاسكربت من تحليل المسار بشكل صحيح، ويكون الموقع مناسب للفهرسة بواسطة محركات البحث.

مثال على إعدادات لسيرفر أباتشي يُرسل نفس الصفحة مهما تغير المسار في الرابط:<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>مثال على إعدادات لسيرفر http على NodeJS يرسل نفس الصفحة مهما تغير المسار في الرابط:const http = require('http')
const fs = require('fs')
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile('index.html', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.html" file.')
    }

    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})

مراجع 

تطبيقات ويب