模块化目前在前端的日常开发中已经不是什么新鲜词,早期AMD规范的requirejs,CMD规范的seajs,以及nodejs的模块化规范commonjs,但是css的模块化一直进展不大,虽然有想less,sass,postcss…的出先,但是这些只是改善了css弱编程方面的问题,在模块化方面还是进展比较缓慢。
首先,前端模块化是大势所趋,而CSS又是前端开发中重要的一部分,在大型web项目中,没有规范的模块化方案的问题对日常的开发和维护影响越来越大。
其次,模块化的好处在项目的维护,提高代码的复用率,便于组内协同开发,提高开发效率方面的效果是肉眼可见的
所以CSS样式的模块化一直在不断的尝试和探索
在讲如何实现CSS模块化之前先讲讲要利用模块化来解决什么问题:
我们在写样式的时候最常见的方式就是为各个模块的根节点设置一个唯一的类名,然后采用样式的后代选择器的方式来实现的,比如下面这样:
<div class="container">
<div class="area1">div>
<div class="area2">
<div class="area3">div>
div>
div>
样式文件:
.container > .area1{
}
.container > .area2 {
}
.container > .area2 .area3 {
}
当然,我们可以借助less,sass这类预处理器来写上面的样式,这样样式的可编程能力会更好,同时样式文件的层次也会更加清晰,比如我们用less写上的样式文件
index.less
.container{
.area1{}
.area2{
.area3{
}
}
}
这样看起来一方面解决了不影响其他模块的问题,同时css的编程能力弱的问题也得到了解决。but ! but ! but ! 这只是针对前端项目比较小的时候,开发人员少比如就一个人就搞定了,像这类前端工程,这种方式确实就能解决问题了。如果这个前端工程师有多个前端开发人员协同开发,甚至可能还设计到跨小组开发的情况,那么这种方式就明显不适用了,因为别人在写类名的时候,他是不知道什么类名已经使用了,所以这也可能导致类名重名,并不能真正解决样式不污染其他的样式和被其他样式污染的问题。
既然有可能重名,那么能不能使用hash值来生成dom元素的唯一标识呢?这样就可以避免重名了。别说,还是真可以。下面我们就来说说在目前前端流行框架Vue和React在CSS模块化方面的尝试。
写过vue项目的人对scoped
应该不陌生,比如像下面这样
<template>
<div class="conatianer">
<div class="area1"></div>
</div>
</template>
<script></script>
<style scoped>
.conatianer{...}
.conatianer .area1{...}
</style>
这样就能让我们的样式只对当前模块起作用,不会影响其他的模块的样式。那他是如何实现的呢?先来看看这样写最后呈现出来的样式文件到底长什么样子,打开浏览器的调试窗口我们会看到style中的样式变成了下面这样:
.conatianer[data-v7ba5bd90] {...}
.conatianer[data-v7ba5bd90] .area1 {...}
当我们不使用scoped
属性的浏览器中看到的结果结项下面这样:
.conatiane {...}
.conatianer .area1{...}
是不是豁然开朗了,Vue本身实现模块化的实质就是在.vue文件编译的时候为dom元素生成一个唯一的属性,然后css使用属性选择器来选择唯一的元素,这样就可以实现私有化的效果了。但这不是我们追求的CSS 模块化,这么说呢?
我们知道CSS选择器是有权重这个概念的,不同的选择器的权重是不一样的,权值也高,优先级的样式就也高,低权值的样式会被高权值的样式覆盖。
Vue的Scoped方案是用来避免自身模块的样式不会干扰其他模块,但是你不惹人家,并不代表人家不来惹你,就像中美贸易战一样,这就是很好的例子。自身的样式可能会被其他没有私有化的样式干扰,在Vue的开发中这类问题不是没有碰见过,比如一些第三方的样式文件,以及一些没有进行私有化的样式文件。所以自身强大才是硬道理。
其实从Vue使用scoped这个词也可以知道,scoped
的意思是范围
,官方并没有使用modules
模块这个词,也就是说Vue的Scoped只是解决了私有化的问题。
当然,Vue的scoped虽然有瑕疵,但是人家在模块化这方面还是做了贡献的,就是先管好自己,自己先做一个好好先生,如果人人都是好好先生,那么不就是人间到处充满爱了吗?当然这有点理想主义,现实是很复杂的,除了自己做个好好先生外,我们还有足够的方法来保护自己和亲人不受他人的伤害。那么还有什么方式来实现样式的模块呢?说过Vue怎么能少了React呢?
在react中,官方推荐使用的是 css modules,具体用法就像下面这样:
main.jsx:
import React,{Component} from "react";
import styles from "assets/css/main.css";
class main extends Component{
constructor(props){
super(props);
}
render(){
return (
<div class={styles.container}>
<div class={styles.child}></div>
</div>)
}
}
export default main;
main.css:
.container{
...
}
.child{
...
}
页面正常显示,打开浏览调试窗口的时候我们会发现原来我们写的样式已经变成下面这样了:
.container-5c73dda7{
...
}
.child-46hgsds3{
...
}
页面中元素的类型也变成了了对应的
<div class="container-5c73dda7">
<div class="child-46hgsds3">div>
div>
通过上面的代码我们可以发现,这是通过唯一的类名和样式做映射,从而达到css模块化的一种方案,这种方案相比于Vue的CSS私有化属性scoped来说好处就是可以防止样式被其他的样式文件污染,也可以阻止自己的样式污染别的模块。当然这里有一个问题,那就是我们在写类名的时候需要用到了变量,这和我们平时写html的习惯有点不一致,那有没有什么解决办法,还是按照我们平常的编码方式。当然有,在React中有高阶组件这个概念,我们可以使用高阶组件,然后正常写元素的类名,就像下面这样:
import React,{Component} from "react";
import styles from "assets/css/main.css";
import CSSModules from "react-css-modules";
class main extends Component{
constructor(props){
super(props);
}
render(){
return (
<div class={styles.container}>
<div class={styles.child}></div>
</div>)
}
}
export default CSSModules(styles,main);
还可以使用ES7的"装饰器"decorator来使用高阶组件,代码会更加简洁,向下面这样:
import React,{Component} from "react";
import styles from "assets/css/main.css";
import CSSModules from "react-css-modules";
@CSSModules(styles)
class main extends Component{
constructor(props){
super(props);
}
render(){
return (
<div class={styles.container}>
<div class={styles.child}></div>
</div>)
}
}
export default main;
是不是更方便了,和我们平时写React没什么区别。
看了上面提到的CSS模块方式,明显感觉到其实React中提倡的这种方案是一种目前为止比较能接受的方式,一方面他能达到模块化的要求,另一方面在代码的编写方式上也没有太大的改变,那么这里就要提到这边文章的猪脚了,也就是react推崇的这种样式模块化方案 CSS Modules。
CSS Modules
就像他的名字一样,就叫CSS模块化,牛逼先吹出来。它不像Vue中的scoped方案,他是由css-loader去实现的,也就是由webpack的loader去实现的,所以在使用CSS Modules时需要配合webpack的css-loader进行编译,就像下面这样先配置webpack配置:
module.exports = {
entry:{...},
output:{...},
module:{
rules:[{
test:/\.css$/,
exclude:/node_module/,
use:[{
loader:'style-loader'
},{
loader:'css-loader',
options:{
importLoaders:1
modules:true,//开启样式模块化
loaclIdentName:'[name]-[hash:8]'//class名称生产规则
}
}]
}]
}
}
除了css,less,sass这类预处理器也可以配合css-loader,使用开启样式的模块化。
具体的使用规范,可以去看阮玉峰的CSS Modules使用教程上面有关于它的详细使用教程,当然他的教程是在React的基础上来说的,但并不表示CSS Modules是专门为React服务的,在Vue中也是能使用的,只是Vue官方已经实现了scoped方案了,正常情况下该方案也能完成我们的需求。
总结一下上面提到的三种方式:
总的来说,CSS Modules方案通过js来维护CSS中的类名的唯一映射值的方案是目前来说更加可行的一种方案。相比较于像CSS Inline完全使用js来编写样式来说已经有比较大的进步了。相信在后面版本的CSS规范中,会有更好的解决方案。