Chrome DevTools如何从自行车移动到标准





关于Chrome DevTools如何从内部模块加载器迁移到标准JavaScript模块的快速说明。我们将告诉您迁移的方式和原因,延迟的原因,迁移的隐性成本以及迁移完成后DevTools团队的结论。但是,让我们从Web开发人员工具的历史开始。



介绍



您可能知道,Chrome DevTools是HTML,CSS和JavaScript网络应用程序。多年以来,DevTools已变得功能丰富,智能且对现代Web平台知识渊博。尽管DevTools进行了扩展,但其架构在很大程度上使该工具成为WebKit的一部分时的原始结构



我们将讲述DevTools的故事,描述解决方案的优势和局限性,以及我们为减轻这些局限性所做的工作。因此,让我们深入了解模块化系统,如何加载代码以及最终如何使用JavaScript模块。



一开始没什么



前端现在具有许多模块化系统及其工具,以及标准化的JavaScript模块格式。 DevTools启动时,这些都不是。该工具建立在12年前编写的WebKit代码之上。



DevTools中模块化系统的首次提及可以追溯到2012年:它是引入模块列表以及相应的资源列表。当时用于编译和构建DevTools的Python基础结构的一部分。在2013年,frontend_modules.json 此commit将模块检出到文件中,然后在2014年将其检入单独的文件中module.json此处)。范例module.json



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


自2014年以来,module.json在开发人员工具中用于指向模块和源文件。同时,网络生态系统迅速发展,并创建了许多模块格式:UMD,CommonJS和最终标准化的JavaScript模块。但是DevTools停留在module.json非标准化,独特的模块化系统具有以下缺点:



  1. module.json 需要自己的构建工具。
  2. 没有IDE集成。当然,她需要特殊的工具来创建自己理解的文件:( jsconfig.json对于VS Code)。
  3. 函数,类和对象已放置在全局范围内,以允许模块之间共享。
  4. 文件列出的顺序很重要。除人工验证外,无法保证您所依赖的代码已上传。


通常,通过评估DevTools模块化系统和其他更广泛使用的模块格式的当前状态,我们得出的结论是,它module.json带来的问题多于解决的问题。



标准的优点



我们选择了JavaScript模块。做出此决定后,Node.js中仍标记了该语言的模块,并且大量NPM软件包不支持它们。无论如何,我们得出的结论是JavaScript模块是最佳选择。



模块的主要优点是它们是一种语言标准化的格式当我们列出缺点时module.json,我们意识到几乎所有这些都与使用非标准化唯一模块格式有关。选择非标准化的模块格式意味着我们必须花费时间使用构建工具和同事的工具来构建集成。这样的集成通常是脆弱的,缺乏对功能的支持,需要额外的维护时间,有时还需要一些棘手的错误。这些错误最终打击了用户。



由于JavaScript模块是标准模块,因此这意味着VS Code这样的IDE,像Closure / TypeScript编译器这样的类型检查工具以及诸如Rollup和minifiers这样的构建工具都可以理解所编写的源代码。而且,当一个新人加入DevTools团队时,他们不必浪费时间学习专有知识module.json



当然,当DevTools最初启动时,以上优势都不存在。在标准组中花费了多年的时间来实现运行时。开发人员(模块的用户)需要花时间才能获得反馈。但是,当模块以该语言显示时,我们可以选择:继续支持我们自己的格式,或者投资过渡到新格式。



新奇的光芒要花多少钱?



即使JavaScript模块有很多我们想使用的好处,我们仍然留在世界上module.json。利用语言的模块意味着我们不得不在技术债务方面投入大量精力。同时,迁移可能会破坏功能并引入回归错误。



这与我们是否应该使用JavaScript模块无关。问题是使用JavaScript模块的能力有多昂贵。我们必须在使用户烦恼的风险与回归,迁移工程师所需的时间以及运行系统的状态恶化之间保持平衡。



最后一点非常重要。即使从理论上讲我们可以使用JavaScript模块,但在迁移过程中,我们最终都会得到同时考虑两种模块类型的代码这不仅在技术上具有挑战性,而且还意味着所有使用DevTools的工程师都需要知道如何在这样的环境中工作。他们将不得不不断地问自己:“这段代码是怎么回事,是module.jsonJS,我该如何进行更改?”

就培训同事而言,迁移的潜在成本高于我们的预期。
在分析了成本之后,我们得出的结论是,仍然值得转向该语言的模块。因此,我们的主要目标是:



  1. 确保标准模块尽可能有用。
  2. 确保与基础上现有模块的集成是module.json安全的,并且不会对用户造成负面影响(回归错误,用户沮丧)。
  3. 提供DevTools迁移指南。主要是通过内置于流程中的制衡机制来防止意外错误。


电子表格,转换和技术债务



目标很明确。但是局限性module.json很难克服。在我们提出一个可用的解决方案之前,它进行了多次迭代,原型和体系结构更改。我们最终编写了带有迁移策略项目文档该文档对时间进行了初步估算:2-4周。

迁移中最密集的部分花费了4个月的时间,从头到尾花费了7个月!
但是,原始计划经受了时间的考验:我们想教DevTools运行时以旧的方式加载所有文件,以使用阵列中scripts module.json列出的文件,而阵列中列出的所有文件modules必须通过动态语言import加载这将是在阵列中的任何文件modules可以工作import,并export从ES6。



此外,我们想分两个阶段进行迁移。最终,我们将最后一个阶段分为两个子阶段:导出和导入。在大型电子表格中跟踪了模块和阶段:





迁移表的一个片段



出口阶段



第一步是为需要在模块/文件之间共享的所有实体添加导出语句。通过为每个文件夹运行脚本来自动进行转换假设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.localFunctionInFilelocalFunctionInFile。但是,我们意识到将两个转换分开进行自动化和安全性更高。因此,“将所有实体传输到一个文件中”将成为第二个导入子阶段。



由于添加关键字export会将文件从“脚本”转换为“模块”,因此必须相应地更新许多DevTools基础结构。该框架包括动态导入运行时以及用于在模块模式下运行的工具(例如ESLint)。



令人讨厌的是,我们的测试是以“非严格”模式运行的。JavaScript模块暗示文件以严格模式运行。这影响了测试。事实证明,大量的测试依赖于非严格模式,包括存在操作员测试with



最后,更新第一个文件夹(添加导出)花费了大约一周的时间,尝试了几次重新加载



导入阶段



在使用导出语句导出所有实体后,由于遗留问题,它们保留在全局范围内,如果它们在多个文件中,则必须更新所有实体引用以使用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有些实体Modele.File甚至被命名为Module.CompletelyDifferentName不匹配意味着我们必须创建一个从旧全局对象到新导入对象的内部映射。
  2. moduleScoped. , Events, , Events. , , , import Events .
  3. , . , . , , . , , ( DevTools). , , .


JavaScript



在2020年9月开始的6个月后的2020年2月,最后的清理在ui /文件夹中进行。因此迁移正式结束了。尘埃落定后,我们正式标记迁移已于2020年3月5日完成



现在,DevTools仅适用于JavaScript模块。我们仍然将某些实体放在全局范围内(在旧文件中module.js)以进行旧式测试或与架构师工具的其他部分集成。它们会随着时间的流逝而被删除,但是我们不认为它们会阻碍开发。我们还为JavaScript模块提供了样式指南



统计



涉及此迁移的CL数量(保守列表(Gerrit中使用的术语,类似于GitHub拉取请求))的保守估计约为250 CL,主要由2位工程师完成。我们尚无关于更改大小的最终统计信息,但是对更改行的保守估计(每个CL插入和删除之间的绝对差之和)大约为30,000行,约占所有DevTools前端代码的20%



Chrome 79支持第一个要导出的文件,该文件于2019年12月的稳定版本中发布。切换到导入的最后一项更改是Chrome 83,该版本于2020年5月的稳定版本中发布。



我们知道,由于在稳定的Chrome浏览器中进行迁移,导致了一种回归。由于无关的默认导出,命令栏中的代码段完成中断还有其他一些回归,但是我们的自动测试用例和Chrome Canary用户已经报告了它们。我们修复了一些错误,然后才将其转变为稳定的Chrome版本。 您可以在这里注册了解整个故事并非全部,但大多数CL与该错误有关。







我们学到了什么?



  1. . , JavaScript ( ) , DevTools . , , , .
  2. — , . , , . , , , .
  3. (, ) . -. , Python Rollup.
  4. (~20% ), . , , . , .
  5. , . . , , , . , , — . , , .


图片


, Level Up , - SkillFactory:





E







All Articles