继之前简单聊了下JS模块化,这次再来聊一聊CSS的模块化。
首先说说我们都遇到了哪些问题并且又有哪些需求:
1、CSS样式表是作用于全局的,如何做到样式隔离
2、复用CSS
3、共享常量
4、css命名难以理解
不像三剑客中的JS那样已经对于模块化出了一套完整的标准并且已经运用到日常的开发中,CSS显得落后了不少。不过聪明的前端工程师为了提高效率、解决问题也已经提出很多先进的理念和落地的技术方案,例如CSS Modules、CSS In Js、Scoped CSS等。
首先谈谈CSS Modules
A css-modules is a css file in which all class names and animation keyframe names
are scoped locally by default.
CSS Modules是一个可以默认所包含的所有的class name、animation keyframe names默认本地化的css文件。
简单的说,在一个文件中我们可以随意的定义类名,不用怕在全局出现冲突。
styles.css
.container {
background-color: #ff0000;
}
.paragraph {
color: blue;
font-weight: bold;
font-size: 20px;
}
当像JS Module将css文件导入的时候,会export一个对象,对象的属性是该css文件所有的类名以及animation Keyframe名,属性的值是则是处理后的name。
import styles from './styles.css';
element.innerHTML = `<div className=${styles.container}>
<p className=${styles.paragraph}>paragraph</p>
<p className=${styles.paragraph}>paragraph</p>
<p className=${styles.paragraph}>paragraph</p>
</div>`;
为了复用css,CSS Modules提供composition与声明dependencies的方式
.className {
background-color: #ff0000;
}
.otherClassName {
composes: className;
font-size: 20px;
}
/**styles.css**/
.className {
background-color: #ff0000;
}
/**otherStyles.css**/
.otherClassName {
composes: className from './styles.css';
font-size: 20px;
}
这类似于ES Module的named exports,可以做到精准依赖。
说了这么多,如何使用呢?
Webpack的css-loader就实现CSS Modules,当处于module模式下,css-loader会将css文件中的类名替换成一个全局唯一的标识符。
{
test: /.css$/,
use: [{
loader: 'css-loader',
options: {
module: true
}
}]
}
同样实现还有Postcss-Modules。
接下来再说一说CSS In JS
"CSS In JS" refers to a pattern where css is composed using JavaScript instead of
defined in external files.
其实思想很简单,就是将css以Javascript对象的形式表达出来。
const styles = {
container: {
margin: 0,
padding: 0,
backgroundColor: '#ccc',
},
};
function App() {
return (
<div style={styles.container}>
This is an app.
</div>
)
}
如果将css的写法都改成js的对象那复用性就会提升很多,不仅是单个样式的复用,常量的复用。这些样式最终都会被编译成内联的样式,就不会有样式全局污染这种问题了。
虽然css in js最终会将css样式变为行内样式,但由于其是用js的方式写css,所以不具备行内样式的常见的劣势。例如无法缓存、无法复用,不易维护等。
当然css in js还是有一些缺点的,比如无法使用一些css功能,例如伪元素、动画等。
还有一个是case by case的,并不是绝对的,就是性能问题。由于js转换为css为运行时转换,对于复杂的页面可能会有一些性能上的损耗。下图是138个节点分别用css modules和css in js设置样式,然后用performance监测性能,分别记录十次scripting时间,最后取平均值,可以得到下列结果。
不过目前社区里也有可以对CSS in JS的使用提前到预编译的阶段,去掉运行时的解决方案,具体可以去查阅astroturf。
还有一种用来在组件内隔离样式的方案是scoped-style
像vue中的scoped-css和angular中组件的范围化样式都是采用这种方案。
主要原理是为单个组件生成一个id,组件内所有的元素都会加上一个属性[id],而该组件内的其他选择器上都会叠加一个这个属性选择器,这样在该组件内的所有样式就不会影响全局。这和shadow DOM的样式隔离有些类似,在一个shadow DOM内定义的样式被范围化,只能影响shadow DOM内的元素。
<template>
<div class="container"></div>
</template>
<style scoped>
.container {
background-color: red;
}
</style>
编译之后的结果是
<div data-v-7ff7c89c class="container"></div>
.container[data-v-7ff7c89c] {
background-color: red;
}
其实w3c css工作组也出了Scoped Styles这一规范:
Scoped Styles is apply css rules to a subtree of document ranther than matching agaginst the entrie document.
关于复用css、共享变量 像一些css预处理工具(less、sass等)都已经提供一些支持的方案,但其实这些都已经被规范所支持。
@import用于从其他样式表中导入样式。
var()函数用来在设置样式属性值时引用当前结点或祖先节点自定义的变量。
总结一下吧
当前CSS的模块化中CSS Modules配合@import与var函数已经成为了CSS模块化的一种主流的方案。CSS Modules没有像CSS In JS那样激进,并且也能紧跟着CSS先进的发展步伐,能成为当前的首选方案也是顺其自然的事情。
其实讲到模块化也是绕不开软件工程的几大目标(可理解性、可维护性、可重用性),模块化只是“术”,软件工程才是真正的“道”。