本文介绍了自 Chrome 85 起在开发者工具中提供的 CSS-in-JS 支持,以及 CSS-in-JS 的一般含义,以及它与开发者工具长期支持的常规 CSS 的区别。
什么是 CSS-in-JS?
CSS-in-JS 的定义相当模糊。从广义上讲,它是一种使用 JavaScript 管理 CSS 代码的方法。例如,这可能意味着 CSS 内容是使用 JavaScript 定义的,最终 CSS 输出由应用动态生成。
在开发者工具的上下文中,CSS-in-JS 表示使用 CSSOM API 将 CSS 内容注入页面。常规 CSS 是使用 <style>
或 <link>
元素注入的,并且具有静态来源(例如 DOM 节点或网络资源)。相反,CSS-in-JS 通常没有静态来源。这里有一个特殊情况,即可以使用 CSSOM API 更新 <style>
元素的内容,导致来源与实际 CSS 样式表不同步。
如果您使用任何 CSS-in-JS 库(例如 styled-component、Emotion、JSS),该库可能会在后台使用 CSSOM API 注入样式,具体取决于开发模式和浏览器。
我们来看看一些示例,了解如何使用 CSSOM API 注入样式表,类似于 CSS-in-JS 库的做法。
// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');
您还可以创建一个全新的样式表:
// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');
// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
开发者工具中的 CSS 支持
在开发者工具中,处理 CSS 时最常用的功能是样式窗格。在样式窗格中,您可以查看哪些规则应用于特定元素,还可以修改规则并实时查看页面上的更改。
在去年之前,对使用 CSSOM API 修改的 CSS 规则的支持非常有限:您只能查看已应用的规则,但无法修改它们。去年,我们的首要目标是允许使用“样式”窗格修改 CSS-in-JS 规则。有时,我们还会将 CSS-in-JS 样式称为“构建的”,以指明它们是使用 Web API 构建的。
我们来详细了解一下开发者工具中的样式编辑功能。
开发者工具中的样式修改机制
在 DevTools 中选择某个元素后,系统会显示 Styles 窗格。样式窗格会发出一个名为 CSS.getMatchedStylesForNode 的 CDP 命令,以获取应用于元素的 CSS 规则。CDP 是 Chrome DevTools Protocol 的缩写,这是一个 API,可让 DevTools 前端获取有关被检查网页的更多信息。
调用时,CSS.getMatchedStylesForNode
会识别文档中的所有样式表,并使用浏览器的 CSS 解析器对其进行解析。然后,它会构建一个索引,将每个 CSS 规则与样式表来源中的位置相关联。
您可能会问,为什么需要再次解析 CSS?问题在于,出于性能方面的原因,浏览器本身并不关心 CSS 规则的来源位置,因此不会存储这些位置。但开发者工具需要源代码位置才能支持 CSS 编辑。我们不希望普通 Chrome 用户付出性能代价,但希望开发者工具用户能够访问源代码位置。这种重新解析方法可同时满足这两种用例,并且缺点极少。
接下来,CSS.getMatchedStylesForNode
实现会要求浏览器的样式引擎提供与给定元素匹配的 CSS 规则。最后,该方法会将样式引擎返回的规则与源代码相关联,并提供有关 CSS 规则的有结构响应,以便开发者工具知道规则的哪个部分是选择器或属性。这样,开发者工具就可以独立修改选择器和属性。
现在,我们来看看编辑功能。还记得 CSS.getMatchedStylesForNode
会返回每个规则的源代码位置吗?这对于编辑至关重要。当您更改规则时,开发者工具会发出另一个 CDP 命令,该命令会实际更新网页。该命令包含要更新的规则 fragment 的原始位置,以及该 fragment 需要更新的新文本。
在后端,处理修改调用时,DevTools 会更新目标样式表。它还会更新其维护的样式表来源的副本,并更新更新后的规则的来源位置。作为对修改调用的响应,DevTools 前端会获取刚刚更新的文本片段的更新后位置。
这说明了为什么在开发者工具中修改 CSS-in-JS 不起作用:CSS-in-JS 没有存储在任何位置的实际来源,并且 CSS 规则位于浏览器内存中的 CSSOM 数据结构中。
我们如何添加对 CSS-in-JS 的支持
因此,为了支持修改 CSS-in-JS 规则,我们认为最佳解决方案是为构建的样式表创建一个源,以便使用上述现有机制进行修改。
第一步是构建源文本。浏览器的样式引擎会将 CSS 规则存储在 CSSStyleSheet
类中。正如前面所述,您可以通过 JavaScript 创建该类的实例。用于构建源文本的代码如下所示:
String InspectorStyleSheet::CollectStyleSheetRules() {
StringBuilder builder;
for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
builder.Append(page_style_sheet_->item(i)->cssText());
builder.Append('\n');
}
return builder.ToString();
}
它会迭代 CSSStyleSheet 实例中找到的规则,并据此构建单个字符串。创建 InspectorStyleSheet 类的实例时,系统会调用此方法。InspectorStyleSheet 类会封装 CSSStyleSheet 实例,并提取 DevTools 所需的其他元数据:
void InspectorStyleSheet::UpdateText() {
String text;
bool success = InspectorStyleSheetText(&text);
if (!success)
success = InlineStyleSheetText(&text);
if (!success)
success = ResourceStyleSheetText(&text);
if (!success)
success = CSSOMStyleSheetText(&text);
if (success)
InnerSetText(text, false);
}
在此代码段中,我们看到 CSSOMStyleSheetText
在内部调用 CollectStyleSheetRules
。如果样式表不是内嵌样式表或资源样式表,系统会调用 CSSOMStyleSheetText
。基本上,这两个代码段已经允许对使用 new CSSStyleSheet()
构造函数创建的样式表进行基本编辑。
一个特殊情况是,与使用 CSSOM API 发生了变异的 <style>
标记关联的样式表。在这种情况下,样式表包含源文本以及源代码中不存在的其他规则。为了处理这种情况,我们引入了一种方法,用于将这些额外的规则合并到源文本中。在这里,顺序很重要,因为 CSS 规则可以插入到原始源文本中间。例如,假设原始 <style>
元素包含以下文本:
/* comment */
.rule1 {}
.rule3 {}
然后,该页面使用 JS API 插入了一些新规则,产生了以下规则顺序:.rule0、.rule1、.rule2、.rule3、.rule4。合并操作后的源文本应如下所示:
.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}
保留原始注释和缩进对于编辑过程至关重要,因为规则的源文本位置必须精确无误。
CSS-in-JS 样式表的另一个特殊之处在于,页面可以随时更改它们。如果实际 CSSOM 规则与文本版本不同步,则无法进行修改。为此,我们引入了所谓的探针,以便浏览器在样式表发生更改时通知 DevTools 的后端部分。然后,在下次调用 CSS.getMatchedStylesForNode 时,系统会同步经过更改的样式表。
完成所有这些工作后,CSS-in-JS 编辑功能已经可以正常使用,但我们希望改进界面,以指明是否构建了样式表。我们在 CDP 的 CSS.CSSStyleSheetHeader 中添加了一个名为 isConstructed
的新属性,前端会使用该属性来正确显示 CSS 规则的来源:
总结
我们在本博文中介绍了与开发者工具不支持的 CSS-in-JS 相关的用例,并介绍了支持这些用例的解决方案。此实现的有趣之处在于,我们能够通过使 CSSOM CSS 规则具有常规源文本来利用现有功能,从而避免在 DevTools 中完全重新构建样式编辑功能。
如需了解更多背景信息,请参阅我们的设计提案或 Chromium 跟踪 bug,其中列出了所有相关补丁。
下载预览渠道
不妨考虑将 Chrome Canary 版、开发者版或 Beta 版用作默认开发浏览器。通过这些预览版渠道,您可以使用最新的 DevTools 功能、测试尖端的 Web 平台 API,并帮助您在用户发现问题之前发现网站上的问题!
与 Chrome DevTools 团队联系
您可以使用以下选项讨论与 DevTools 相关的新功能、更新或任何其他内容。
- 请访问 crbug.com 向我们提交反馈和功能请求。
- 在 DevTools 中,依次选择 More options > Help > Report a DevTools issue 以报告 DevTools 问题。
- 向 @ChromeDevTools 发送推文。
- 在 “开发者工具的新变化”YouTube 视频或 “开发者工具提示”YouTube 视频中留言。