به روز رسانی معماری DevTools: مهاجرت به ماژول های جاوا اسکریپت

تیم ون در لیپه
Tim van der Lippe

همانطور که می دانید Chrome DevTools یک برنامه وب است که با استفاده از HTML، CSS و جاوا اسکریپت نوشته شده است. در طول سال‌ها، DevTools دارای ویژگی‌های غنی‌تر، هوشمندتر و دانش‌تر در مورد پلتفرم وب گسترده‌تر شده است. در حالی که DevTools در طول سال ها گسترش یافته است، معماری آن تا حد زیادی شبیه معماری اصلی زمانی است که هنوز بخشی از WebKit بود.

این پست بخشی از یک سری پست های وبلاگ است که تغییراتی را که ما در معماری DevTools ایجاد می کنیم و نحوه ساخت آن را توضیح می دهد. ما توضیح خواهیم داد که DevTools در طول تاریخ چگونه کار کرده است، مزایا و محدودیت‌ها چه بوده و برای کاهش این محدودیت‌ها چه کرده‌ایم. بنابراین، بیایید عمیقاً به سیستم های ماژول، نحوه بارگذاری کد و نحوه استفاده از ماژول های جاوا اسکریپت بپردازیم.

در ابتدا چیزی وجود نداشت

در حالی که نمای ظاهری کنونی دارای انواع سیستم‌های ماژول با ابزارهایی است که در اطراف آن‌ها ساخته شده‌اند، و همچنین قالب ماژول‌های جاوا اسکریپت استاندارد شده در حال حاضر ، هیچ‌کدام از این‌ها در زمانی که DevTools برای اولین بار ساخته شد وجود نداشت. DevTools بر روی کدی ساخته شده است که در ابتدا بیش از 12 سال پیش در WebKit ارسال شد.

اولین اشاره به یک سیستم ماژول در DevTools از سال 2012 سرچشمه می گیرد: معرفی لیستی از ماژول ها به همراه فهرست منابع مرتبط . این بخشی از زیرساخت پایتون بود که در آن زمان برای کامپایل و ساخت DevTools استفاده می شد. یک تغییر بعدی همه ماژول ها را در یک فایل frontend_modules.json جداگانه ( commit ) در سال 2013 و سپس به فایل های module.json جداگانه ( commit ) در سال 2014 استخراج کرد.

نمونه فایل module.json :

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

از سال 2014، الگوی module.json در DevTools برای تعیین ماژول ها و فایل های منبع آن استفاده می شود. در همین حال، اکوسیستم وب به سرعت تکامل یافت و قالب های ماژول های متعددی از جمله UMD، CommonJS و در نهایت ماژول های استاندارد جاوا اسکریپت ایجاد شد. با این حال، DevTools با فرمت module.json گیر کرده است.

در حالی که DevTools به کار خود ادامه می‌دهد، استفاده از یک سیستم ماژول غیر استاندارد و منحصربه‌فرد چند جنبه منفی داشت:

  1. قالب module.json نیاز به ابزار ساخت سفارشی داشت، شبیه باندلرهای مدرن.
  2. هیچ یکپارچه سازی IDE وجود نداشت، که نیاز به ابزار سفارشی برای تولید فایل هایی داشت که IDE های مدرن قابل درک بودند ( اسکریپت اصلی برای تولید فایل های jsconfig.json برای VS Code ).
  3. توابع، کلاس ها و اشیاء همگی در محدوده جهانی قرار گرفتند تا اشتراک گذاری بین ماژول ها ممکن شود.
  4. فایل‌ها وابسته به ترتیب بودند، به این معنی که ترتیب فهرست‌بندی sources مهم بود. هیچ تضمینی وجود نداشت که کدی که به آن تکیه می کنید بارگیری شود، به جز اینکه یک انسان آن را تأیید کرده باشد.

در مجموع، هنگام ارزیابی وضعیت فعلی سیستم ماژول در DevTools و سایر فرمت‌های ماژول (که بیشتر مورد استفاده قرار می‌گیرند)، به این نتیجه رسیدیم که الگوی module.json بیشتر از آن که حل کند، مشکلاتی ایجاد می‌کند و زمان آن فرا رسیده است که برای دور کردن خود برنامه‌ریزی کنیم. از آن

مزایای استانداردها

از بین سیستم‌های ماژول موجود، ماژول‌های جاوا اسکریپت را به عنوان یکی از ماژول‌های مهاجرت انتخاب کردیم. در زمان آن تصمیم، ماژول‌های جاوا اسکریپت هنوز پشت پرچمی در Node.js ارسال می‌شدند و تعداد زیادی از بسته‌های موجود در NPM، ماژول‌های جاوا اسکریپت را نداشتند که بتوانیم از آن استفاده کنیم. با وجود این، ما به این نتیجه رسیدیم که ماژول های جاوا اسکریپت بهترین گزینه هستند.

مزیت اصلی ماژول های جاوا اسکریپت این است که قالب ماژول استاندارد شده برای جاوا اسکریپت است. وقتی نکات منفی module.json را فهرست کردیم (به بالا مراجعه کنید)، متوجه شدیم که تقریباً همه آنها مربوط به استفاده از یک قالب ماژول غیر استاندارد و منحصر به فرد است.

انتخاب یک قالب ماژول غیراستاندارد به این معنی است که ما باید زمان خود را برای ادغام ساختن با ابزارهای ساخت و ابزارهایی که نگهبانان ما استفاده می‌کنند، صرف کنیم.

این ادغام‌ها اغلب شکننده بودند و از ویژگی‌ها پشتیبانی نمی‌کردند، به زمان نگهداری اضافی نیاز داشتند، که گاهی منجر به اشکالات ظریفی می‌شد که در نهایت برای کاربران ارسال می‌شد.

از آنجایی که ماژول‌های جاوا اسکریپت استاندارد بودند، به این معنی بود که IDEهایی مانند VS Code، بررسی‌کننده‌های نوع مانند Closure Compiler/TypeScript و ابزارهای ساخت مانند Rollup/minifiers قادر به درک کد منبعی هستند که ما نوشتیم. علاوه بر این، هنگامی که یک نگهدارنده جدید به تیم DevTools می‌پیوندد، نیازی به صرف زمان برای یادگیری یک قالب اختصاصی module.json ندارند، در حالی که (احتمالاً) قبلاً با ماژول‌های جاوا اسکریپت آشنا هستند.

البته، زمانی که DevTools در ابتدا ساخته شد، هیچ یک از مزایای فوق وجود نداشت. سال‌ها کار در گروه‌های استاندارد، پیاده‌سازی زمان اجرا و توسعه‌دهندگانی که از ماژول‌های جاوا اسکریپت استفاده می‌کردند، طول کشید تا به نقطه‌ای که اکنون هستند، بازخورد ارائه کنند. اما زمانی که ماژول‌های جاوا اسکریپت در دسترس قرار گرفتند، یک انتخاب داشتیم: یا به حفظ قالب خود ادامه دهیم، یا برای مهاجرت به قالب جدید سرمایه‌گذاری کنیم.

هزینه براق جدید

اگرچه ماژول‌های جاوا اسکریپت دارای مزایای فراوانی بودند که می‌خواهیم از آنها استفاده کنیم، اما در دنیای غیر استاندارد module.json باقی ماندیم. بهره‌مندی از مزایای ماژول‌های جاوا اسکریپت به این معنی بود که ما باید به میزان قابل توجهی در پاکسازی بدهی‌های فنی سرمایه‌گذاری می‌کردیم و مهاجرتی را انجام می‌دادیم که به طور بالقوه می‌توانست ویژگی‌ها را شکسته و باگ‌های رگرسیون را ایجاد کند.

در این مرحله، سوال این نبود که "آیا می خواهیم از ماژول های جاوا اسکریپت استفاده کنیم؟"، بلکه سوال این بود که "چقدر می توان از ماژول های جاوا اسکریپت استفاده کرد؟" . در اینجا، ما باید خطر شکستن کاربران خود را با رگرسیون، هزینه مهندسان (مقدار زیادی از) زمان مهاجرت و وضعیت بدتر موقتی که در آن کار می‌کنیم، متعادل می‌کردیم.

این نکته آخر بسیار مهم بود. حتی اگر در تئوری می‌توانستیم به ماژول‌های جاوا اسکریپت برسیم، در طول یک مهاجرت، کدی را دریافت می‌کنیم که باید هر دو ماژول module.json و جاوا اسکریپت را در نظر بگیرد. دستیابی به این امر نه تنها از نظر فنی دشوار بود، بلکه به این معنی بود که همه مهندسانی که روی DevTools کار می کنند باید بدانند چگونه در این محیط کار کنند. آنها باید به طور مداوم از خود بپرسند "برای این بخش از پایگاه کد، آیا ماژول های module.json یا جاوا اسکریپت است و چگونه می توانم تغییراتی ایجاد کنم؟".

نگاهی پنهانی: هزینه پنهان هدایت نگهبانان همکارمان از طریق مهاجرت بیشتر از آن چیزی بود که پیش‌بینی می‌کردیم.

پس از تجزیه و تحلیل هزینه، به این نتیجه رسیدیم که هنوز ارزش مهاجرت به ماژول های جاوا اسکریپت را دارد. بنابراین اهداف اصلی ما به شرح زیر بود:

  1. اطمینان حاصل کنید که استفاده از ماژول های جاوا اسکریپت از مزایای آن تا حد ممکن بهره می برد.
  2. اطمینان حاصل کنید که ادغام با سیستم مبتنی بر module.json موجود ایمن است و منجر به تأثیر منفی کاربر (اشکالات رگرسیون، ناامیدی کاربر) نمی شود.
  3. برای جلوگیری از اشتباهات تصادفی، تمام نگهدارندگان DevTools را در انتقال هدایت کنید، در درجه اول با چک و تعادل داخلی.

صفحات گسترده، تحولات و بدهی فنی

در حالی که هدف مشخص بود، رفع محدودیت‌های اعمال شده توسط قالب module.json دشوار بود. چندین تکرار، نمونه های اولیه و تغییرات معماری طول کشید تا راه حلی را ایجاد کنیم که با آن راحت بودیم. ما یک سند طراحی با استراتژی مهاجرتی که به پایان رسیدیم نوشتیم. سند طراحی همچنین تخمین زمان اولیه ما را ذکر کرده است: 2-4 هفته.

هشدار اسپویلر: فشرده ترین قسمت مهاجرت 4 ماه و از ابتدا تا انتها 7 ماه طول کشید!

با این حال، طرح اولیه آزمایش زمان را پس داد: ما به DevTools آموزش می‌دهیم که تمام فایل‌های فهرست شده در آرایه scripts را در فایل module.json با استفاده از روش قدیمی بارگیری کند، در حالی که همه فایل‌های فهرست شده در آرایه modules با ماژول‌های جاوا اسکریپت واردات پویا . هر فایلی که در آرایه modules قرار داشته باشد می تواند از واردات/صادرات ES استفاده کند.

علاوه بر این، ما مهاجرت را در 2 فاز انجام می دهیم (ما در نهایت فاز آخر را به 2 مرحله فرعی تقسیم می کنیم، در زیر ببینید): فاز export و import . وضعیت ماژول در کدام فاز در یک صفحه گسترده بزرگ ردیابی شد:

صفحه گسترده مهاجرت ماژول های جاوا اسکریپت

قطعه ای از برگه پیشرفت در اینجا برای عموم در دسترس است.

فاز 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 در یک فایل، فایل را از یک "اسکریپت" به یک "ماژول" تبدیل می کند، بسیاری از زیرساخت های DevTools باید بر این اساس به روز می شدند. این شامل زمان اجرا (با واردات پویا)، اما همچنین ابزارهایی مانند ESLint برای اجرا در حالت ماژول بود.

یکی از کشفیاتی که در حین کار بر روی این مسائل به دست آوردیم این است که آزمایشات ما در حالت "درهم و برهم" اجرا می شدند. از آنجایی که ماژول‌های جاوا اسکریپت نشان می‌دهند که فایل‌ها در حالت "use strict" اجرا می‌شوند، این روی تست‌های ما نیز تأثیر می‌گذارد. همانطور که مشخص شد، تعداد غیر ضروری تست‌ها بر این شلختگی تکیه می‌کردند، از جمله تستی که از عبارت with - استفاده می‌کرد.

در پایان، به‌روزرسانی اولین پوشه برای گنجاندن بیانیه‌های export حدود یک هفته طول کشید و چندین تلاش با relands انجام شد.

فاز 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. گاهی اوقات بین نام های moduleScoped درگیری وجود دارد. برجسته‌تر از همه، ما از الگویی برای اعلام انواع خاصی از Events استفاده کردیم، که در آن هر نماد فقط Events نام داشت. این بدان معنی است که اگر به انواع مختلفی از رویدادهای اعلام شده در فایل‌های مختلف گوش می‌دادید، یک تداخل نام در عبارت import - برای آن Events رخ می‌دهد.
  3. همانطور که معلوم شد، وابستگی های دایره ای بین فایل ها وجود دارد. این در زمینه دامنه جهانی خوب بود، زیرا استفاده از نماد پس از بارگیری همه کدها بود. با این حال، اگر به import نیاز دارید، وابستگی دایره‌ای مشخص می‌شود. این بلافاصله مشکلی نیست، مگر اینکه در کد دامنه جهانی خود، که DevTools نیز داشت، فراخوانی تابع عوارض جانبی داشته باشید. در مجموع، برای ایمن ساختن این تحول نیاز به جراحی و بازسازی مجدد داشت.

یک دنیای کاملا جدید با ماژول های جاوا اسکریپت

در فوریه 2020، 6 ماه پس از شروع در سپتامبر 2019، آخرین پاکسازی ها در پوشه ui/ انجام شد. این نشان دهنده پایان غیررسمی مهاجرت بود. پس از نشستن گرد و غبار، ما به طور رسمی مهاجرت را در 5 مارس 2020 به عنوان پایان یافته علامت گذاری کردیم. 🎉

اکنون، همه ماژول ها در DevTools از ماژول های جاوا اسکریپت برای اشتراک گذاری کد استفاده می کنند. ما همچنان برخی از نمادها را در محدوده جهانی (در فایل‌های module-legacy.js ) برای آزمایش‌های قدیمی خود یا برای ادغام با سایر بخش‌های معماری DevTools قرار می‌دهیم. اینها به مرور زمان حذف خواهند شد، اما ما آنها را مسدود کننده ای برای توسعه آینده نمی دانیم. ما همچنین یک راهنمای سبک برای استفاده از ماژول های جاوا اسکریپت داریم.

آمار

تخمین های محافظه کارانه برای تعداد CL ها (مخفف تغییرات لیست - اصطلاحی که در Gerrit استفاده می شود که نشان دهنده یک تغییر است - شبیه به درخواست کشش GitHub) دخیل در این مهاجرت حدود 250 CL است که عمدتا توسط 2 مهندس انجام می شود . ما آمار قطعی در مورد اندازه تغییرات انجام شده نداریم، اما یک تخمین محافظه کارانه از خطوط تغییر یافته (محاسبه شده به عنوان مجموع اختلاف مطلق بین درج ها و حذف ها برای هر CL) تقریباً 30000 (~20٪ از کل کد ظاهری DevTools) است. ) .

اولین فایل با استفاده export در Chrome 79 ارسال شد، در دسامبر 2019 به حالت پایدار منتشر شد. آخرین تغییر برای انتقال به import در Chrome 83 ارسال شد، در ماه مه 2020 به پایدار عرضه شد.

ما از یک رگرسیون مطلع هستیم که به Chrome stable ارسال شده و به عنوان بخشی از این انتقال معرفی شده است. تکمیل خودکار قطعات در منوی فرمان به دلیل صادرات default غیرمجاز شکست خورد . ما چندین رگرسیون دیگر داشته‌ایم، اما مجموعه‌های آزمایشی خودکار ما و کاربران Chrome Canary این موارد را گزارش کردند و قبل از اینکه بتوانند به کاربران پایدار Chrome دسترسی پیدا کنند، آنها را برطرف کردیم.

می‌توانید سفر کامل را ببینید (همه CLها به این باگ متصل نیستند، اما اکثر آنها هستند) در crbug.com/1006759 وارد شده‌اند.

چیزی که یاد گرفتیم

  1. تصمیمات اتخاذ شده در گذشته می تواند تاثیر طولانی مدتی بر پروژه شما داشته باشد. اگرچه ماژول‌های جاوا اسکریپت (و سایر قالب‌های ماژول) برای مدتی طولانی در دسترس بودند، DevTools در موقعیتی نبود که مهاجرت را توجیه کند. تصمیم گیری در مورد زمان مهاجرت و زمان عدم مهاجرت دشوار و بر اساس حدس و گمان های تحصیل کرده است.
  2. تخمین‌های زمانی اولیه ما به‌جای ماه‌ها، هفته‌ها بود. این تا حد زیادی از این واقعیت ناشی می شود که ما بیشتر از آنچه در تحلیل هزینه اولیه خود پیش بینی کرده بودیم، مشکلات غیرمنتظره ای پیدا کردیم. با وجود اینکه طرح مهاجرت قوی بود، بدهی فنی (بیشتر از آنچه که ما دوست داشتیم) مسدود کننده بود.
  3. مهاجرت ماژول های جاوا اسکریپت شامل مقدار زیادی پاکسازی بدهی فنی (به ظاهر نامرتبط) بود. مهاجرت به قالب ماژول استاندارد شده مدرن به ما این امکان را داد تا بهترین شیوه های کدنویسی خود را با توسعه وب امروزی دوباره هماهنگ کنیم. به عنوان مثال، ما توانستیم باندلر Python سفارشی خود را با یک پیکربندی Minimal Rollup جایگزین کنیم.
  4. علیرغم تأثیر زیاد روی پایگاه کد ما (~20٪ از کد تغییر کرده است)، رگرسیون بسیار کمی گزارش شده است. در حالی که ما با مشکلات متعددی در انتقال دو فایل اول روبرو بودیم، پس از مدتی یک گردش کار کاملاً خودکار و نیمه خودکار داشتیم. این بدان معناست که تأثیر منفی کاربر برای کاربران پایدار ما برای این مهاجرت حداقل بود.
  5. آموزش پیچیدگی های یک مهاجرت خاص به نگهبانان همکار دشوار و گاهی غیرممکن است. پیگیری مهاجرت در این مقیاس دشوار است و نیاز به دانش دامنه زیادی دارد. انتقال آن دانش دامنه به دیگرانی که در همان پایگاه کد کار می کنند فی نفسه برای کاری که انجام می دهند مطلوب نیست. دانستن اینکه چه چیزهایی را به اشتراک بگذارید و چه جزئیاتی را به اشتراک نگذارید یک هنر است، اما لازم است. بنابراین بسیار مهم است که میزان مهاجرت های بزرگ را کاهش دهیم، یا حداقل آنها را همزمان انجام ندهیم.

کانال های پیش نمایش را دانلود کنید

استفاده از Chrome Canary ، Dev یا Beta را به عنوان مرورگر توسعه پیش‌فرض خود در نظر بگیرید. این کانال‌های پیش‌نمایش به شما امکان دسترسی به جدیدترین ویژگی‌های DevTools را می‌دهند، به شما اجازه می‌دهند APIهای پلتفرم وب پیشرفته را آزمایش کنید و به شما کمک می‌کنند تا قبل از کاربران، مشکلات سایت خود را پیدا کنید!

با تیم Chrome DevTools در تماس باشید

از گزینه‌های زیر برای بحث در مورد ویژگی‌های جدید، به‌روزرسانی‌ها یا هر چیز دیگری مربوط به DevTools استفاده کنید.