إعادة تحميل بنية أدوات مطوّري البرامج: النقل إلى وحدات JavaScript

Tim van der Lippe
Tim van der Lippe

كما تعلم، أدوات مطوري البرامج في Chrome هو تطبيق ويب تمت كتابته باستخدام HTML وCSS وJavaScript. على مرّ السنين، أصبحت أدوات مطوّري البرامج أكثر ثراءً بالميزات وذكاءً ومعرفةً بمنصة الويب الأوسع نطاقًا. على الرغم من توسُّع أدوات مطوري البرامج على مدار السنوات، فإن هيكلتها تشبه إلى حد كبير البنية الأصلية عندما كانت لا تزال جزءًا من WebKit.

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

في البداية، لم يكن هناك شيء

على الرغم من أنّ الواجهة الأمامية الحالية تضم مجموعة متنوّعة من أنظمة الوحدات مع أدوات مُصمّمة حولها، بالإضافة إلى تنسيق وحدات JavaScript الموحّد حاليًا، لم يظهر أي من هذه الأنظمة عند إنشاء "أدوات مطوري البرامج" لأول مرة. تم تصميم أدوات مطوّري البرامج استنادًا إلى الرموز البرمجية التي تم شحنها في البداية في WebKit قبل أكثر من 12 عامًا.

يعود الإشارة الأولى إلى نظام الوحدات في "أدوات مطوري البرامج" إلى عام 2012: تقديم قائمة بالوحدات مع قائمة بالمصادر المرتبطة بها. وكان هذا جزءًا من البنية الأساسية للغة بايثون التي كانت تُستخدم في ذلك الوقت لتجميع أدوات مطوّري البرامج وإنشائها. في عام 2013، تم استخراج جميع الوحدات في ملف frontend_modules.json منفصل (الإتمام) في عام 2013، ثم تم نقلها إلى ملفات module.json منفصلة (الإتمام) في عام 2014.

مثال على ملف module.json:

{
  "dependencies": [
    "common"
  ],
  "scripts": [
    "StylePane.js",
    "ElementsPanel.js"
  ]
}

منذ عام 2014، تم استخدام نمط module.json في "أدوات مطوري البرامج" لتحديد وحداته وملفات المصدر. وفي الوقت نفسه، تطورت منظومة الويب المتكاملة بسرعة وتم إنشاء تنسيقات متعددة للوحدات، بما في ذلك UMD وCommonJS ووحدات JavaScript الموحّدة في نهاية المطاف. مع ذلك، لم تتوقّف "أدوات مطوري البرامج" عن التنسيق module.json.

وعلى الرغم من أنّ أدوات مطوّري البرامج ظلت تعمل، كان هناك بعض الجوانب السلبية لاستخدام نظام وحدات غير موحّد وفريد:

  1. كان التنسيق module.json يتطلب استخدام أدوات تصميم مخصّصة، مشابهة للحِزم الحديثة.
  2. لم يكن هناك دمج IDE، الأمر الذي تطلّب أدوات مخصّصة لإنشاء ملفات يمكن أن تفهمها برامج IDE الحديثة (النص البرمجي الأصلي لإنشاء ملفات jsconfig.json لترميز VS Code).
  3. تم وضع جميع الدوال والفئات والكائنات على النطاق العمومي لتسهيل المشاركة بين الوحدات.
  4. كانت الملفات تعتمد على الترتيب، ما يعني أنّ الترتيب الذي تم إدراج sources به كان مهمًا. لم يكن هناك ما يضمن تحميل الرمز البرمجي الذي تعتمد عليه، إلّا أنّ شخصًا آخر قد تحقّق منه.

بشكل عام، عند تقييم الحالة الحالية لنظام الوحدات في أدوات مطوّري البرامج وتنسيقات الوحدات الأخرى (الأكثر استخدامًا)، استنتجنا أنّ نمط module.json كان يتسبب في عدد من المشاكل أكبر من العدد الذي تم حله، وحان الوقت للتخطيط لعملية الابتعاد عنه.

فوائد المعايير

من بين أنظمة الوحدات الحالية، اخترنا وحدات JavaScript لتكون الوحدة التي سيتم النقل إليها. في الوقت الذي تم فيه اتخاذ ذلك القرار، كانت وحدات JavaScript لا تزال تظهر ضمن علامة في Node.js، وكان هناك عدد كبير من الحزم المتوفرة على NPM لم يكن يتوفر بها حزمة وحدات JavaScript يمكننا استخدامها. وعلى الرغم من ذلك، توصلنا إلى أن وحدات JavaScript هي الخيار الأفضل.

الفائدة الأساسية لوحدات JavaScript هي أنّها تنسيق الوحدة الموحّد للغة JavaScript. عندما أدرجنا الجوانب السلبية لـ module.json (انظر أعلاه)، أدركنا أنّ معظمها تقريبًا كانت مرتبطة باستخدام تنسيق فريد وغير موحّد.

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

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

نظرًا لأن وحدات JavaScript كانت المعيار، فهذا يعني أن برامج IDE مثل VS Code، ومدققات النوع مثل Closure Compiler/TypeScript وأدوات تصميم مثل Rollup/minifiers سيكونوا قادرين على فهم الكود المصدر الذي كتبناه. بالإضافة إلى ذلك، عندما ينضم مشرف جديد إلى فريق "أدوات مطوري البرامج"، لن يحتاج إلى قضاء الوقت في تعلُّم تنسيق module.json خاص، في حين أنّه سيكون على الأرجح على دراية بوحدات JavaScript.

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

تكلفة التصميم الجديد

على الرغم من أنّ وحدات JavaScript توفّر الكثير من المزايا التي نرغب في استخدامها، إلا أنّنا بقينا في عالم module.json غير العادي. وبالاستفادة من مزايا وحدات JavaScript، كان علينا الاستثمار بشكل كبير في إزالة الديون التقنية، وإجراء عملية نقل من المحتمل أن تؤدي إلى تعطُّل الميزات وظهور أخطاء انحدارية.

في هذه المرحلة، لم يكن السؤال هو "هل نريد استخدام وحدات JavaScript؟" بل كان السؤال "ما تكلفة استخدام وحدات JavaScript؟". هنا، كان علينا الموازنة بين مخاطر كسر المستخدمين والانحدارات، وتكلفة المهندسين الذين يقضون (قدرًا كبيرًا) من الوقت في نقل البيانات والوضع المؤقت الأسوأ الذي كنا نعمل فيه.

وقد اتضح أن هذه النقطة الأخيرة مهمة للغاية. على الرغم من أنّه يمكننا من الناحية النظرية الوصول إلى وحدات JavaScript، إلّا أنّه أثناء عملية النقل، قد نحصل في نهاية المطاف على رمز يجب أن يأخذ في الاعتبار كل من وحدتَي module.json وJavaScript. ولم يكن تحقيق ذلك من الناحية الفنية فحسب، بل كان يعني أيضًا أنّ جميع المهندسين الذين يعملون على "أدوات مطوري البرامج" سيحتاجون إلى معرفة كيفية العمل في هذه البيئة. سيكون عليهم أن يسألوا أنفسهم بشكل مستمر "بخصوص هذا الجزء من قاعدة الرموز، هل هي وحدات module.json أم وحدات JavaScript وكيف يمكنني إجراء التغييرات؟".

نظرة خاطفة: كانت التكلفة الخفية لتوجيه مقدّمي الرعاية لدينا خلال عملية نقل البيانات أكبر مما توقّعناه.

بعد تحليل التكلفة، توصّلنا إلى أنّ النقل إلى وحدات JavaScript كان لا يزال من المفيد. وبالتالي، كانت أهدافنا الرئيسية هي التالية:

  1. تأكَّد من أنّ استخدام وحدات JavaScript يستفيد من الفوائد إلى أقصى حدّ ممكن.
  2. تأكَّد من أنّ عملية الدمج مع النظام الحالي المستند إلى module.json آمنة ولا تؤدي إلى أي تأثير سلبي على المستخدم (أخطاء في الانحدار أو استياء المستخدمين).
  3. وجِّه جميع مشرفي أدوات مطوّري البرامج خلال عملية نقل البيانات، باستخدام عمليات التحقّق والتوازنات المضمّنة في المقام الأول لمنع الأخطاء غير المقصودة.

جداول البيانات والتحولات والديون التقنية

وعلى الرغم من أنّ الهدف كان واضحًا، فإنّ القيود التي يفرضها تنسيق module.json كان من الصعب تطبيقها. لقد استغرق الأمر العديد من التكرارات والنماذج الأولية والتغييرات المعمارية قبل أن نتمكن من تطوير حل كنا مرتاحين له. لقد كتبنا مستند تصميم يتضمّن استراتيجية نقل البيانات التي انتهى بنا المطاف بها. ذكر مستند التصميم أيضًا تقديرنا الأولي للوقت: 2-4 أسابيع.

تنبيه: لقد استغرقت عملية النقل أكثر كثافةً 4 أشهر، واستغرقت من البداية إلى النهاية 7 أشهر.

مع ذلك، اختبرت الخطة الأولية وقت اختبار الوقت: كان يتم تدريب وقت تشغيل "أدوات مطوري البرامج" على تحميل جميع الملفات المدرَجة في مصفوفة scripts في الملف module.json بالطريقة القديمة، في حين أنّ جميع الملفات المدرَجة في المصفوفة modules باستخدام الاستيراد الديناميكي لوحدات JavaScript. وسيتمكّن أي ملف موجود في مصفوفة modules من استخدام عمليات الاستيراد/التصدير ES.

بالإضافة إلى ذلك، سنُجري عملية النقل على مرحلتَين (لقد قسمنا في النهاية المرحلة الأخيرة إلى مرحلتَين فرعيتَين، كما هو موضّح أدناه): المرحلة export وimport. حالة الوحدة التي سيتم فيها تتبع المرحلة في جدول بيانات كبير:

جدول بيانات نقل وحدات JavaScript

يتوفّر مقتطف من ورقة بيانات مستوى التقدّم للجميع هنا.

مرحلة export

تتمثل المرحلة الأولى في إضافة عبارات export لجميع الرموز التي كان من المفترض مشاركتها بين الوحدات/الملفات. ستكون عملية التحويل تلقائية من خلال تشغيل نص برمجي لكل مجلد. ومع ذلك، سيظهر الرمز التالي في عالم module.json:

Module.File1.exported = function() {
  console.log('exported');
  Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
  console.log('Local');
};

(هنا، Module هو اسم الوحدة وFile1 اسم الملف. في شجرة المصدر التي نستخدمها، ستكون front_end/module/file1.js).

سيتم تحويله إلى ما يلي:

export function exported() {
  console.log('exported');
  Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
  console.log('Local');
}

/** Legacy export object */
Module.File1 = {
  exported,
  localFunctionInFile,
};

في البداية، كانت خطتنا هي إعادة كتابة عمليات استيراد الملف نفسه خلال هذه المرحلة أيضًا. في المثال أعلاه، سنعيد كتابة Module.File1.localFunctionInFile إلى localFunctionInFile. ومع ذلك، أدركنا أنّه من الأسهل أتمتة التطبيق وأكثر أمانًا إذا فصلنا عمليتَي التحويل. وبالتالي، ستصبح "نقل كل الرموز في الملف نفسه" المرحلة الفرعية الثانية من المرحلة import.

بما أنّ إضافة الكلمة الرئيسية export في أحد الملفات تؤدي إلى تحويل الملف من "نص برمجي" إلى "وحدة"، كان لا بد من تعديل الكثير من البنية الأساسية في "أدوات مطوري البرامج" وفقًا لذلك. وقد شمل ذلك وقت التشغيل (مع الاستيراد الديناميكي)، وكذلك أدوات مثل ESLint للتشغيل في وضع الوحدة.

من بين ما اكتشفناه أثناء العمل على حل هذه المشكلات هو أن اختباراتنا كانت تجري في وضع "غير متقن". بما أنّ وحدات JavaScript تشير ضمنًا إلى أنّ الملفات يتم تشغيلها في وضع "use strict"، سيؤثر ذلك أيضًا في اختباراتنا. تبيّن لنا أنّ عددًا بسيطًا من الاختبارات كان يعتمد على هذا الدقة، بما في ذلك إجراء اختبار استخدم عبارة with 🔍.

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

مرحلة import

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

على سبيل المثال، بالنسبة إلى الرموز التالية التي تتوفّر في عالم module.json:

Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();

سيتم تحويلها إلى:

import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';

import {moduleScoped} from './AnotherFile.js';

Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();

ومع ذلك، كانت هناك بعض التنبيهات في هذا النهج:

  1. لم تتم تسمية كل رمز باسم Module.File.symbolName. تمت تسمية بعض الرموز باسم Module.File فقط أو حتى Module.CompletelyDifferentName. يعني هذا التناقض أنه كان علينا إنشاء تعيين داخلي من الكائن العمومي القديم إلى الكائن المستورَد الجديد.
  2. ففي بعض الأحيان، قد يكون هناك تعارض بين أسماء الوحدات الفرعية. وقد استخدمنا على وجه التحديد نمطًا في الإعلان عن أنواع معيّنة من Events، حيث تمت تسمية كل رمز باسم Events فقط. وهذا يعني أنّه إذا كنت تستمع إلى أنواع متعدّدة من الأحداث التي تم تعريفها في ملفات مختلفة، قد يحدث تعارض في الاسم في عبارة import-لتلك Events.
  3. كما اتضح، كانت هناك تبعيات دائرية بين الملفات. كان هذا جيدًا في سياق النطاق العالمي، حيث كان استخدام الرمز بعد تحميل جميع التعليمات البرمجية. ومع ذلك، إذا كنت بحاجة إلى import، ستكون التبعية الدائرية واضحة. هذه ليست مشكلة على الفور، ما لم تكن هناك استدعاءات لدوال ذات تأثير جانبي في رمز النطاق العمومي، والذي تضمه "أدوات مطوري البرامج" أيضًا. وبوجه عام، احتاجت هذه العملية إلى بعض الجراحة وإعادة البناء لجعل عملية التحول آمنة.

عالم جديد تمامًا مع وحدات JavaScript

في شباط (فبراير) 2020، وبعد 6 أشهر من البدء في أيلول (سبتمبر) 2019، تم تنفيذ آخر عمليات تنظيف في المجلد ui/. كان ذلك بمثابة نهاية غير رسمية لعملية نقل البيانات. بعد أن يستقر الغبار، حدّدنا رسميًا عملية النقل على أنّها انتهت في 5 آذار (مارس) 2020. 🎉

والآن، تستخدم جميع الوحدات في "أدوات مطوري البرامج" وحدات JavaScript لمشاركة الرمز. ما زلنا نضع بعض الرموز على النطاق العام (في ملفات module-legacy.js) للاختبارات القديمة أو لدمجها مع أجزاء أخرى من بنية "أدوات مطوري البرامج". ستتم إزالتها بمرور الوقت، ولكننا لا نعتبرها عائقًا للتطوير في المستقبل. لدينا أيضًا دليل حول أسلوب استخدام وحدات JavaScript.

الإحصاءات

التقديرات المتحفظة لعدد متغيّرات التصميم التراكمية (CLs) (اختصار لقائمة التغييرات، وهو المصطلح المستخدَم في Gerrit والذي يمثّل تغييرًا على غرار طلب السحب في GitHub) المشارِكة في عملية النقل هذه حوالي 250 متغيّرات التصميم التراكمية، ويجريها مهندسان على الأرجح. ليس لدينا إحصاءات دقيقة عن حجم التغييرات التي تم إجراؤها، ولكن التقدير المتحفظ للخطوط التي تم تغييرها (يتم احتسابه على أنّه مجموع الفرق المطلق بين عمليات الإدراج والحذف لكل CL) يبلغ تقريبًا 30,000 (حوالي% 20 من جميع رموز الواجهة الأمامية في أدوات مطوّري البرامج).

تم شحن أول ملف يستخدم export في الإصدار 79 من Chrome، وتم طرحه في الإصدار الثابت في كانون الأول (ديسمبر) 2019. تم شحن آخر تغيير لنقل البيانات إلى import في Chrome 83، وتم طرحه ليصبح ثابتًا في أيار (مايو) 2020.

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

يمكنك الاطّلاع على الرحلة الكاملة (لا تكون جميع قوائم التغييرات مرفقة بهذا الخطأ، ولكن تم تسجيل معظمها) في crbug.com/1006759.

الاستنتاجات التي توصّلنا إليها

  1. يمكن أن يكون للقرارات التي يتم اتخاذها في الماضي تأثير طويل الأمد على مشروعك. على الرغم من توفّر وحدات JavaScript (وتنسيقات الوحدات الأخرى) لفترة طويلة، لم تكن "أدوات مطوّري البرامج" في وضع يسمح لها بإجراء عملية النقل. من الصعب تحديد وقت وموعد عدم الهجرة استنادًا إلى تخمينات مستنيرة.
  2. تقديراتنا الأولية كانت بالأسابيع وليس شهور. ينبع هذا إلى حد كبير من حقيقة أننا وجدنا مشكلات غير متوقعة أكثر مما توقعناه في تحليلنا الأولي للتكلفة. وعلى الرغم من أن خطة الانتقال كانت صلبة، إلا أن الديون التقنية كانت (في كثير من الأحيان أكثر مما كنا نرغب في ذلك) هي أداة الحظر.
  3. تضمّنت عملية نقل وحدات JavaScript قدرًا كبيرًا من عمليات تنظيف الديون التقنية (تبدو غير مرتبطة ببعضها). لقد سمح لنا الانتقال إلى تنسيق الوحدات الموحّدة الحديثة بإعادة تنظيم أفضل ممارسات الترميز مع تطوير الويب في العصر الحديث. على سبيل المثال، تمكّنا من استبدال حزمة Python المخصّصة بإعدادات تجميع محدودة.
  4. وعلى الرغم من التأثير الكبير على قاعدة الرموز لدينا (تغير حوالي 20٪ من الرمز)، تم الإبلاغ عن عدد قليل جدًا من التراجعات. على الرغم من أننا واجهنا العديد من المشكلات أثناء نقل أول ملفين، إلا أنه بعد فترة من الوقت كان لدينا سير عمل قوي وتلقائي جزئيًا. وهذا يعني أنّ عملية نقل البيانات هذه كانت للتأثير السلبي على المستخدمين الثابتين كان ضئيلاً.
  5. ومن الصعب أحيانًا شرح تفاصيل عملية انتقال معيّنة لمسؤولي النقل الآخرين، وقد يكون ذلك مستحيلاً في بعض الأحيان. من الصعب متابعة عمليات النقل بهذا المقياس وتتطلب الكثير من المعرفة بالمجال. وليس من المستحسن نقل معرفة النطاق إلى أشخاص آخرين يعملون على قاعدة الرموز نفسها في المهمة التي يؤدونها. إن معرفة ما يجب مشاركته والتفاصيل التي لا يجب مشاركتها هي فن، ولكنه ضروري. وبالتالي، من الأهمية بمكان تقليل عدد عمليات نقل البيانات الكبيرة، أو على الأقل عدم إجرائها في الوقت نفسه.

تنزيل قنوات المعاينة

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

التواصل مع فريق "أدوات مطوري البرامج في Chrome"

يُرجى استخدام الخيارات التالية لمناقشة الميزات والتغييرات الجديدة في المشاركة أو أي موضوع آخر ذي صلة بـ "أدوات مطوري البرامج".

  • يمكنك إرسال اقتراحات أو ملاحظات إلينا عبر crbug.com.
  • يمكنك الإبلاغ عن مشكلة في "أدوات مطوري البرامج" باستخدام خيارات إضافية   المزيد > مساعدة > الإبلاغ عن مشاكل في "أدوات مطوري البرامج" في "أدوات مطوري البرامج".
  • يمكنك نشر تغريدة على @ChromeDevTool.
  • يمكنك إضافة تعليقات على الميزات الجديدة في فيديوهات YouTube أو نصائح حول أدوات مطوّري البرامج في فيديوهات YouTube حول الميزات الجديدة.