
كنت دائمًا مهتمًا بكيفية ترتيب حبر من الداخل ، وكيفية بناء سير العمل ، وكيفية بناء الاتصالات ، والمعايير المطبقة ، وكيفية كتابة الكود هنا. لحسن الحظ ، ظهرت لي مثل هذه الفرصة ، لأنني أصبحت مؤخرًا جزءًا من المهارة. باستخدام مثال إعادة تشكيل صغيرة لنسخة الجوال ، سأحاول الإجابة على السؤال: ما هو شكل العمل هنا في المقدمة. في البرنامج: Node و Vue و Vuex و SSR مع صلصة من الملاحظات على التجربة الشخصية في Habré.
أول ما تحتاج إلى معرفته عن فريق التطوير هو أننا قليلون. قليل منها ثلاث جبهات ، ظهران وتقنيان لكل هابر باكسلي. بالطبع ، هناك أيضًا اختبار ومصمم وثلاثة Vadim ومكنسة معجزة ومسوق وغيرها من Bumburums. لكن لا يوجد سوى ستة مساهمين مباشرين في أنواع هبرة. هذا نادر جدًا - المشروع الذي يبلغ جمهوره بملايين الدولارات والذي يبدو وكأنه مؤسسة عملاقة من الخارج هو في الواقع أشبه ببدء التشغيل المريح مع الهيكل التنظيمي الأكثر ثباتًا.
مثل العديد من شركات تكنولوجيا المعلومات الأخرى ، يدرك هابر أفكار Agile ، وممارسة CI ، وهذا كل شيء. ولكن وفقًا لمشاعري ، يتطور هبر كمنتج بشكل غير متموج أكثر منه باستمرار. لذلك ، بالنسبة للعديد من السباقات المتتالية ، فإننا نعمل بجد على ترميز وتصميم وإعادة تصميم وكسر شيء ما وتحديد وإصلاح التذاكر وبدء إصدار تذاكر جديدة والخطوة على أشعل النار واطلاق النار على أنفسنا في الأرجل لإطلاق الميزة أخيرًا في برود. ثم تأتي فترة هدوء ، فترة إعادة تطوير ، وقت فعل ما هو في الربع "غير الملح".
فقط حول مثل هذا العدو "خارج الموسم" سيتم مناقشته أدناه. هذه المرة حصلت على إعادة بيع النسخة المحمولة من هبر. بشكل عام ، لدى الشركة آمال كبيرة عليها ، وفي المستقبل يجب أن تحل محل حديقة حيوانات Habr المتجسدة بالكامل وتصبح حلاً عالميًا متعدد المنصات. في يوم من الأيام ، سيظهر تخطيط قابل للتكيف ، و PWA ، ووضع غير متصل بالإنترنت ، وتخصيص المستخدم ، والكثير من الأشياء المثيرة للاهتمام.
وضعنا المهمة
مرة واحدة ، في موقف عادي ، تحدثت إحدى الجبهات عن مشاكل في بنية مكون التعليقات في إصدار الهاتف المحمول. من هذا العرض التقديمي ، قمنا بتنظيم اجتماع مصغر في شكل العلاج النفسي الجماعي. قال كل منهم بدوره أين كان يعاني من الألم ، تم تثبيت كل شيء على الورق ، وتعاطفه ، وفهمه ، إلا أنه لم يصفق أحد. وكانت النتيجة قائمة من 20 مشكلة ، والتي أوضحت أن الهاتف المحمول Habr يجب أن يسلك طريقًا طويلًا وشائكًا لتحقيق النجاح.
وكان شاغلي الرئيسي هو كفاءة الموارد وما يسمى بالواجهة السلسة. كل يوم على طريق "العمل في المنزل - المنزل" ، رأيت هاتفي القديم يحاول بشدة عرض 20 عنوانًا في الدفق. بدا الأمر مثل هذا:
واجهة هبر المتنقلة قبل إعادة بيعهاما الذي يحدث هنا؟ باختصار ، أعطى الخادم صفحة HTML للجميع بنفس الطريقة ، بصرف النظر عما إذا كان المستخدم قد قام بتسجيل الدخول أم لا. ثم يتم تحميل العميل JS ويطلب مرة أخرى البيانات اللازمة ، ولكن مع تعديل الترخيص. هذا هو ، في الواقع ، لقد فعلنا نفس العمل مرتين. تومض الواجهة ، وقام المستخدم بتنزيل مئات الكيلوبايت الإضافي. في التفاصيل ، بدا كل شيء أكثر زاحف.
دائرة SSR القديمة CSR. يكون التفويض ممكنًا فقط في المرحلتين C3 و C4 ، عندما لا تكون Node JS مشغولة في إنشاء HTML ويمكنها تقديم طلبات API بالوكالة.تم وصف بنياننا في ذلك الوقت بدقة شديدة بواسطة أحد مستخدمي Habr:
النسخة المحمولة هي القرف. أنا أتكلم كما هو. مزيج رهيب من SSR جنبا إلى جنب مع المسؤولية الاجتماعية للشركات.
كان علينا أن نعترف بذلك ، مهما كان محزنًا.
لقد اكتشفت الخيارات ، وحددت لنفسي تذكرة في "جيرا" مع وصف على مستوى "الآن أصبح أمرًا سيئًا ، واجعل القواعد" ، وبالحدود العريضة ، قمت بتحليل المهمة:
- إعادة استخدام البيانات
- تقليل عدد من يعيد رسم ،
- استبعاد الطلبات المكررة
- جعل عملية التحميل أكثر وضوحا.
إعادة استخدام البيانات
من الناحية النظرية ، تم تصميم العرض من جانب الخادم لحل مشكلتين: عدم التعرض لقيود محركات البحث فيما يتعلق
بفهرسة SPA وتحسين مقياس
FMP (
TTI الذي يزداد سوءًا). في السيناريو الكلاسيكي ، الذي تمت
صياغته أخيرًا
في Airbnb في عام 2013 (مرة أخرى في Backbone.js) ، فإن SSR هو نفس تطبيق JS المتماثل الذي يعمل في بيئة العقدة. يعرض الخادم ببساطة التصميم الذي تم إنشاؤه استجابةً للطلب. ثم يحدث الإماهة من جانب العميل ، ثم يعمل كل شيء دون إعادة تحميل الصفحة. بالنسبة إلى Habr ، وكذلك بالنسبة للعديد من الموارد الأخرى المملوءة بالنص ، يعد تقديم الخادم عنصرًا أساسيًا في بناء علاقات ودية مع محركات البحث.
على الرغم من مرور أكثر من ست سنوات على ظهور التكنولوجيا ، وخلال هذا الوقت ، تدفقت كثيرًا من المياه في عالم الواجهة الأمامية ، فبالنسبة للعديد من المطورين ، لا تزال هذه الفكرة مغطاة بحجاب من السرية. لم نقف جانبا ، وشرعنا في تطبيق Vue مع دعم SSR للمنتج ، وفقدنا تفاصيل صغيرة واحدة: لم نرفض الحالة الأولية للعميل.
لماذا؟ لا توجد إجابة دقيقة على هذا السؤال. إما أنهم لا يريدون زيادة حجم الاستجابة من الخادم ، أو بسبب مجموعة من المشكلات المعمارية الأخرى ، أو ببساطة لم يقلعوا. بطريقة أو بأخرى ، يبدو أن رمي الحالة وإعادة استخدام كل شيء فعله الخادم مناسب ومفيد تمامًا. المهمة في الواقع تافهة -
الدولة ببساطة تضخ نفسها في سياق التنفيذ ، ويضيفها Vue تلقائيًا إلى التخطيط الذي تم إنشاؤه كمتغير عام:
window.__INITIAL_STATE__
.
كانت إحدى المشكلات التي نشأت عدم القدرة على تحويل الهياكل
الدائرية إلى JSON ؛ تم حلها ببساطة عن طريق استبدال هذه الهياكل مع نظائرها المسطحة.
بالإضافة إلى ذلك ، عند التعامل مع محتوى UGC ، تذكر أنه يجب تحويل البيانات إلى كيانات HTML حتى لا يتم فصل HTML. لهذه الأغراض نستخدمها
هو .
تقليل يعيد رسم
كما يتضح من الرسم البياني أعلاه ، في حالتنا ، تؤدي إحدى مثيلات Node JS وظيفتين: SSR و "proxy" في واجهة برمجة التطبيقات ، حيث يتم ترخيص المستخدم. هذا الظرف يجعل التخويل مستحيلًا في وقت تنفيذ كود JS على الخادم ، نظرًا لأن العقدة مترابط واحدة ووظيفة SSR متزامنة. وهذا يعني أن الخادم ببساطة لا يمكنه إرسال الطلبات إلى نفسه بينما مكدس الاتصال مشغول بشيء ما. اتضح أننا تخطينا الحالة ، ولكن الواجهة لم تتوقف عن الارتعاش ، حيث يجب تحديث البيانات الموجودة على العميل مع مراعاة جلسة المستخدم. كان من الضروري تعليم تطبيقنا وضع البيانات الصحيحة في الحالة الأولية ، مع مراعاة تسجيل دخول المستخدم.
لم يكن هناك سوى حلين للمشكلة:
- التشبث بيانات التخويل لطلبات interserver ؛
- تقسيم طبقات عقدة JS إلى حالتين منفصلتين.
الحل الأول يتطلب استخدام المتغيرات العامة على الخادم ، والثاني مدد الوقت اللازم لإكمال المهمة لمدة شهر على الأقل.
كيفية اتخاذ خيار؟ غالباً ما يتحرك حبر على طريق المقاومة الأقل. بشكل غير رسمي ، هناك رغبة عامة معينة لتقليل الدورة من الفكرة إلى النموذج الأولي. يذكرنا نموذج المواقف تجاه المنتج إلى حد ما بمسلمات booking.com ، مع الاختلاف الوحيد هو أن Habr أكثر جدية بشأن ملاحظات المستخدم ويثق في اعتماد مثل هذه القرارات لك كمطور.
بعد هذا المنطق ورغبتي في حل المشكلة بسرعة ، اخترت المتغيرات العالمية. وكما يحدث هذا غالبًا ، يتعين عليهم الدفع عاجلاً أم آجلاً. لقد دفعنا على الفور تقريبًا: لقد عملنا في عطلة نهاية الأسبوع ، واكتشفنا العواقب ، وكتبنا حالة
الوفاة وبدأنا تقسيم الخادم إلى قسمين. كان الخطأ غبيًا جدًا ، ولم يكن من السهل التكاثر في مشاركتها. ونعم ، لمثل هذا العار ، ولكن بطريقة ما ، تعثر وشخير ، ما زال عملي في البرنامج الخاص بي مع المتغيرات العالمية قيد الإنتاج ويعمل بنجاح كبير تحسباً للانتقال إلى بنية "يومين" جديدة. كانت هذه خطوة مهمة ، لأنه تم تحقيق الهدف رسميًا - تعلم SSR إعطاء صفحة جاهزة تمامًا للاستخدام ، وأصبحت واجهة المستخدم أكثر هدوءًا.
واجهة هبر المتنقلة بعد المرحلة الأولى من إعادة بيع المساكنفي النهاية ، تؤدي بنية SSR-CSR لنسخة الجوال إلى هذه الصورة:

"يومين" SSR-CSR مخطط. تعد Node JS API جاهزة دائمًا للإدخال / الإخراج غير المتزامن ولا يتم حظرها بواسطة وظيفة SSR ، نظرًا لأن الأخير في مثيل منفصل. سلسلة الاستعلام رقم 3 ليست مطلوبة.استبعاد الطلبات المكررة
بعد التلاعب ، توقف تقديم الصفحة الأولية عن إثارة الصرع. لكن الاستخدام الإضافي لـ Habr في وضع SPA لا يزال يسبب حيرة.
نظرًا لأن تدفق المستخدم يعتمد على انتقالات
قائمة نماذج المقالات → التعليقات → بالعكس ، فقد كان من المهم تحسين استهلاك موارد هذه السلسلة في المقام الأول.
تؤدي العودة إلى موجز النشر إلى طلب بيانات جديدلم يكن لدي لحفر عميق. يوضح screencast أعلاه أن التطبيق يعيد الاستعلام عن قائمة المقالات عند التمرير للخلف ، وخلال الطلب لا نرى المقال ، مما يعني أن البيانات السابقة تختفي في مكان ما. يبدو أن مكون قائمة المقالات يستخدم حالة محلية ويفقدها عند التدمير. في الواقع ، استخدم التطبيق الحالة العامة ، لكن بنية Vuex بنيت على الجبهة: الوحدات مرتبطة بالصفحات ، والتي ترتبط بدورها بالطرق. علاوة على ذلك ، فإن جميع الوحدات النمطية "لمرة واحدة" - تعيد كل زيارة تالية للصفحة إعادة كتابة الوحدة بالكامل:
ArticlesList: [ { Article1 }, ... ], PageArticle: { ArticleFull1 },
في المجموع ، كان لدينا الوحدة النمطية
ArticlesList ، التي تحتوي على كائنات من نوع
المادة ووحدة
PageArticle ، والتي كانت نسخة موسعة من كائن
المقالة ، نوع من
ArticleFull . بشكل عام ، لا يحمل هذا التنفيذ أي شيء فظيع في حد ذاته - إنه بسيط للغاية ، وقد يقول المرء بسذاجة ، لكنه واضح للغاية. إذا قمت بقص صفارات الوحدة مع كل تغيير في المسار ، فيمكنك التعايش معه. ومع ذلك ، فإن الانتقال بين موجزات المقالات ، على سبيل المثال
/ feed → / all ، مكفول للتخلص من كل ما يتعلق بالتغذية الشخصية ، نظرًا لأن لدينا قائمة
مقالات واحدة فقط نضع فيها بيانات جديدة. هذا يؤدي مرة أخرى إلى استعلامات مكررة.
بتجميع كل شيء تمكنت من اكتشافه حول هذا الموضوع ، قمت بصياغة هيكل جديد للدولة وعرضه على زملائي. كانت المناقشات طويلة ، ولكن في النهاية ، فاقت الحجج "من أجل" الشكوك وبدأت في التنفيذ.
من الأفضل الكشف عن منطق الحل على مرحلتين. أولاً ، نحاول إلغاء ربط وحدة Vuex من الصفحات والربط مباشرة بالطرق. نعم ، سيكون هناك المزيد من البيانات في المتجر ، وستصبح الرسائل أكثر تعقيدًا قليلاً ، لكننا لن نقوم بتحميل المقالات مرتين. بالنسبة لنسخة الجوال ، ربما تكون هذه هي الحجة الأقوى. سيبدو شيء مثل هذا:
ArticlesList: { ROUTE_FEED: [ { Article1 }, ... ], ROUTE_ALL: [ { Article2 }, ... ], }
ولكن ماذا لو كانت قوائم المقالات يمكن أن تتداخل بين مسارات متعددة ، وماذا لو أردنا إعادة استخدام بيانات كائن
مقال لتقديم صفحة نشر ، وتحويلها إلى
ArticleFull ؟ في هذه الحالة ، سيكون من المنطقي استخدام مثل هذا الهيكل:
ArticlesIds: { ROUTE_FEED: [ '1', ... ], ROUTE_ALL: [ '1', '2', ... ], }, ArticlesList: { '1': { Article1 }, '2': { Article2 }, ... }
ArticlesList هنا هو مجرد نوع من مستودع المادة. جميع المقالات التي تم تحميلها خلال جلسة المستخدم. نحن نعاملهم بعناية قدر الإمكان ، لأن هذه هي حركة المرور التي قد تم تحميلها من خلال الألم في مكان ما في المترو بين المحطات ، ونحن بالتأكيد لا نريد أن يتسبب هذا الألم للمستخدم مرة أخرى ، مما اضطره إلى تحميل البيانات التي قام بتنزيلها بالفعل. كائن
ArticlesIds هو مجرد مجموعة من المعرفات (مثل "الروابط") لكائنات
المقالة . تسمح لك هذه البنية بعدم تكرار البيانات الشائعة في التوجيهات وإعادة استخدام كائن
المقال عند تقديم صفحة نشر عن طريق دمج البيانات الموسعة فيها.
أصبح إخراج قائمة المقالة أكثر شفافية أيضًا: يتكرر مكون التكرار على الصفيف بمعرفات المقالة ويرسم مكون دعابة المقالة ، ويمرر معرف الدعائم ، ويسترد المكون الفرعي بدوره البيانات الضرورية من
ArticlesList . عندما تنتقل إلى صفحة المنشور ، نحصل على التاريخ الحالي من قائمة
المقالات ، ونقدم طلبًا للبيانات المفقودة ، ونضيفه ببساطة إلى الكائن الموجود.
لماذا هذا النهج أفضل؟ كما كتبت أعلاه ، فإن هذا النهج أكثر حذراً فيما يتعلق بالبيانات التي تم تنزيلها ويسمح لك بإعادة استخدامها. ولكن إلى جانب ذلك ، فإنه يفتح الطريق أمام بعض الفرص الجديدة التي تتناسب تمامًا مع مثل هذه الهندسة المعمارية. على سبيل المثال ، الاقتراع وتحميل المقالات إلى الخلاصة كما تظهر. يمكننا ببساطة إضافة منشورات جديدة إلى "متجر"
ArticlesList ، وحفظ قائمة منفصلة من المعرفات الجديدة في
ArticlesIds وإخطار المستخدم بهذا. عند النقر فوق الزر "إظهار المنشورات الجديدة" ، فإننا ببساطة نقوم بإدراج معرف جديد في بداية مجموعة قائمة المقالات الحالية وسيعمل كل شيء بطريقة سحرية تقريبًا.
جعل التنزيل أكثر متعة
كان الكرز على كعكة إعادة البناء هو مفهوم الهياكل العظمية ، مما يجعل عملية تنزيل المحتوى على الإنترنت البطيء أقل إثارة للاشمئزاز. لم تكن هناك مناقشات حول هذا الموضوع ، استغرقت الرحلة من الفكرة إلى النموذج الأولي ساعتين حرفيًا. تم رسم التصميم من قبل أنفسنا تقريبًا ، وقمنا بتدريس مكوناتنا كيفية تقديم كتل div متواضعة بالكاد أثناء انتظار البيانات. ذاتيا ، هذا النهج للتحميل يقلل حقا من كمية هرمونات التوتر في جسم المستخدم. الهيكل العظمي يشبه هذا:
HabraloadingRefleksiruem
لقد عملت في حبري منذ ستة أشهر ولا يزال الأصدقاء يسألون: حسنًا ، كيف تحب ذلك؟ جيد ومريح - نعم. ولكن هناك شيء يميز هذا العمل عن الآخرين. لقد عملت في فرق غير مبالية تمامًا بمنتجها ، ولم أكن أعرف ولم أفهم من هم مستخدموها. ولكن هنا كل شيء مختلف. هنا تشعر بالمسؤولية عما تفعله. في عملية تطوير ميزة ، فإنك تصبح مالكها جزئيًا ، وتشارك في جميع اجتماعات المنتج المتعلقة بوظائفك ، وتقدم اقتراحات وتتخذ القرارات بنفسك. يعد صنع المنتج الذي تستخدمه يوميًا رائعًا للغاية ، وكتابة التعليمات البرمجية للأشخاص الذين قد يكونون أفضل من ذلك هو مجرد شعور لا يصدق (بدون تهكم).
بعد إصدار كل هذه التغييرات ، تلقينا ردود فعل إيجابية ، وكانت لطيفة للغاية. انها ملهمة. شكرا لك اكتب أكثر.
اسمحوا لي أن أذكرك أنه بعد المتغيرات العالمية ، قررنا تغيير الهيكل وفصل طبقة الوكيل في نسخة منفصلة. وصلت بنية "يومين" بالفعل إلى الإصدار في شكل اختبار تجريبي عام. الآن يمكن لأي شخص أن يتحول إليها ويساعدنا في جعل Habr المحمول أفضل. هذا كل شيء لهذا اليوم. سأكون سعيدًا بالإجابة على جميع أسئلتك في التعليقات.