[翻译]CSS模块-未来的编码方式_html/css_WEB-ITnose
前言
这是Glen Maddern发布于2015年8月19日的一篇文章,主要是之前翻译的文章《理解CSS模块方法》里提到这篇文章,现在算是顺藤摸瓜跟进来看看。
这里的翻译都是根据我自己的理解进行的,所以不是一句一句的来的,有哪些不对的也在所难免,水平有限,希望大家指出。
正文
如果想在最近CSS开发思想上找到一个转变点,最好去找Christopher Chedeau 2014年11月在NationJS上发表的“css in js”演讲。这是一个分界线,各种不同的思想,就像高速粒子似的在自己的方向上快速发展。例如:在React及一些依赖React的项目中写样式, React Style,jsxstyle和Radium是其中三个,最新的,最巧妙的,和最可行的方法。如果发明是在一种探索的情况下相邻的可能(adjacent possible),那么christopher是创造了很多接近相邻(adjacent)可能性。
这些问题,以不同的形式存在于大的CSS代码库中。christopher指出,这些问题都可能通过在js中写代码来解决。但这种解决方案引入了其自身的复杂性和特性。只要看一下,在之前提到的项目中(React Style,jsxstyle和Radium),处理在:hover状态下range的方法。这个问题,在浏览器端css中已经早被解决了。
CSS Modules team找到问题的关键--保持和CSS一致,使用styles-in-js的方式编写。虽然我们还是坚持看好使用了CSS的形式,但还有要感谢对我们提供很多建议的朋友。
我们一直在绞尽脑汁地思考CSS,怎样去编写更好。
第1步:默认局部作用域
在css模块中,每一个文件都是独立编译的,因此你可以使用一些CSS短命名-不用担心命名冲突。下面看一下,提交按钮的4种状态的例
常规的CSS书写方法
用Suit或BEM命名、一些CSS样式、一段html。代码如下:
css代码段:
/* components/submit-button.css */
.Button { /* all styles for Normal */ }
.Button--disabled { /* overrides for Disabled */ }
.Button--error { /* overrides for Error */ }
.Button--in-progress { /* overrides for In Progress */
html代码段:
上面代码运行不错,我们有4种状态的类名,BEM命名,避免了使用嵌套选择器。使用大写字母开头的单词Button作为选择器,避免与之前或引用样式的类名冲突。并且我们使用--modifier语法来消除基础样式。
到现在为止,这都是一段不错的可维护的代码。但也引入了严格的命名规范。但这也是能用标准CSS,做到的最好的方式了。
CSS模块书写方法
使用CSS模块,你不用担心使用一些短命名了。可以像下面这样。
/* components/submit-button.css */
.normal { /* all styles for Normal */ }
.disabled { /* all styles for Disabled */ }
.error { /* all styles for Error */ }
.inProgress { /* all styles for In Progress */
看,你不用在任何地方再去加长长的前缀。为什么可以这样做,我们可以像其它语言一样,不用在本地变量前加长长的前缀,只要把CSS对应的文件名改成submit-botton.css。
这可以让在JS中使用require或import加载的CSS模块文件,可以被编译出来。
/* components/submit-button.js */
import styles from './submit-button.css';
buttonElem.outerHTML = ``
真正在页面使用的样式名,是动态生成的唯一标识。CSS模块把文件编译成ICSS格式的文件,这种格式文件可以方便CSS和JS进行通信。当你运行程序,会得到类似下面的代码
得到类似结果,说明运行成功~
命名约定
还是拿按钮的例子来说
/* components/submit-button.css */
.normal { /* all styles for Normal */ }
.disabled { /* all styles for Disabled */ }
.error { /* all styles for Error */ }
.inProgress { /* all styles for In Progress */
所有类名都是独立的,不是一个是基类名,其它的用来修改。在CSS模块中,所有类必须包括所有的属性和样式。这让你在JS中使用类名时有很大的不同。
/* 不要像这样 */
`class=${[styles.normal, styles['in-progress']].join(" ")}`
/* 不同之处是使用单独的类名 */
`class=${styles['in-progress']}`
/* 最好使用驼峰式 */
`class=${styles.inProgress}`
当然,如果你是按照代码量来收钱的,你可以按照你的方式继续。
一个React例子
这里不是关于React特有的CSS模块。但React提供了,在使用CSS模块时,特别优秀的体验。下面做一个复杂点的例子。
/* components/submit-button.jsx */
import { Component } from 'react';
import styles from './submit-button.css';
export default class SubmitButton extends Component {
render() {
let className, text = "Submit"
if (this.props.store.submissionInProgress) {
className = styles.inProgress text = "Processing..."
} else if (this.props.store.errorOccurred) {
className = styles.error
} else if (!this.props.form.valid) {
className = styles.disabled
} else {
className = styles.normal
}
return
}
}
你可以使用你的样式,不用再担心全局冲突,让你可以专注于组件开发,而不是在写样式上。一旦离开之前的频繁在CSS,js之间切换方式,你就再也不想回去了。
但这只是开始,当你考虑样式合并时,CSS模块又没法使用了。
第2步 一切皆为组件
前面提到CSS模块需要每种状态都包含所有所需的样式。
这里假设你需要多个状态,我们对比一下CSS模块和BEM命名。
/* BEM Style */
innerHTML = `
/* CSS Modules */
innerHTML = `
等一下,如何在所有状态共享样式呢?答案是CSS模块的最有力工具-组件。
.common { /* all the common styles you want */ }
.normal { composes: common; /* anything that only applies to Normal */ }
.disabled { composes: common; /* anything that only applies to Disabled */ }
.error { composes: common; /* anything that only applies to Error */ }
.inProgress { composes: common; /* anything that only applies to In Progress */ }
关键词composes指出.normal包含.common中的样式,就像sass里的@extend关键词一样。sass是通过重写css选择器来实现的。css模块则是通过改变js中使用的类名来实现。
SASS:
使用前面的BEM例子,使用一些SASS的@extend
.Button--common { /* font-sizes, padding, border-radius */ }
.Button--normal { @extends .Button--common; /* blue color, light blue background */}
.Button--error { @extends .Button--common; /* red color, light red background */}
这将编译为
.Button--common, .Button--normal, .Button--error { /* font-sizes, padding, border-radius */ }
.Button--normal { /* blue color, light blue background */ }
.Button--error { /* red color, light red background */ }
你只需要在你的标签上引用一个类名,可以得到通用的和独有的样式。功能很强大,但你必须知道,这也存在着特殊情况和陷阱。Hugo Giraudel 汇总了一些问题,想了解更多,请点击《为什么你应该避免使用SASS的@extend》
使用CSS模块
composes关键词和@extend使用方法类似,但工作方式是不同的。看个例子
.common { /* font-sizes, padding, border-radius */ }
.normal { composes: common; /* blue color, light blue background */ }
.error { composes: common; /* red color, light red background */ }
在浏览器中将会被编译为
.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 { /* blue color, light blue background */ }
.components_submit_button__error__1638bcd { /* red color, light red background */ }
在js代码中,import styles from "./submit-button.css"将得到
styles: {
common: "components_submit_button__common__abc5436",
normal: "components_submit_button__common__abc5436 components_submit_button__normal__def6547", error: "components_submit_button__common__abc5436 components_submit_button__error__1638bcd"
}
还是使用styles.normal或stryles.error,在DOM中将被渲染为多个类名
这就是composes的功能,你可以合并多个样式,但不用去修改你的JS代码,也不会重写你的CSS选择器。
第3步.文件间共享代码
使用SASS或LESS工作,通过@import来引用同一个工作空间的文件。你可以声明变量,函数,并在其它文件中使用。很不错的方法,但在各个不同的项目中,变量命名有可能冲突。那么你就得重构你的代码,编写如variables.scss和settings.scss,你也不清楚哪些组件依赖于哪些个变量了。你的settings文件会变得很大。
也有更好的解决方案(《使用Webpack构建更小巧的CSS》),但由于SASS的全局属性,还是有很大的限制。
CSS模块一次只运行一个单独的文件,因此不会污染全局作用域。js代码用使用import或require来引用依赖,CSS模块使用compose从另一个文件引用样式。
/* colors.css */
.primary { color: #720; }
.secondary { color: #777; }/* other helper classes... */
/* submit-button.css */
.common { /* font-sizes, padding, border-radius */ }
.normal { composes: common; composes: primary from "../shared/colors.css"; }
使用组件,我们可以像引用本地类名一样,引用colors.css文件的类。而且,组件变化的类名在输出时会被改变,但CSS文件本身并不变化,composes块也会在生成浏览器端CSS之前被去除。
/* colors.css */
.shared_colors__primary__fca929 { color: #720; }
.shared_colors__secondary__acf292 { color: #777; }
/* submit-button.css */
.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 {}
实际上,在浏览器端,normal没有自身的样式。这是好事情,你可以添加新的语义化的对象,但不用去添加CSS样式。我们还可以做得更多一点,
在全站开发中增加类名和视觉的一致性,在浏览器端减少样式代码的大小。
旁注:可以使用csso检测并移除空类。
第4步:单一职责模块
组件的强大之处在于描述一个元素是什么,而不修饰它的样式。它以一种不同的方式来映射页面实体(元素)和样式实体(样式规则)。
看一个旧的CSS例子
.some_element { font-size: 1.5rem; color: rgba(0,0,0,0); padding: 0.5rem; box-shadow: 0 0 4px -2px; }
一个元素,一些样式,很简单。尽管这样,还是存在一些问题:color,font-size,box-shadow,padding,这些都在这里指定了,但无法在其它地方使用。
我们用SASS重构一下。
$large-font-size: 1.5rem;
$dark-text: rgba(0,0,0,0);
$padding-normal: 0.5rem;
@mixin subtle-shadow { box-shadow: 0 0 4px -2px; }
.some_element {
@include subtle-shadow;
font-size: $large-font-size;
color: $dark-text;
padding: $padding-normal;
}
比旧的CSS样式有很大的改进,我们只是定义了很少的一部分。事实上像$large-font-size是排版,$padding-normal是布局,这些都仅仅用名字表达含义,不会在任何地方运行。如果要声明一个box-shadow变量,但它并不能表达自身含义,这时就必须使用@mixin或@extend了。
使用CSS模块
通过使用组件,我们可以在组件中,注释写明哪些可以重复使用的类名。
.element {
composes: large from "./typography.css";
composes: dark-text from "./colors.css";
composes: padding-all-medium from "./layout.css";
composes: subtle-shadow from "./effect.css";
}
使用文件系统,而不是命名空间,来划分不同用途的样式。自然会出现引用多个单一用途的文件。
如果你想从一个文件中引用多个类,这里有一个简便的方法:
/* this short hand: */
.element {
composes: padding-large margin-small from "./layout.css";
}
/* is equivalent to: */
.element {
composes: padding-large from "./layout.css";
composes: margin-small from "./layout.css";
}
使你在网站开发上,每一种视觉对应一个类名。用上面的方式,来开发你的网站,变为一种可能。
.article {
composes: flex vertical centered from "./layout.css";
}
.masthead {
composes: serif bold 48pt centered from "./typography.css";
composes: paragraph-margin-below from "./layout.css";
}
.body {
composes: max720 paragraph-margin-below from "layout.css";
composes: sans light paragraph-line-height from "./typography.css";
}
这是一种我有兴趣进一步探索的技术。在我看来,它结合了像Tachyons的原子CSS技术,像Semantic UI样式类名的可读性,单一职责等优势。
但CSS模块的故事才刚刚开始,希望你能去在现在或将来使用它,并传播它。
上手
通过使用CSS模块,希望能帮助你和你的团队,即可以交流当前的CSS知识和产品,又可以更舒服,更高效地完成工作。我们已经尽可能保持语法的简单,并写了一些例子,当你可以使用这些例子里的代码时,你就可以使用它进行工作了。这里有一些关于Webpack,JSPM和Browseriry项目的DEMO,希望对你有所帮助。我们一直看有哪些新的环境可以运行CSS模块:正在适配服务器端NODEJS和Rails。
为了使事情更简单,这里做了一个Plunkr,可以直接动手,不用安装。开始吧
如果你准备使用了,可以看一看CSS模块源码,如果有什么问题,可以在issue里进行讨论。CSS模块组,规模小,无法涵盖所有的应用场景。
期待你们的讨论。
祝:写样式开心。
推荐阅读
-
[翻译]CSS模块-未来的编码方式_html/css_WEB-ITnose
-
实现CSS等分布局的4种方式_html/css_WEB-ITnose
-
CSS全屏布局的5种方式_html/css_WEB-ITnose
-
实现html锚点的两种方式_html/css_WEB-ITnose
-
css垂直居中的几种方式_html/css_WEB-ITnose
-
栅格布局的两种简单的实现方式_html/css_WEB-ITnose
-
未来CSS文件加载方式_html/css_WEB-ITnose
-
PostCSS一种更优雅、更简单的书写CSS方式_html/css_WEB-ITnose
-
Schema ? 模块化,响应式的前端开发框架_html/css_WEB-ITnose
-
解说css中的margin属性缩写方式_html/css_WEB-ITnose