خارطة طريق 2021

لقد مر ما يقرب من شهرين منذ أن تم إصدار webpack 5 رسميًا. نظرًا لحالة الرعاية، لم نتمكن من تكريس الكثير من الوقت لـ webpack كما كنا نود. متحدثًا عن نفسي فقط (@sokra)، لقد استمتعت بالاستراحة القصيرة وعملت على بعض المشاريع الجانبية. ومن المفارقات، أثناء استخدامي لـ webpack 5 وجميع ميزاته المتطورة (asset modules، worker support، persistent caching)، اكتشفت بضعة أخطاء إضافية في webpack 5 من المحتمل أن يواجهها الأشخاص عند ترقية مشاريعهم إلى webpack 5، مما أدى إلى تخصيص الكثير من العمل لإصلاح الأخطاء. إليك ملخص صغير:

ماذا حدث حتى الآن؟

تم الكشف عن بعض الأشياء الإضافية من webpack، سواء من حيث الأنواع (typings) أو من حيث وقت التشغيل (runtime). تم إجراء عدد قليل من تحسينات الأداء سهلة التنفيذ. كان الكود الخالي من الفواصل المنقوطة (semicolons) يولد كودًا غير صالح/غير صحيح في بعض الحالات، وقد تم تصحيح ذلك. أدى الجمع بين الكود الخالي من الآثار الجانبية + الوحدات المتسلسلة (concatenated modules) + إعادة التصدير (reexports) إلى بعض الحالات الطرفية (edge cases)، والتي تم إصلاحها (على الأقل المعروفة منها).

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

لإطلاق الخطأ، نحتاج إلى ثلاثة مكونات:

  • منذ إصدار webpack 5، سيقوم التحسين (optimization) في وضع production (الإنتاج) بتشغيل تحليل الصادرات المستخدمة (Tree Shaking) ضد كل بيئة تشغيل (runtime) (والتي غالبًا ما تكون مطابقة لنقاط الدخول entrypoints)، مما يعني أن webpack يمكنه تحسين كل وقت تشغيل (أو نقطة دخول) على حدة.
  • يسمح تكوين optimization.splitChunks المخصص بدمج الوحدات بالقوة في حزمة (chunk) واحدة. يتم ذلك عن طريق تمرير الخيار name. على سبيل المثال { test: /node_modules/, name: "vendors" } يدمج الوحدات من node_modules في حزمة واحدة. على الرغم من أن هذا غير موصى به بشكل عام، إلا أنه ممكن وربما يكون منطقيًا في بعض الحالات. الأمر برمته يتعلق بالمقايضات (trade-offs) على أي حال، واختيار دمج جميع الموردين (vendors) في حزمة واحدة يمكن أن يكون مفيدًا للتخزين المؤقت طويل المدى لهذه الحزمة للزيارات المتكررة أو بين نقاط الدخول المتعددة.
  • عندما لا يتم استخدام صادرات الوحدة الخالية من الآثار الجانبية، يتم حذف الوحدة بأكملها من رسم بياني الوحدة (module graph) ولا تُولد عبارة import أي كود وقت تشغيل (runtime code) على الإطلاق.

تحدث مشكلة في حالة طرفية (edge case) حيث يتم دمج الوحدات من نقطتي دخول في حزمة واحدة وتشير إلى وحدة خالية من الآثار الجانبية ليست في الحزمة المشتركة لأن إحدى نقطتي الدخول فقط هي التي تستخدم صادرات من الوحدة الخالية من الآثار الجانبية. الوحدات الموجودة في الحزمة المشتركة تُستخدم من قبل كلتا نقطتي الدخول، وبالتالي فإنها تحتاج إلى تضمين الصادرات التي تستخدمها أي من نقطتي الدخول. هذا يعني أنها ستولد كودًا يشير إلى الوحدة الخالية من الآثار الجانبية والتي لا تتوفر في وقت التشغيل لنقطة الدخول الأخرى في الحالة الطرفية المذكورة أعلاه، مما يؤدي إلى حدوث خطأ undefined is not a function أو cannot read property 'call' of undefined في وقت التشغيل.

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

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

هذا كل شيء بخصوص هذه الاستطرادة، أتمنى ألا تكون مملة جدًا...

خارطة طريق 2021

بافتراض أنه يمكننا تسوية وضع الرعاية الخاص بنا، فإنه من المخطط القيام بما يلي في عام 2021:

المزيد من الاستقرار

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

وحدات EcmaScript (EcmaScript Modules)

تكتسب وحدات EcmaScript (ESM) تدريجيًا تبنيًا واسع النطاق. من جانب الكتابة، أصبحت بالفعل المعيار الفعلي لكتابة التعليمات البرمجية. من حيث دعم المتصفحات، يبدو الوضع جيدًا أيضًا (باستثناء IE11 وعدد قليل من متصفحات الهواتف المحمولة القديمة). لا تزال المتصفحات تفتقر إلى حد ما في دعم ESM لـ WebWorkers.

يمكن للمرء أيضًا إنشاء حزم تعمل في علامة نصية <script type="module">، ولكن هذا له فوائد قليلة في الوقت الحالي.

هناك مجالات متعددة في webpack حيث يمكن تحسين دعم ESM:

ESM كآلية لتحميل الحزم (chunk loading mechanism)

عند استهداف الويب، يقوم webpack بتحميل الحزم (chunks) عبر علامات script. عند استهداف node.js، يقوم webpack بتحميل الحزم عبر require أو fs + vm. عند استهداف WebWorkers، يقوم webpack بتحميل الحزمة عبر importScripts.

في مستقبل ليس ببعيد، ستدعم جميع هذه البيئات ESM والأهم من ذلك دالة import() الديناميكية. وبالتالي يمكن لآلية تحميل الحزم المعتمدة على import() توحيد جميع هذه البيئات، بينما تتطلب كود وقت تشغيل (runtime code) أقل.

الحزم ذاتية التنفيذ (Self-executed chunks)

حاليًا، دائمًا ما تكون الحزم التي يتم تحميلها عند الطلب في webpack عبارة عن حاويات للوحدات ولا تنفذ أبدًا كود الوحدة مباشرة. عند كتابة import("./module") في الوحدات، سيتم تحويل ذلك إلى شيء مثل __webpack_load_chunk__("chunk-containing-module.js").then(() => __webpack_require__("./module")). هناك العديد من الحالات التي لا يمكن فيها تغيير هذا (مثل عند تحميل حزم متعددة أو تحميل CSS أيضًا)، ولكن هناك بعض الحالات التي يمكن فيها لـ webpack إنشاء حزمة تنفذ الوحدة المضمنة مباشرة. قد يؤدي هذا إلى تقليل التعليمات البرمجية المولدة وتجنب تغليف الدالة في الحزمة.

حاليًا لست متأكدًا بعد مما إذا كان هذا يستحق العناء، ولكن الأمر يستحق على الأقل النظر فيه.

صادرات ESM

حاليًا لا يمكن إنشاء صادرات ESM لحزمة عبر output.library.type: "module". يمكن أن يكون هذا مفيدًا عند دمج حزم webpack في بيئات تحميل ESM أو نصوص مضمنة (inline scripts).

استيرادات خارجية ESM (ESM externals)

يسمح Webpack بتعريف externals (العناصر الخارجية) وهي عبارة عن وحدات لم يتم تحزيمها ولكنها موجودة في وقت التشغيل. هناك العديد من أنواع العناصر الخارجية بدءًا من globals مرورًا بـ CommonJs/AMD/System ووصولًا إلى التحميل من علامة script التقليدية. حتى import() (type: "import") يمكن استخدامها لتحميل عنصر خارجي، لكن import (type: "module") لا يمكن استخدامه بعد.

من المثير للاهتمام، على الرغم من أن type: "module" غير مدعوم حتى الآن، إلا أن webpack يستخدمه بالفعل كإعداد افتراضي عند كتابة مثلاً import x from "https://example.com/module.js". كان الافتراضي هو اختيار إضافة دعم بسلاسة للعناصر الخارجية ESM دون إدخال تغيير جذري (breaking change).

قد تكون عناوين URL المطلقة في الـ imports منطقية، على سبيل المثال، عند استخدام خدمات خارجية تقدم واجهة برمجة التطبيقات (API) الخاصة بها كـ ESM: import { event } from "https://analytics.company.com/api/v1.js" (قد يكون استخدام import("https://analytics.company.com/api/v1.js") أكثر منطقية للتعامل بشكل رشيق مع الأخطاء عند الاعتماد على هذه الخدمة الخارجية، ولكن يمكن أيضًا التقاط الأخطاء في مستوى أعلى من الرسم البياني للوحدات).

كالعادة، يسمح تكوين externals بربط أي اسم وحدة بعناصر خارجية:

export default {
  externalsType: "module",
  externals: {
    analytics: "https://analytics.company.com/api/v1.js",
    svelte: "https://jspm.dev/svelte@3",
    react: "https://cdn.skypack.dev/preact@10",
    "react-dom": "https://esm.sh/[react,react-dom]/react-dom",
  },
};

مكتبة ESM (ESM library)

عند دعم صادرات واستيرادات ESM، قد يعتقد الناس أن تحزيم مكتبة أمر منطقي، ومن المحتمل أن يكون ذلك صحيحًا في بعض الحالات، ولكن في كثير من الحالات سيؤدي التحزيم الأصلي إلى نتائج أسوأ. أكبر مشكلة هي علامة "sideEffects": false. فهي تؤثر على الوحدات على أساس كل ملف لتخطي وحدات بأكملها. عند تسلسل وحدات متعددة خالية من الآثار الجانبية، لم يعد من الممكن تخطي الوحدات الفردية، مما يؤدي إلى تحسين أسوأ عندما لا يتم استخدام جميع صادرات المكتبة.

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

يمكنني التفكير في وضع خاص، والذي لا يطبق التحزيم (chunking) وبدلاً من ذلك، يُصدر الوحدات الخام (المُعالجة) المتصلة عبر استيرادات وصادرات ESM (أو أيضًا require في CommonJS). لذلك يعني هذا أن المحملات (loaders)، والرسم البياني للوحدات، وتحسينات الأصول قيد التشغيل، ولكن لا يتم إنشاء رسم بياني للحزم (chunk graph) ويتم إصدار كل وحدة في الرسم البياني للوحدات كملف منفصل.

تحذيرات الوضع الصارم (Strict mode warnings)

عند إنشاء حزمة ESM، سيُجبر كل الكود المضمن على استخدام الوضع الصارم (strict mode). بالنسبة للعديد من الوحدات، لا يمثل هذا مشكلة، ولكن هناك بضع حزم أقدم قد تواجه صعوبة مع دلالات مختلفة (different semantic). نريد إظهار تحذيرات لهذه الحالات.

المزيد من المواطنين من الدرجة الأولى (first-class citizen)

قام webpack 4 و 5 بالكثير من العمل لدعم أنواع الوحدات غير JS، ويدعم webpack 5 بالفعل بعض أنواع الوحدات افتراضيًا: JS (ESM/CJS/AMD)، و JSON، و WebAssembly، والأصول (Asset). منذ إصدار webpack 5، كان أحد أهدافنا طويلة المدى هو أن نصبح مُحسِّنًا لتطبيقات الويب (web-app optimizer)، مع هدف دعم كل ما يدعمه المتصفح. لذلك من الناحية الفنية، يجب أن يعمل تطبيق الويب العادي مباشرة من الصندوق مع webpack، مع تحسينه أثناء التنقل.

لقد اتخذ الإصدار الأولي من webpack 5 بالفعل بعض الخطوات الكبيرة في هذا الاتجاه: يتم دعم new Worker محليًا. يتم دعم new URL(...) محليًا (assets).

يتم دعم WebAssembly و JSON بالفعل، على الرغم من أن المقترحات لم تنته بعد.

لكن لا يزال هناك نوعان من الموارد مفقودان للقصة الكاملة: HTML و CSS.

CSS كوحدات (CSS as modules)

يدعم webpack حاليًا CSS عبر css-loader، أو style-loader أو mini-css-extract-plugin. هذا يعمل بشكل جيد جدًا، لكنني أعتقد أنه يمكننا بذل المزيد من خلال دعم CSS كنوع وحدة أصلي (native module type) في webpack.

ستكون الفائدة الكبرى في تجربة المطور (developer experience): تكوين mini-css-extract-plugin ليس الأسهل والتخلص منه من شأنه تبسيط الكثير للمطور. لا يعني ذلك أنه لا يمكنك إضافة تخصيص إضافي فوق ذلك. أرى العديد من المطورين لا يستخدمون CSS الخام، بل يستخدمون معالجات مسبقة (preprocessors) فوق ذلك (مع دعم CSS الأصلي، سيبدو هذا هكذا: { test: /\.sass$/, type: "stylesheet", use: "sass-loader" }).

وفقًا لتقرير State of CSS 2020، تعد وحدات CSS طريقة شائعة لكتابة CSS معياري، وكونها نوع وحدة أصلي في webpack يسمح بالاستفادة من تحسين رسم بياني الوحدة مثل إزالة الأكواد غير المستخدمة (Used Exports Optimization وتحسين Side-Effects). عند استخدام وحدات CSS، هذا يعني أن CSS الناتج سيحتوي فقط على قواعد CSS التي يُشار إليها من التطبيق (كما هو معتاد من Tree Shaking في JS).

هناك بعض التحسينات المحتملة الخاصة بوحدات CSS الممكنة من خلال المعرفة العالمية للتطبيق الذي يمتلكه webpack: يمكن تقسيم قواعد CSS إلى قواعد أصغر لتجنب تكرار الخصائص الشائعة. هذا يمكن أن يؤدي إلى حجم تحميل أصغر بكثير لأن مخرجات CSS تحتوي على خصائص متكررة أقل (Atomic CSS).

ولكن هناك "لكن" كبيرة هنا: هناك عمل نحو اقتراح "وحدات CSS" (CSS Modules) مختلف في مجتمع WebComponents، والذي من المخطط أن يصبح مدعومًا بشكل أصلي من قبل المتصفحات. على الأقل هذا هو هدف الاقتراح. للأسف، هذا الاقتراح مختلف عما يُستخدم حاليًا في نظام الواجهة الأمامية البيئي (Frontend ecosystem) ولكنه يستخدم بناء جملة مشابه. عادة، يصطف webpack مع الاقتراحات (proposals)، لذلك هذا شيء يجب مراعاته هنا. علينا التحقق مما إذا كان من الممكن تجنب النزاعات المحتملة.

HTML كنقطة دخول (HTML as entrypoint)

باتباع مثال Parcel، نريد أيضًا دعم HTML أصليًا كنقاط دخول (entrypoints). سيكون دعم ذلك يتماشى مع الهدف كمُحسِّن تطبيقات الويب (web app optimizer)، حيث تبدأ تطبيقات الويب عادةً بمستند HTML. إنه أيضًا تحسين ضخم لتجربة المطورين للمبتدئين حيث يمكن استنتاج العديد من الأشياء من HTML.

يسمح لك التحكم في HTML المولد بالتحسين بشكل أكثر قوة افتراضيًا. حاليًا، نمنع إعادة التسمية أو تقسيم الحزم الأولية (initial chunks) افتراضيًا، لأن هذا يتطلب بنية تحتية إضافية لتوليد HTML.

تستفيد نقاط دخول HTML أيضًا من CSS كوحدات ووحدات الأصول (Asset modules)، حيث يمكن الإشارة إلى هذه الموارد من HTML أيضًا (مثل <link rel=stylesheet />، <img src="..." />، <link rel=icon />).

وحدات HTML (HTML modules)

هناك أيضًا اقتراح بشأن الدعم الأصلي لاستيراد HTML في المتصفحات، وهذا شيء سنتابعه، خاصة نظرًا لوجود تداخل كبير مع نقاط دخول HTML.

أداء خرائط المصدر (SourceMap performance)

يعد استخدام خرائط المصدر (الكاملة) مع webpack حاليًا مكلفًا للغاية مع webpack، لأن الأداء لمعالجة خرائط المصدر ليس الأفضل. هذا شيء نريد التحقيق فيه بالنسبة لـ webpack، ولكن أيضًا بالنسبة لـ terser، الذي يستخدمه webpack كمُصغِّر (minimizer) افتراضيًا.

حقل exports/imports في package.json

أضاف Node.js 14 دعمًا لحقل exports في package.json للسماح بتحديد نقاط الدخول للحزمة. حذا webpack 5 حذوه، وحتى أضاف شروطًا إضافية مثل production/development.

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

هذا شيء نريد إضافته أيضًا.

تحسين تحليل CommonJS

في حين أن ESM هو المستقبل، لا يزال هناك الكثير من حزم CommonJS في npm وقيد الاستخدام. أضاف Webpack 5 التحليل لوحدات CommonJS للسماح بـ Tree Shaking لمعظم هذه الوحدات.

لكن يمكننا أن نفعل المزيد. في حين يتم دعم العديد من أنماط التصدير (exporting patterns)، يتم دعم عدد قليل فقط من أنماط الاستيراد. نريد إضافة دعم لمزيد من الأنماط للسماح بمزيد من التحسينات لوحدات CommonJS أيضًا.

استبدال الوحدة الساخنة (Hot Module Replacement) لـ Module Federation

أضاف Webpack 5 ميزة جديدة تسمى "Module Federation" والتي تسمح بدمج عمليات بناء متعددة معًا في وقت التشغيل. حاليًا، يدعم Hot Module Replacement (HMR) بنية واحدة فقط في كل مرة ولا يمكن أن تنتقل التحديثات بين البنيات (builds). نريد التحسين هنا والسماح لتحديثات HMR بالانتقال بين عمليات البناء المختلفة، مما سيحسن تطوير تطبيقات الفيدرالية (federation applications).

نظام التلميحات (Hinting system)

حاليًا، يعرض webpack التحذيرات والأخطاء للمستخدم. أثناء البناء، هناك عدد قليل من الحالات التي يمكننا فيها إخبار المستخدم بشيء ما، مثل مشاكل محتملة (footguns) أو فرص تحسين، لكنها لا تتناسب مع التحذيرات أو الأخطاء ولا نريد الإغراق في المخرجات بكل هذه المعلومات. لذلك نريد إضافة فئة أخرى: التلميحات (Hints). نريد جمع كل التلميحات أثناء عمليات البناء (يمكن أن تصدر الإضافات (plugins) بعضًا منها أيضًا)، ولكننا لا نعرض سوى عدد محدود منها في المخرجات (افتراضيًا واحد فقط). يجب أن يؤدي هذا إلى نوع من تجربة "هل تعلم" (Did you know) للمستخدم.

تعدد مؤشرات الترابط (Multi-Threading)

بينما يجعل التخزين المؤقت المستمر (Persistent Caching) البناء المخزن مؤقتًا "سريعًا للغاية"، لا يزال هناك مجال للتحسين لعمليات البناء الأولية بدون Persistent Cache. تنفيذ Javascript في Node.js أحادي مؤشر الترابط (single-threaded) افتراضيًا، لكن الإضافات الحديثة تسمح باستخدام worker_threads، وهي واجهة برمجة تطبيقات (API) تشبه WebWorkers.

يمكن استخدام هذا لتوزيع العمل عبر جميع وحدات المعالجة المركزية (CPUs). كانت هناك بالفعل بعض الاستعدادات في webpack 5 لذلك: على سبيل المثال يمكن تسلسل هياكل البيانات الداخلية (serializing of internal data structures) وتدعم قوائم انتظار العمل (work queues) الإضافات (plugins). لكن بعض أجزاء ذلك لا تزال غير واضحة وتتطلب التجريب.

كان هذا في قائمة التصويت لدينا لفترة من الوقت، لكن لم يصوت الكثيرون على ذلك. هل هذا حقًا شيء يحتاجه الناس؟

WebAssembly

حاليًا، لا يزال WebAssembly في المرحلة التجريبية (experimental) وليس مُفعّلًا افتراضيًا. بمجرد وصول الاقتراح إلى المرحلة 4 (Stage 4)، يمكننا تمكينه افتراضيًا.

قد يؤدي هذا أيضًا إلى تبني أوسع لـ WebAssembly في النظام البيئي (ecosystem). أعتقد أننا قد نرى المزيد في هذا المجال في 2021.

تنويه (Disclaimer)

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

Edit this page·

2 Contributors

ameencfwRlxChap2