Как вы, возможно, знаете, Chrome DevTools — это веб-приложение, написанное с использованием HTML, CSS и JavaScript. С годами DevTools стал более многофункциональным, умным и осведомленным о более широкой веб-платформе. Хотя DevTools с годами расширялся, его архитектура во многом напоминает исходную архитектуру, когда она еще была частью WebKit .
Этот пост является частью серии постов в блоге, описывающих изменения, которые мы вносим в архитектуру DevTools, и способы ее построения . Мы объясним, как DevTools исторически работал, каковы были преимущества и ограничения и что мы сделали, чтобы смягчить эти ограничения. Поэтому давайте углубимся в модульные системы, в то, как загружать код и как мы в итоге начали использовать модули JavaScript.
В начале ничего не было
Несмотря на то, что в нынешней среде внешнего интерфейса существует множество систем модулей с построенными на их основе инструментами, а также стандартизированный формат модулей JavaScript , ни одна из них не существовала на момент создания DevTools. DevTools построен на основе кода, который изначально появился в WebKit более 12 лет назад.
Первое упоминание о системе модулей в DevTools относится к 2012 году: введение списка модулей с соответствующим списком источников . Это была часть инфраструктуры Python, которая использовалась тогда для компиляции и сборки DevTools. В результате последующего изменения все модули были извлечены в отдельный файл frontend_modules.json
( коммит ) в 2013 году, а затем в отдельные файлы module.json
( коммит ) в 2014 году.
Пример файла module.json
:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
С 2014 года шаблон module.json
используется в DevTools для указания модулей и исходных файлов. Тем временем веб-экосистема быстро развивалась, и было создано множество форматов модулей, включая UMD, CommonJS и, в конечном итоге, стандартизированные модули JavaScript. Однако DevTools придерживался формата module.json
.
Хотя DevTools продолжал работать, было несколько недостатков использования нестандартизированной и уникальной системы модулей:
- Формат
module.json
требовал специальных инструментов сборки, аналогичных современным упаковщикам. - Не было интеграции IDE, что требовало специальных инструментов для создания файлов, понятных современным IDE ( исходный скрипт для создания файлов jsconfig.json для VS Code ).
- Функции, классы и объекты были помещены в глобальную область видимости, чтобы сделать возможным совместное использование между модулями.
- Файлы зависели от порядка, то есть порядок перечисления
sources
был важен. Не было никакой гарантии, что код, на который вы полагаетесь, будет загружен, за исключением того, что его проверил человек.
В целом, оценивая текущее состояние модульной системы в DevTools и других (более широко используемых) форматах модулей, мы пришли к выводу, что шаблон module.json
создает больше проблем, чем решает, и пришло время планировать наш уход. от этого.
Преимущества стандартов
Из существующих модульных систем мы выбрали модули JavaScript в качестве модулей для миграции. На момент принятия этого решения модули JavaScript все еще поставлялись под флагом Node.js, и у большого количества пакетов, доступных в NPM, не было пакета модулей JavaScript, который мы могли бы использовать. Несмотря на это, мы пришли к выводу, что модули JavaScript — лучший вариант.
Основное преимущество модулей JavaScript заключается в том, что это стандартизированный формат модулей для JavaScript . Когда мы перечислили недостатки module.json
(см. выше), мы поняли, что почти все они связаны с использованием нестандартизированного и уникального формата модуля.
Выбор нестандартизированного формата модуля означает, что нам самим придется потратить время на создание интеграции с инструментами сборки и инструментами, которые использовали наши сопровождающие.
Эти интеграции часто были хрупкими и не поддерживали функции, что требовало дополнительного времени на обслуживание и иногда приводило к тонким ошибкам, которые в конечном итоге доходили до пользователей.
Поскольку модули JavaScript были стандартом, это означало, что IDE, такие как VS Code, средства проверки типов, такие как Closure Compiler/TypeScript, и инструменты сборки, такие как Rollup/minifiers, смогут понять написанный нами исходный код. Более того, когда новый специалист по сопровождению присоединится к команде DevTools, ему не придется тратить время на изучение собственного формата module.json
, хотя он (вероятно) уже будет знаком с модулями JavaScript.
Конечно, когда DevTools был изначально создан, ни одного из вышеперечисленных преимуществ не существовало. Потребовались годы работы в группах по стандартизации, реализациям среды выполнения и разработчикам, использующим модули JavaScript и обеспечивающим обратную связь, чтобы достичь того уровня, на котором они находятся сейчас. Но когда стали доступны модули JavaScript, у нас появился выбор: либо продолжать поддерживать собственный формат, либо инвестировать в переход на новый.
Стоимость блестящей новинки
Несмотря на то, что модули JavaScript имели множество преимуществ, которые мы хотели бы использовать, мы остались в нестандартном мире module.json
. Использование преимуществ модулей JavaScript означало, что нам пришлось вложить значительные средства в устранение технического долга, выполнение миграции, которая потенциально могла нарушить работу функций и привести к ошибкам регрессии.
На тот момент речь шла не о том, «Хотим ли мы использовать модули JavaScript?», а о том , «Насколько дорого обходится использование модулей JavaScript?» . Здесь нам пришлось сбалансировать риск поломки наших пользователей с помощью регрессий, затраты инженеров, тратящих (большое количество) времени на миграцию, и временное худшее состояние, в котором мы будем работать.
Последний пункт оказался очень важным. Хотя теоретически мы могли бы перейти к модулям JavaScript, во время миграции мы получили бы код, который должен был бы учитывать как модуль module.json
, так и модули JavaScript. Этого было не только технически сложно достичь, но это также означало, что всем инженерам, работающим над DevTools, необходимо было знать, как работать в этой среде. Им придется постоянно спрашивать себя: «Для этой части кодовой базы это module.json
или модули JavaScript, и как мне внести изменения?».
Краткий обзор: скрытые затраты на сопровождение наших коллег-сопровождающих во время миграции оказались больше, чем мы ожидали.
После анализа затрат мы пришли к выводу, что переход на модули JavaScript все же имеет смысл. Поэтому нашими основными целями были следующие:
- Убедитесь, что использование модулей JavaScript приносит максимальную пользу.
- Убедитесь, что интеграция с существующей системой на основе
module.json
безопасна и не приводит к негативному воздействию на пользователей (регрессионные ошибки, разочарование пользователей). - Проведите всех сопровождающих DevTools через миграцию, в первую очередь с помощью встроенных сдержек и противовесов для предотвращения случайных ошибок.
Таблицы, преобразования и технический долг
Хотя цель была ясна, ограничения, налагаемые форматом module.json
оказалось трудно обойти. Потребовалось несколько итераций, прототипов и архитектурных изменений, прежде чем мы разработали решение, которое нас устраивало. Мы написали дизайн-документ со стратегией миграции, которую получили в итоге. В проектной документации также указана наша первоначальная оценка времени: 2–4 недели.
Спойлер: самая интенсивная часть миграции заняла 4 месяца, а от начала до конца — 7 месяцев!
Однако первоначальный план выдержал испытание временем: мы научим среду выполнения DevTools загружать все файлы, перечисленные в массиве scripts
в файле module.json
, используя старый способ, в то время как все файлы, перечисленные в массиве modules
, будут загружать модули JavaScript. динамический импорт . Любой файл, который будет находиться в массиве modules
, сможет использовать импорт/экспорт ES.
Кроме того, мы выполнили миграцию в два этапа (в конечном итоге мы разделили последний этап на два подэтапа, см. ниже): этапы 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
для работы в режиме модуля.
При работе над этими проблемами мы сделали одно открытие: наши тесты выполнялись в «небрежном» режиме. Поскольку модули JavaScript предполагают, что файлы выполняются в "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();
Однако при таком подходе были некоторые предостережения:
- Не каждый символ был назван
Module.File.symbolName
. Некоторые символы назывались исключительноModule.File
или дажеModule.CompletelyDifferentName
. Это несоответствие означало, что нам пришлось создать внутреннее сопоставление старого глобального объекта с новым импортированным объектом. - Иногда возникали конфликты между именами модулей. Наиболее заметно то, что мы использовали шаблон объявления определенных типов
Events
, где каждый символ назывался простоEvents
. Это означало, что если вы прослушивали несколько типов событий, объявленных в разных файлах, в оператореimport
для этихEvents
возникал конфликт имен. - Как оказалось, между файлами существовали циклические зависимости. Это было нормально в контексте глобальной области видимости, поскольку символ использовался после загрузки всего кода. Однако если вам требуется
import
, циклическая зависимость будет явной. Это не проблема сразу, если только у вас нет вызовов функций с побочным эффектом в вашем глобальном коде, который также был в DevTools. В общем, потребовались некоторые операции и рефакторинг, чтобы сделать преобразование безопасным.
Совершенно новый мир с модулями JavaScript
В феврале 2020 года, через 6 месяцев после старта в сентябре 2019 года, были произведены последние чистки в папке ui/
. Это ознаменовало неофициальный конец миграции. Дав улечься пыли, мы официально отметили миграцию как завершенную 5 марта 2020 года . 🎉
Теперь все модули в DevTools используют модули JavaScript для совместного использования кода. Мы по-прежнему размещаем некоторые символы в глобальной области видимости (в файлах module-legacy.js
) для наших устаревших тестов или для интеграции с другими частями архитектуры DevTools. Со временем они будут удалены, но мы не считаем их препятствием для будущего развития. У нас также есть руководство по стилю использования модулей JavaScript .
Статистика
По скромным оценкам количество CL (сокращение от «список изменений» — термин, используемый в Gerrit и обозначающий изменение — аналогично запросу на извлечение GitHub), участвующих в этой миграции, составляет около 250 CL, в основном выполняемых двумя инженерами . У нас нет точных статистических данных о размере внесенных изменений, но консервативная оценка измененных строк (рассчитанная как сумма абсолютной разницы между вставками и удалениями для каждого CL) составляет примерно 30 000 (~ 20% всего кода внешнего интерфейса DevTools). ) .
Первый файл, использующий export
появился в Chrome 79, выпущенном в стабильную версию в декабре 2019 года. Последнее изменение для перехода на import
появилось в Chrome 83, выпущенном в стабильную версию в мае 2020 года.
Нам известно об одной регрессии, которая была добавлена в стабильную версию Chrome и была представлена в рамках этой миграции. Автодополнение фрагментов в меню команд сломалось из-за постороннего экспорта default
. У нас было несколько других регрессий, но наши наборы автоматизированных тестов и пользователи Chrome Canary сообщили о них, и мы исправили их до того, как они смогли добраться до пользователей стабильной версии Chrome.
Посмотреть полный путь (не все CL привязаны к этой ошибке, но большинство из них) можно на crbug.com/1006759 .
Что мы узнали
- Решения, принятые в прошлом, могут иметь долгосрочное влияние на ваш проект. Несмотря на то, что модули JavaScript (и другие форматы модулей) были доступны уже довольно давно, DevTools не могла оправдать переход. Решение о том, когда мигрировать, а когда нет, является трудным и основано на обоснованных предположениях.
- Наши первоначальные оценки времени составляли недели, а не месяцы. Во многом это связано с тем, что мы обнаружили больше неожиданных проблем, чем ожидали при первоначальном анализе затрат. Несмотря на то, что план миграции был надежным, технический долг был (чаще, чем нам хотелось бы) препятствием.
- Миграция модулей JavaScript включала в себя большое количество (казалось бы, не связанных между собой) устранения технического долга. Переход на современный стандартизированный формат модулей позволил нам согласовать наши лучшие практики кодирования с современной веб-разработкой. Например, мы смогли заменить наш собственный упаковщик Python минимальной конфигурацией Rollup.
- Несмотря на большое влияние на нашу кодовую базу (изменилось около 20% кода), было зарегистрировано очень мало регрессий. Хотя при переносе первых нескольких файлов у нас возникло множество проблем, через некоторое время у нас появился надежный, частично автоматизированный рабочий процесс. Это означало, что негативное воздействие на наших постоянных пользователей в ходе этой миграции было минимальным.
- Обучать коллег-сопровождающих тонкостям конкретной миграции сложно, а иногда и невозможно. За миграциями такого масштаба сложно следить, и они требуют глубоких знаний предметной области. Передача этих знаний предметной области другим людям, работающим с той же кодовой базой, сама по себе нежелательна для выполняемой ими работы. Знать, чем поделиться, а какими деталями не делиться, — это искусство, но необходимое. Поэтому крайне важно сократить количество крупных миграций или, по крайней мере, не выполнять их одновременно.
Загрузите предварительный просмотр каналов
Рассмотрите возможность использования Chrome Canary , Dev или Beta в качестве браузера для разработки по умолчанию. Эти каналы предварительного просмотра предоставляют вам доступ к новейшим функциям DevTools, позволяют тестировать передовые API-интерфейсы веб-платформы и помогают находить проблемы на вашем сайте раньше, чем это сделают ваши пользователи!
Свяжитесь с командой Chrome DevTools
Используйте следующие параметры, чтобы обсудить новые функции, обновления или что-либо еще, связанное с DevTools.
- Отправляйте нам отзывы и запросы на добавление новых функций на crbug.com .
- Сообщите о проблеме DevTools, используя Дополнительные параметры > Справка > Сообщить о проблеме DevTools в DevTools.
- Напишите в Твиттере @ChromeDevTools .
- Оставляйте комментарии к видеороликам YouTube «Что нового в DevTools» или «Советы разработчика» на YouTube .