简单易懂的CSS Modules

不要误会,CSS Modules可不是在说“css模块化”这个好像在某些地方见过的词,它实际上是特指一种近期才出现的技术手段。javascript

什么技术手段呢?请待后文说明。css

层叠样式表

咱们知道,css的全名叫作层叠样式表,这个“层叠”究竟是什么意思呢?html

有一种解释是,若是你先写了一条样式规则(选手1):java

.title {
    color: silver;
}

而后又在后边写了一条相似的(选手2):react

.title {
    color: gold;
}

由于名字相同,选手2就会和选手1打起来(让你丫冒充我!)。结果是选手2获胜,class名为title的元素,最终的color值为goldwebpack

css里就像这样,随时可能一言不和就发生战争,结果输掉的一方就会被胜利的一方所覆盖。“层叠”一词能够说形象地描述了这个过程。git

那么,为何会有这样的层叠(zhàn zhēng )呢?github

css的做用域问题

在javascript里,能够作到这样的搭配:web

var title = "silver";

(function(){
    var title = "gold";
    console.log(title); // gold
}());

console.log(title); // silver

利用javascript的函数做用域,两位一样名为title的选手能够友好相处。sass

但回到css里的样式规则,状况就彻底不是这么回事了。

css不是程序语言,但若是说要给它加一个做用域的概念的话,那就是:只有全局做用域。

不管分拆为多少个css文件,不管用怎样的方式引入,全部的样式规则都位于同一做用域,只要选择符近似,就有发生覆盖的可能

减小相互影响的策略

为减小相互影响,避免预料以外的样式覆盖,咱们一直以来想过不少办法。

好比你接手一个别人留下来的旧项目,接下来要新增一个标题元素的时候,你会有意识地不去使用.title这样模糊的class名,由于它太容易重名了。最终,你用的名称多是:

.module-sp-title {
    color: deepskyblue;
}

即便你决定要用.title这个名字,你也会加上包含选择符做为限定:

.module-1 .title { 
    font-size: 18px;
}
/* ... */
.module-2 .title {
    font-size: 14px;
}

其中.module-1.module-2的名字应该是惟一的,这样的代码在组件化(模块化)的开发风格里很常见。

此外,一些有名的css理论,如SMACSS,会建议你为全部布局样式使用l-layout-的前缀,以示区分。

相似的作法还有不少,但归结起来,都是在尝试提供一种合理的命名约定。而合理的命名约定,的确是组织css代码的有效策略。

如今,咱们有了新的可用策略,CSS Modules就是其中之一。

技术流的模块化

CSS Modules是一种技术流的组织css代码的策略,它将为css提供默认的局部做用域。

CSS Modules是如何作到的呢?来看一个CSS Modules的简单例子吧。

有这样的一个html元素:

<h2 id="example_title" class="title">a title for CSS Modules</h2>

按照普通css的写法,咱们能够这样为它添加样式:

.title {
    background-color: snow;
}

如今咱们改用CSS Modules。首先,css保持不变。而后,修改html的写法。再也不这样直接写html,而是改成在javascript文件里动态添加,这样作(css文件名为main.css):

var styles = require("./main.css");

var el = document.getElementById("example_title");
el.outerHTML = '<h2 class="' + styles.title + '">a title for CSS Modules</h2>';

咦,require了一个css文件?对的,因此要用到webpack。编译后,html和css会变成这样:

CSS Modules的示例

看到这样不太美观的class名你大概就明白了,CSS Modules没法改变css全局做用域的本性,它是依靠动态生成class名这一手段,来实现局部做用域的。显然,这样的class名就能够是惟一的,无论本来的css代码写得有多随便,均可以这样转换获得不冲突的css代码。

模拟的局部做用域也没有关系,它是可靠的。

这个CSS Modules的例子说完了,但你必定跟我最初看到的时候同样有不少问题。

CSS Modules的应用细节

如何启用CSS Modules

“webpack编译css我也用过,怎么我用的时候不长这样?”

通常来讲,require一个css文件的写法是:

require("./main.css");

但在前面的例子中,用了var styles = require("./main.css");的写法。这就好像是在说,我要这个css文件里的样式是局部的,而后我根据须要自行取用。

在项目里应用CSS Modules有不少方法,目前比较经常使用的是使用webpack的css-loader。在webpack配置文件里写css-loader?modules就能够开启CSS Modules,例如前面的例子所用的:

module: {
    loaders: [{
        test: /\.css$/,
        loader: 'style!css?modules'
    }]
}

才发现一直用着的css-loader原来有这功能?其实,CSS Modules确实是一个后来才并入css-loader的新功能。

自定义生成的class名

“名字都这样了,还怎么调试?”

为css-loader增长localIdentName参数,是能够指定生成的名字。localIdentName的默认值是[hash:base64],通常开发环境建议用相似这样的配置:

{
    test: /\.css$/,
    loader: 'style!css?modules&localIdentName=[name]__[local]___[hash:base64:5]'
}

一样应用到前面的例子里,这时候就会变成这样的结果:

CSS Modules指定名字

这样是否是要有意义多了?

若是是线上环境,能够考虑用更短的名字进一步减少css文件大小。

CSS Modules下的html

(看了前面例子里的el.outerHTML = ...后)

“什么,outerHTML?class名还要拼接?你家html才这么写呢!”

很遗憾,CSS Modules官方的例子,也是这个意思:要使用CSS Modules,必须想办法把变量风格的class名注入到html中。也就是说,html模板系统是必需的,也正是如此,相比普通css的状况,CSS Modules的html写起来要更为费劲。

若是你搜一下CSS Modules的demo,能够发现大部分都是基于React的。显然,虚拟DOM风格的React,搭配CSS Modules会很容易(ES6):

import styles from './ScopedSelectors.css';

import React, { Component } from 'react';

export default class ScopedSelectors extends Component {

  render() {
    return (
      <div className={ styles.root }>
        <p className={ styles.text }>Scoped Selectors</p>
      </div>
    );
  }

};

若是不使用React,仍是那句话,只要有办法把变量风格的class名注入到html中,就能够用CSS Modules。原始的字符串拼接的写法显然很糟糕,但咱们能够借助各类模板引擎和编译工具作一些改进。下面请看一个用Jade的参考示例。

想象一下你有一个用普通css的页面,但你想在一小块区域使用CSS Modules。这一块区域在一个容器元素里:

<div id="module_sp_container"></div>

而后用jade来写html(关联的css文件为module_sp.css):

- styles = require("./module_sp.css");
h2(class=styles.title) a title for CSS Modules

接下来,仍然是在javascript里添加这段jade生成的html:

var el = document.getElementById("module_sp_container");
var template = require("./main.jade");
el.innerHTML = template();

最后,记得在css-loader启用CSS Modules的同时,增长jade-loader:

{
    test: /\.jade$/,
    loader: 'jade'
}

编译运行,就能够获得想要的结果。除Jade之外,还有些其余CSS Modules的html应用方案,推荐参考github上的这篇issue

目前CSS Modules还在发展中,并且也在考虑改进CSS Modules下的html写做体验。CSS Modules团队成员有提到一个叫CSS Modules Injector的将来规划项目,目的是让开发者不用javascript也可使用CSS Modules(这就很接近原生html + css的组合了)。

CSS Modules下的样式复用

“样式都是惟一的了,怎么复用?”

咱们已经说了挺多普通css单个全局做用域的坏处。但对应的,这也有一个很大的好处,就是便于实现样式的复用。css理论OOCSS也是在追求这一点。

CSS Modules提供一个composes方法用于样式复用。例如,你有一个btn.css里有一条:

.btn{
    display: inline-block;
}

而后,你在另外一个CSS Module的module_sp.css里能够这样引入它:

.btn-sp{
    composes: btn from "./btn.css";
    font-size: 16px;
}

那么,这个div.btn-sp的DOM元素将会是:

CSS Modules compose

能够看到,composes的用法比较相似sass的@extend,但不一样于@extend的是,composes并不增长css里的选择符总量,而是采用组合多个class名的形式。在这个例子里,本来仅有1个class的div.btn-sp,变成了2个class。

所以,CSS Modules建议只使用1个class就定义好对应元素所需的所有样式。它们会再由CSS Modules转换为适当的class组合。

CSS Modules团队成员认为composes是CSS Modules里最强大的功能:

For me, the most powerful idea in CSS Modules is composition, where you can deconstruct your visual inventory into atomic classes, and assemble them at a module level, without duplicating markup or hindering performance.

更详细的composes的用法及其理解,推荐阅读CSS Modules: Welcome to the Future

其余可能有用的补充

和已有的普通css共存

不少项目会引入Bootstrap、Materialize等框架,它们是普通的、全局的css。此外,你也可能本身会写一些普通css。如何共存呢?CSS Modules团队成员对此提到过:

a CSS Module should only import information relative to it

意思是,建议把CSS Modules看作一种新的css,和原来的普通css区分开来。好比,composes的时候,不要从那些普通的css里去取。

在css-loader里经过指定testincludeexclude来区分它们。保持CSS Modules的纯净,只有想要应用CSS Modules的css文件,才启用CSS Modules。

只转换class和id

通过我本身的测试,CSS Modules只转换class和id,此外的标签选择符、伪类等都不会被转换。

建议只使用class。

一个CSS Module的输出

简单用console.log()就能够查看CSS Module的输出:

var styles = require("./main.css");
console.log("styles = ", styles);

结果相似这样:

{
    "btn-sp":  "_2SCQ7Kuv31NIIiVU-Q2ubA _2r6eZFEKnJgc7GLy11yRmV",
    title: "_1m-KkPQynpIso3ofWhMVuK"
}

这能够帮助理解CSS Modules是怎样工做的。

预编译器

sass等预编译器也能够用CSS Modules,对应的loader多是这样:

{
    test: /\.scss$/,
    loader: 'style!css?modules!resolve-url!sass?sourceMap'
}

注意不要由于是sass就习惯性地用嵌套写法,CSS Modules并不适合使用包含选择符。

建议的命名方式

CSS Modules会把.title转换为styles.title,因为后者是用在javascript中,所以驼峰命名会更适合。

若是像我以前那样写.btn-sp,须要注意在javascript中写为styles["btn-sp"]

此外,你还能够为css-loader增长camelCase参数来实现自动转换:

{
    test: /\.css$/,
    loader: 'style!css?modules&camelCase',
}

这样即使你写.btn-sp,你也能够直接在javascript里用styles.btnSp

结语

不管是一直以来咱们认真遵循的命名约定,仍是这个新的CSS Modules,目的都是同样的:可维护的css代码。我以为就CSS Modules基本仍是在写css这一点来讲,它仍是很友好的。

虽然本文为了严谨,结果写了至关长的篇幅,但但愿你读过以后,还能以为CSS Modules是简单易懂的。由于这样,我就达成个人目的:扣题,了。

(从新编辑自个人博客,原文地址:http://acgtofe.com/posts/2016/04/css-modules-made-simple

相关文章
相关标签/搜索