باستخدام وحدات جافا سكريبت في الإنتاج: الوضع الحالي. الجزء 1

منذ عامين ، كتبت عن تقنية تسمى الآن نموذج الوحدة النمطية / nomodule. يتيح لك التطبيق الخاص به كتابة تعليمات JavaScript البرمجية باستخدام إمكانيات ES2015 + ، ثم استخدام الحزم والمحولات لإنشاء نسختين من قاعدة الشفرة. يحتوي أحدها على بناء جملة حديث (يتم تحميله باستخدام بنية مثل <script type="module"> ، والثاني هو بناء جملة ES5 (يتم تحميله باستخدام <script nomodule> ). يسمح نمط الوحدة / nomodule بإرسالها إلى المستعرضات التي تدعم الوحدات النمطية ، رمز أقل بكثير من المتصفحات التي لا تدعم هذه الميزة ، والآن يتم دعم هذا النمط من قبل معظم أطر الويب وأدوات سطر الأوامر.



في السابق ، حتى لو أخذنا في الاعتبار القدرة على إرسال شفرة جافا سكريبت الحديثة إلى الإنتاج ، وعلى الرغم من أن معظم المتصفحات تدعم الوحدات ، فقد أوصيت بجمع الكود في حزم.

لماذا؟ غالبًا ما كان لدي شعور بأن تحميل الوحدات النمطية في المتصفح كان بطيئًا. على الرغم من أن البروتوكولات الحديثة ، مثل HTTP / 2 ، تدعم نظريا التحميل الفعال لملفات متعددة ، خلصت جميع دراسات الأداء في ذلك الوقت إلى أن استخدام المجمعات لا يزال أكثر فعالية من استخدام الوحدات النمطية.

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

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

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

المفاهيم الخاطئة عن الوحدات


كثير من الناس الذين تحدثت معهم يرفضون الوحدات تمامًا ، ولا يعتبرونها أحد خيارات تطبيقات الإنتاج على نطاق واسع. يستشهد الكثير منهم بالدراسة التي ذكرتها بالفعل. وهذا الجزء منه ، والذي ينص على أنه لا ينبغي استخدام الوحدات النمطية في الإنتاج ، ما لم تكن مسألة "تطبيقات ويب صغيرة تتضمن أقل من 100 وحدة تختلف في شجرة تبعية" صغيرة "نسبيًا (أي - مستوى لا يتجاوز عمقه 5 مستويات). "

إذا كنت قد بحثت في أي وقت مضى في دليل node_modules لأي من مشاريعك ، فمن المحتمل أن تعرف أنه حتى التطبيق الصغير يمكنه بسهولة الحصول على أكثر من 100 وحدة تبعية. أريد أن أقدم لكم نظرة على عدد الوحدات المتاحة في بعض حزم npm الأكثر شعبية.
صفقة
عدد الوحدات
التاريخ FNS
729
lodash-ES
643
rxjs
226

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

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

لكنك تعرف ماذا؟ الحقيقة هي أنه يمكنك استخدام كل هذا واستخدام وحدات في الإنتاج.

في الواقع ، الوحدات النمطية هي تنسيق يجب أن نسعى جاهدين لتحويل الشفرة إليه ، نظرًا لأن المتصفحات تعرف بالفعل كيفية تحميل الوحدات النمطية (والمتصفحات التي لا يمكنها القيام بذلك يمكنها تحميل نسخة احتياطية باستخدام آلية الترميز). إذا نظرت إلى الشفرة التي تنشئها الحزم الأكثر شيوعًا ، فستجد العديد من أجزاء القوالب التي يكون الغرض منها فقط تحميل التعليمات البرمجية الأخرى بشكل ديناميكي وإدارة التبعيات. ولكن كل هذا لن يكون ضروريًا إذا استخدمنا فقط الوحدات النمطية وأشكال التعبير import export .

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

تجدر الإشارة إلى أنهم يخططون لإضافة دعم للوحدات النمطية في الإصدار التالي من الطرود. لا تدعم Webpack الوحدات النمطية كتنسيق الإخراج ، ولكن هنا - المناقشات التي تركز على هذه المشكلة.

هناك اعتقاد خاطئ آخر يتعلق بالوحدات النمطية هو أن بعض الأشخاص يعتقدون أنه لا يمكن استخدام هذه الوحدات إلا في حالة استخدام 100٪ من تبعيات المشروع للوحدات النمطية. لسوء الحظ (أعتبرها أسفًا عظيمًا) ، لا تزال معظم حزم npm قيد الإعداد للنشر باستخدام تنسيق CommonJS (تتم ترجمة بعض الوحدات ، حتى تلك المكتوبة باستخدام ميزات ES2015 ، إلى تنسيق CommonJS قبل نشرها على npm)!

هنا ، مرة أخرى ، أود أن أشير إلى أن Rollup يحتوي على مكون إضافي ( rollup-plugin-commonjs ) يأخذ شفرة مصدر الإدخال مكتوبة باستخدام CommonJS ويحولها إلى رمز ES2015. بالتأكيد ، سيكون من الأفضل إذا كان تنسيق التبعية المستخدم من البداية يستخدم تنسيق الوحدة ES2015. ولكن إذا كانت بعض التبعيات ليست كذلك ، فإن هذا لا يمنعك من نشر المشاريع باستخدام الوحدات النمطية في الإنتاج.

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

كود الأمثل بناء استراتيجية


يعد إنشاء كود الإنتاج دائمًا محاولة لتحقيق التوازن بين إيجابيات وسلبيات الحلول المختلفة. من ناحية ، يرغب المطور في تحميل الشفرة وتنفيذها في أسرع وقت ممكن. من ناحية أخرى ، لا يريد تنزيل الكود الذي لن يستخدمه مستخدمو المشروع.

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

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

تستخدم معظم الحزم ، افتراضيًا ، تقنيات تقسيم الشفرات استنادًا إلى أوامر الاستيراد الديناميكية. لكنني أقول إن تقسيم الشفرة فقط مع التركيز على الاستيراد الديناميكي لا يسمح بتقسيمها إلى أجزاء صغيرة بدرجة كافية. ينطبق هذا بشكل خاص على المواقع التي بها العديد من المستخدمين العائدين (أي في الحالات التي يكون فيها التخزين المؤقت مهمًا).

أعتقد أنه يجب تقسيم الشفرة إلى أجزاء صغيرة قدر الإمكان. يجدر تقليل حجم الأجزاء حتى ينمو عددهم بدرجة كبيرة بحيث يؤثر على سرعة تنزيل المشروع. وعلى الرغم من أنني أوصي بالتأكيد بأن يقوم الجميع بتحليلهم الخاص للوضع ، إذا كنت تعتقد أن الحسابات التقريبية التي أجريت في الدراسة التي ذكرتها ، عند تحميل أقل من 100 وحدة ، لا يوجد تباطؤ ملحوظ في التحميل. لم تكشف دراسة منفصلة عن أداء HTTP / 2 عن تباطؤ ملحوظ في المشروع عند تنزيل أقل من 50 ملفًا. ومع ذلك ، فقد اختبروا فقط الخيارات التي بلغ فيها عدد الملفات 1 و 6 و 50 و 1000. ونتيجة لذلك ، من المحتمل أن يكون 100 ملف قيمة يمكنك التنقل إليها بسهولة دون الخوف من فقدان سرعة التنزيل.

إذن ، ما هي أفضل طريقة لتقسيم الكود إلى أجزاء؟ بالإضافة إلى تقسيم الشفرة بناءً على أوامر الاستيراد الديناميكية ، أنصحك بإلقاء نظرة فاحصة على تقسيم الشفرة في حزم npm. باستخدام هذا الأسلوب ، يقع ما يتم استيراده إلى المشروع من مجلد node_modules في جزء منفصل من الشفرة النهائية بناءً على اسم الحزمة.

فصل الحزمة


لقد ذكرت أعلاه أن بعض الإمكانات الحديثة للحزم تجعل من الممكن تنظيم خطة عالية الأداء لنشر المشاريع القائمة على الوحدات النمطية. ما كنت أتحدث عنه يمثله ميزتان جديدتان لـ Rollup. الأول هو الفصل التلقائي للرمز من خلال أوامر import() الديناميكية (المضافة في الإصدار 1.0.0 ). الخيار الثاني هو الفصل اليدوي للرمز الذي يؤديه البرنامج بناءً على خيار manualChunks (أضيف في الإصدار 1.1.0 ).

بفضل هاتين الميزتين ، أصبح من السهل للغاية الآن تكوين عملية الإنشاء ، حيث يتم تقسيم الشفرة على مستوى الحزمة.

فيما يلي مثال لتكوين يستخدم خيار manualChunks ، والذي node_modules تقع كل وحدة يتم استيرادها من node_modules في جزء منفصل من الشفرة يتطابق اسمه مع اسم الحزمة (من الناحية الفنية ، اسم دليل الحزمة في مجلد node_modules ):

 export default {  input: {    main: 'src/main.mjs',  },  output: {    dir: 'build',    format: 'esm',    entryFileNames: '[name].[hash].mjs',  },  manualChunks(id) {    if (id.includes('node_modules')) {      //   ,    `node_modules`.      //   - ,       .      const dirs = id.split(path.sep);      return dirs[dirs.lastIndexOf('node_modules') + 1];    }  }, } 

يقبل خيار manualChunk وظيفة تقبل ، كوسيطة واحدة ، المسار إلى ملف الوحدة النمطية. هذه الوظيفة يمكن أن ترجع اسم سلسلة. ما تشير إليه سوف يشير إلى جزء من التجميع الذي يجب أن تضاف إليه الوحدة الحالية. إذا لم تُرجع الوظيفة أي شيء ، فسيتم إضافة الوحدة إلى الجزء الافتراضي.

النظر في تطبيق الذي يستورد cloneDeep() ، debounce() find() lodash-es من حزمة lodash-es . إذا قمت بتطبيق التكوين أعلاه عند إنشاء هذا التطبيق ، فسيتم وضع كل وحدة من هذه الوحدات (بالإضافة إلى كل وحدة نمطية lodash التي استوردتها هذه الوحدات) في ملف إخراج واحد باسم npm.lodash-es.XXXX.mjs (هنا XXXX فريدة من نوعها تجزئة ملف الوحدة النمطية في lodash-es ).

في نهاية الملف ، سترى تعبير تصدير كما يلي. يرجى ملاحظة أن هذا التعبير لا يحتوي إلا على أوامر تصدير للوحدات النمطية التي تمت إضافتها إلى الجزء ، وليس كل الوحدات النمطية lodash .

 export {cloneDeep, debounce, find}; 

ثم ، إذا كان الكود الموجود في أي من الأجزاء الأخرى يستخدم وحدات lodash (ربما فقط طريقة debounce() ) ، في هذه الأجزاء ، في الجزء العلوي منها ، سيكون هناك تعبير استيراد يبدو كما يلي:

 import {debounce} from './npm.lodash.XXXX.mjs'; 

نأمل أن يوضح هذا المثال مسألة كيفية عمل الفصل اليدوي للرمز في مجموعة التحديثات. بالإضافة إلى ذلك ، أعتقد أن نتائج فصل الشفرة باستخدام تعبيرات import export أسهل بكثير في القراءة وفهم من شفرة الأجزاء ، والتي تم تشكيل آليات غير قياسية تستخدمها في حزمة معينة فقط.

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

 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["import1"],{ /***/ "tLzr": /*!*********************************!*\  !*** ./app/scripts/import-1.js ***!  \*********************************/ /*! exports provided: import1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "import1", function() { return import1; }); /* harmony import */ var _dep_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./dep-1 */ "6xPP"); const import1 = "imported: " + _dep_1__WEBPACK_IMPORTED_MODULE_0__["dep1"]; /***/ }) }]); 

ماذا لو كان هناك المئات من التبعيات npm؟


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

بالطبع ، إذا كان التطبيق الخاص بك يستورد وحدات من مئات حزم npm المختلفة ، فلا يزال بإمكانك أن تكون في وضع لا يستطيع فيه المتصفح تحميلها كلها بفعالية.

ومع ذلك ، إذا كان لديك بالفعل العديد من تبعيات npm ، فيجب ألا تتخلى عن هذه الاستراتيجية تمامًا في الوقت الحالي. تذكر أنك ربما لن تقوم بتنزيل جميع تبعيات npm في كل صفحة. لذلك ، من المهم معرفة عدد التبعيات التي يتم تحميلها بالفعل.

ومع ذلك ، أنا متأكد من أن هناك بعض التطبيقات الحقيقية التي تحتوي على العديد من تبعيات npm بحيث لا يمكن تمثيل هذه التبعيات كأجزاء منفصلة. إذا كان المشروع الخاص بك هو ذلك تمامًا - أوصي بأن تبحث عن طريقة لتجميع الحزم حيث يمكن تغيير التعليمات البرمجية التي يمكن أن تتغير فيها الاحتمالات العالية في نفس الوقت (مثل react react-dom ) حيث سيتم أيضًا react-dom التجزئة المؤقت لهذه الحزم في نفس الوقت في وقت لاحق ، سأعرض مثالًا يتم فيه تجميع كل تبعيات React في الجزء نفسه.

أن تستمر ...

أعزائي القراء! كيف تتعامل مع مشكلة فصل الكود في مشاريعك؟

Source: https://habr.com/ru/post/ar466537/


All Articles