写点什么

微软提出 CSS Modules V1 :通过 import 语句将 CSS 模块导入到组件中

2019 年 8 月 29 日

微软提出CSS Modules V1 :通过import语句将CSS模块导入到组件中

CSS Modules V1 是 Microsoft 提出的一项新建议,它是 ES Script 模块系统提出了一项扩展。在 CSS 模块的帮助下,Web 开发人员可以将 CSS 加载到组件定义中(例如 import styles from “styles.css”;),并且与其他模块类型无缝对接。


为什么我们需要 CSS 模块?

ES6 规范引入了 JavaScript 模块系统,为 Web 开发人员带来了诸多好处;引入模块后 JS 代码更趋于组件化,开发人员也更容易管理依赖项。但组件定义中缺少对 CSS 的支持,之前一直没有对应的解决方案。现有的实践总有以下一种或几种缺陷:



  • 副作用,比如将<style>元素附加到文档中的操作就会有副作用。如果在文档的顶级作用域内完成此操作,那么它会破坏 shadow 根样式作用域。如果这个操作在 shadow 根内部完成,则组件的每个独立实例必须在其 shadow 根实例中包含它自己的<style>元素。



  • 内联 CSS 文本作为 JavaScript 中的字符串。这种操作没有作充分的性能优化(它同时由 JS 和 CSS 解析器处理),并且会为开发人员带来糟糕的体验。



  • 动态 fetch()ing CSS 通常不是静态可分析的,并且需要开发人员对复杂应用程序的依赖项做非常细致的管理工作。



CSS 模块扩展了 ES 模块基础结构,允许从 CSS 文件导入 CSSStyleSheet 对象,然后就可以通过adoptStyleSheets数组将其添加到文档或 shadowRoot 中了。引入 CSS 模块后,上述问题也都能得到解决。


这个功能也是开发社区呼吁的——可以参阅这里的讨论,其中有许多开发人员对其表示了兴趣。JS bundler 中的 CSS 加载器大受欢迎,也是这项功能潜在需求的佐证之一。


导入 CSS 模块

可以继续使用导入其他 ES 模块所用的import语句导入 CSS 模块:


import styles from "styles.css";document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
复制代码


模块的默认导出是从 CSS 文件生成的 CSSStyleSheet。CSS 模块没有命名导出。


一些实现细节

程序会检查 HTTP 响应头中的 MIME 类型以确定如何解析指定模块。MIME 类型的 text/css 将被视为 CSS 模块。导入的每个 CSS 模块都有自己的模块记录,符合 ES6 规范的定义;这些模块还会参与模块映射,并进入模块依赖关系图。


CSS 模块的 V1 版本将使用 Synthetic 模块构建。具体来说,给定一个 text/css 文件,要创建一个新的 CSS 模块遵循以下步骤:


  1. 通过 constructor 创建一个 CSSStyleSheet()。

  2. 在新样式表上调用CSSStyleSheet.replaceSync,并将文件内容作为参数(关于为什么这里用 replaceSync 而不是 replace,后文会具体说明)。此调用抛出的错误会导致模块创建失败并出现解析错误。

  3. 通过CreateSyntheticModule创建一个新的 Synthetic 模块,其中“default”作为 exportNames 的唯一条目,并使用 evaluateSteps 调用 SetMutableBinding(“default”, sheet),其中 sheet 是在步骤 1 中创建的 CSSStyleSheet。

  4. 使用在步骤 3 中创建的 Synthetic 模块创建新的 CSS 模块脚本作为其记录。


为什么使用 CSSStyleSheet.replaceSync 而不是 CSSStyleSheet.replace?

CSS 模块的 V1 版本功能不全,暂时不支持 @import。这里的原因在于,不清楚 CSS 模块中的 @import 是否应该被视为模块图中它自己的 CSS 模块,也不知道 CSS 模块是否应该是 leaf module。我们正在考虑三种可行方法:


  1. CSS 模块是 leaf module,且不允许 @import 引用(遵循可构造样式表中的replaceSync示例。这就是 CSS 模块 V1 版本采用的实现方法,这也是为什么上文描述的 CSS 模块创建的第 2 步使用 replaceSync 而不是 replace;如果给定输入包含 @import 规则,则抛出 replaceSync。

  2. CSS 模块是 leaf module;在为 CSS 模块创建模块记录之前,加载其样式表的完整 @import 树,如果无法解析,则将其视为模块的解析错误。

  3. CSS 模块不是 leaf modul。将 CSS 模块的 @import 后的样式表作为模块图中请求的子模块处理,子模块带有自己的模块记录。它们将被实例化并作为不同的模块评估。


从长远来看,1 号选项会引入不必要的限制。


选项 2 和 3 之间的主要区别之一是,3 意味着如果 CSS 文件对于给定领域多次 @import,则每个导入都会共享单独的一份 CSSStyleSheet(因为对于给定的模块 specifier,一个模块仅会被实例化/评估一次)。如果开发人员错误地多次包含一个样式表或者由于共享的 CSS 依赖,就有可能带来内存/性能损失。另一方面这也是与现有行为背道而驰,现在同一.css 文件的多个 @import 各自带有自己的 CSSStyleSheet。


@justinfagnani 在这里指出,在选项 3 中共享 @imported 样式表后,开发人员用工具编辑 CSS 或主题系统时,可以动态更改共享样式表,并在样式表的所有不同导入器上应用更改。


但正如 @tabatkins 在这里提到的那样,选项 3 与当前的 @import 行为有很大的不同,它无法动态再现:CSS 对象模型不能用来使多个样式表依赖于单个子样式表。.parentStyleSheet 和.ownerRule 引用这里也存在问题,因为这些引用当前仅引用单个表,并且如果样式表具有多个导入器就会糊涂了。


这里的讨论更加深入一些。鉴于目前大家尚未达成共识,我们现在的 V1 版本会使用选项 1 来回避问题。这是向前兼容的,因为在 CSS 模块中只要使用 @import 就会阻止模块加载。我们不准备等待选项 2 和 3 争出结果后才推进工作,因为早早发布版本后我们就可以获得早期开发人员对该功能的反馈,更好地了解它在实践中的使用方式,从而作出更加合适的决策。此外,CSS 模块的 V1 版本完成后,HTML模块的开发工作也能正常推进了。


示例:使用 CSS 模块的自定义元素定义

以下是关于定义自定义元素的示例,其中 CSS 是作为 JavaScript 字符串内联置入的:


<!doctype html><html>    <head>        <script type="module">            class HTML5Element extends HTMLElement {                constructor() {                    super();                    let shadowRoot = this.attachShadow({ mode: "open" });
let style = document.createElement("style"); style.innerText = ` .outerDiv { border:0.1em solid blue; display:inline-block; padding: 0.4em; }
.devText { font-weight: bold; font-size: 1.2em; text-align: center; margin-top: 0.3em; }
.mainImage { height:254px; } `;
let outerDiv = document.createElement("div"); outerDiv.className = "outerDiv"; let mainImage = document.createElement("img"); mainImage.className = "mainImage"; mainImage.src = "https://www.w3.org/html/logo/downloads/HTML5_Logo_512.png"; let devText = document.createElement("div"); devText.className = "devText"; devText.innerText = "CSS Modules Are Great!";
this.shadowRoot.appendChild(outerDiv); outerDiv.appendChild(mainImage); outerDiv.appendChild(devText); this.shadowRoot.appendChild(style); } }
window.customElements.define("my-html5-element", HTML5Element); </script> </head> <body> <my-html5-element></my-html5-element> </body></html>
复制代码


以下示例中,上面的自定义元素定义合并到一个 CSS 模块,以避免 CSS-as-a-JS-string(或插入<style>标记等):


<!doctype html><html>    <head>        <script type="module">            import styles from './html5Element.css';
class HTML5Element extends HTMLElement { constructor() { super(); let shadowRoot = this.attachShadow({ mode: "closed" });
this.shadowRoot.adoptedStyleSheets = [styles];
let outerDiv = document.createElement("div"); outerDiv.className = "outerDiv"; let mainImage = document.createElement("img"); mainImage.className = "mainImage"; mainImage.src = "https://www.w3.org/html/logo/downloads/HTML5_Logo_512.png"; let devText = document.createElement("div"); devText.className = "devText"; devText.innerText = "CSS Modules Are Great!";
shadowRoot.appendChild(outerDiv); outerDiv.appendChild(mainImage); outerDiv.appendChild(devText); } }
window.customElements.define("my-html5-element", HTML5Element); </script> </head> <body> <my-html5-element></my-html5-element> </body></html>
复制代码


英文原文:CSS Modules V1 Explainer


2019 年 8 月 29 日 08:304819

评论

发布
暂无评论
发现更多内容

怎么向女朋友解释什么叫区块链?

艾小仙

比特币 区块链 以太坊 defi

云原生 go-zero 微服务框架

Kevin Wan

go golang microservice go-zero 微服务框架

一个在交流群里讨论过两轮的问题,答案竟然跟一个 PEP 有关

Python猫

Python 编程

职场求生攻略答疑篇之 3 —— 数据是土地

臧萌

数据 职场成长

智能商业时代的思考(一)从在线化到网络化

刘旭东

拼多多 淘宝 智能商业 网络协同

如何将VSCode变成绿色版本

lmymirror

vscode 教程

2020-09-03-第十三周学习总结

路易斯李李李

Elasticsearch之mapping

北漂码农有话说

SpringBoot 缓存之常用注解

hepingfly

Java 缓存 springboot 注解

Google鼓励的13条代码审查标准 [建议收藏]

简爱W

java安全编码指南之:声明和初始化

程序那些事

安全编码 java安全编码 编码指南 对象初始化

人生革命由自律发起

胡迪伦

自学编程 拖延症 懒惰 死循环

拥抱K8S系列-04-基于docker部署更多应用

张无忌

Docker 标准化 vsftpd

宁波新基建之路 基于制造优势破题智慧发展

CECBC区块链专委会

新基建

为什么Java二维数组不用指定列的长度

Rayjun

Java 数组

2020-09-03-第十三周作业

路易斯李李李

区块链技术破解数字版权保护难题

CECBC区块链专委会

区块链 版权保护 数字技术

oeasy教您玩转linux010206toilet

o

Python 为什么能支持任意的真值判断?

Python猫

Python 编程

商业通识 : 商业到底是什么?

Walker

学习 得到 个人成长 商业

[翻译]Defer,Panic,and Recover

卓丁

golang defer panic recover

妈妈,今天您几点下班?

脑极体

Flink从保存点启动应用-18

小知识点

scala 大数据 flink

为稳外贸保驾护航 区块链交易平台显身手

CECBC区块链专委会

区块链 银行 福费廷

Python 为什么要在 18 年前引入布尔类型?且与 C、C++ 和 Java 都不同?

Python猫

Python 编程

持续集成有什么好处?快来看鸭

清菡

jenkins

JavaScript 深拷贝与浅拷贝

梁凤波

区块链技术应用于链接智慧医疗

CECBC区块链专委会

区块链 社会保险 智能医疗

首个数字银行卡明年发行,广州出台区块链措施支持大湾区

CECBC区块链专委会

区块链 金融科技 社会

【MySQL】我这样分析MySQL中的事务,面试官对我刮目相看!!

冰河

MySQL 面试 事务 隔离级别 冰河

商业通识 : 商业为什么能进步?

Walker

学习 得到 个人成长 商业

「中国技术开放日·长沙站」现场直播

「中国技术开放日·长沙站」现场直播

微软提出CSS Modules V1 :通过import语句将CSS模块导入到组件中-InfoQ