CSS Module

4,635 阅读6分钟

开发过程中,我们遇到样式冲突如何解决

  1. 增加CSS样式选择器的深度,提高样式的权重;
  2. 使用!important提高所要生效样式的权重;
  3. 给每个样式都起与众不同的名字,保证不会出现重名;
  4. 替当前模块设置一个nameSpace和其他模块进行区分;

上面四种方式1跟2本质上是对样式进行覆盖,3跟4 是从根本上避免了样式覆盖的可能性。

CSS Module就是采用了第3种方法。在最终构建页面时会自动重命名class,保证全部的class都不会重名。

什么是CSS Module

官方文档:

CSS files in which all class names and animation names are scoped locally by default.

CSS Modules不是一个官方的规范,也不是浏览器的一种机制,它是一种构建步骤中的一个进程。(构建通常需要 webpack 或者browserify的帮助)。通过构建工具的帮助,可以将 class的名字或者选择器的名字作用域化。(类似命名空间化)。

CSS Module只是加入了局部作用域和模块依赖,这恰恰是网页组件最急需的功能。可以保证某个组件的样式,不会影响到其他组件。

CSS Module的背景

项目开发中模块指的是可组合、分解和更换的单元。我们把代码按照一定的规则和逻辑拆分,分解成可组合可更换的单元,最大限度的实现代码复用。在CSS中除了代码复用更重要的是解决局部作用域的问题,也就是为了避免全局样式污染

CSS Module解决了哪些问题?

全局污染会带来一系列混乱的问题,比如在项目中已经定义了某一个元素的样式,但是现在有一个需求是这个元素的样式要重新定义,但是全局已经定义了,这个时候我们可以有几种选择:!important(加上一个important优先级)、inline(写一个行内样式)或者写一个复杂度选择器。

随着项目的越来越大,越来越难以维护,就容易导致命名的混乱。

为了避免样式名的冲突,我们写的选择器也越来越复杂,然后命名也越来越长。这时如果没有一个样式的命名规范,代码将越来越难以维护。这样下去就容易导致代码的层次结构越来越不清晰。我们想要实现一个代码的复用也会也来越难。从成千上万的代码中找到自己想要复用的样式,这是有一定难度的。而且因为选择器的越来越复杂和命名越来越长导致了代码的压缩也就不彻底了,对于长的class名是无能为力的,因为要保证类名的唯一性。

  • 全局污染
  • 命名混乱
  • 层次结构不清晰
  • 代码难以复用
  • 代码压缩不彻底

嵌套层次过深的选择器

为了解决全局样式的冲突问题,就不得不引入一些特地命名namespac来区分scope,但是往往有些namespace命名得不够清晰,就会造成要想下一个样式不会覆盖,就要再加一个新的namespace来进行区分,最终可能一个元素最终的显示样式类似如以下

.wrap .table .row .cell .content .header .title {
  padding: 10px;
  font-weight: 700;
}

在上一个元素的显示上使用了7个选择器,会有以下问题:

  • 根据CSS选择器的解析规则可以知道,层级越深,比较的次数也就越多。当然在更多的情况下,可能嵌套的层次还会更深,另外,这里单单用了类选择器,而采用ID选择器的时候,可能对整个网页的渲染影响更重。

  • 增加了不必要的字节开销

  • 语义混乱,当文档中出现过多的content、title以及item这些通用的类名时,你可能要花上老半天才知道它们到底是用在哪个元素上

  • 可扩展性不好,约束越多,扩展性越差,可能会因为需求改变而大面积的重写样式文件

    CSS的渲染规则可以参看这篇文章探究 CSS 解析原理

CSS Module如何使用?

CSS Modules的使用在不同框架里大同小异。今天主要说一下在Vue里面CSS Module的使用方法

CSS Module需要借助构建工具来使用。那么,Vue CLI的构建工具是Webpack。版本:@vue/cli 4.4.1

  1. 在vue.config.js中配置 CSS Module
// css-loader升级v3后使用css.requireModuleExtension代替css.modules
    css: {
        // 是否使用css分离插件 ExtractTextPlugin
        extract: true,
        // 开启 CSS source maps
        sourceMap: true,
        requireModuleExtension: true
    },
  1. 配置完后,我们就可以在组件内使用CSS Module了。使用方法如下:
/* 在 <style> 上添加 module 特性 */
<style module>
.shapes__title {
    color: #000;
    margin-top: 10px;
}
</style>

关于module的作用,vue官方文档解释如下:

这个 module 特性指引 Vue Loader 作为名为 $style 的计算属性,向组件注入 CSS Module局部对象。然后你就可以在模板中通过一个动态类绑定来使用它了。

在模板中使用CSS Modules声明的class:

<template>
  <div class="wrap">
    <!-- 使用 CSS Module 声明的样式 -->
    <h2 :class="$style.shapes__title">CSS Module</h1>
  </div>
</template>
  1. CSS Module的一些基本语法:

CSS Modules分局部作用域和全局作用域。

两者的区分是通过:local() 与:global()来设定的。因为CSS Module默认的是局部作用域,所以,:local()默认省略。因此,上面例子的CSS部分也可以这样写:

<style module>
:local(.shapes__title) {
    color: #000;
    margin-top: 10px;
}
</style>

如果要某个样式暴露给全局则需要使用:global():

<style module>
/* 使用 CSS Modules */
.shapes__title {
    color: #000;
    margin-top: 10px;
}
:global(.title){
  color: #333;
}
</style>

需要注意的是,:global()修饰的样式是不会被重命名的,使用全局样式时直接赋值给class就行了,不需要进行类绑定。

<template>
  <div class="hello">
    <!-- 使用 CSS Module 声明的样式 -->
    <h2 :class="$style.shapes__title">CSS Module</h1>
    <h2 class="title">使用:global声明的样式</h2>
  </div>
</template>

<style module>
/* 使用 CSS Modules */
.shapes__title {
    color: #000;
    margin-top: 10px;
}
:global(.title){
  color: #333;
}
</style>

composes:Class 的组合

CSS Module可以将两个选择器的样式组合在一起。也就是说,一个选择器可以继承另一个选择器的样式。通过composes来实现。composes两者来源的class:

  • 组合当前样式表的class
.shapes__title {
    color: #000;
    margin-top: 10px;
}
.box{
    font-size: 16px;
    composes: shapes__title;
}
  • 组合导入样式表的class
// common.less
.title {
    font-size: 14px;
    font-weight: 700;
    color: #333333;
}
.box__title {
    font-size: 16px;
    composes: title from '../assets/styles/common.less';
}

在组件中的使用.box .box__title

<template>
  <div class="hello">
    <!-- 使用 CSS Modules 声明的样式 -->
    <h2 class="title">你好</h2>
    <h3 :class="$style.box">composes: class 组合当前样式表的class</h3>
    <h3 :class="$style.box__title">composes: class 组合导入样式表的class</h3>
  </div>
</template>

注意:css module中,不能在子选择器中使用compose github.com/css-modules…

css module作者在issues/261中提到“Composition works differently to mixins. It does not mutates rules, just concatenates names.”

设置重命名规则

CSS Module会自动重命名class。开发过程中我们可以对重命名设定规则

规则的设定通过配置文件实现:

    // css-loader升级v3后使用css.requireModuleExtension代替css.modules
    css: {
        // 是否使用css分离插件 ExtractTextPlugin
        extract: true,
        // 开启 CSS source maps
        sourceMap: true,
        // 因为配置了loaderOptions.css, 尽管requireModuleExtension的值为默认值,我们也需要指出
        requireModuleExtension: true,
        loaderOptions: {
            css: {
                modules: {
                    // 格式化类名:name是当前文件名称,local是当前定义的类名,hash是hash生成的字符串,长度为5
                    localIdentName: '[local]_[hash:base64:5]'
                }
            }
        }
    }

一些建议

CSS Modules 是对现有的 CSS 做减法。 为了追求简单可控,建议遵循如下原则:

  1. CSS Module只转换class和id,此外的标签选择符、伪类等都不会被转换。建议只使用class。
  2. 让 CSS 的优先级保持相对扁平,不层叠多个 class,只使用一个 class 把所有样式定义好
  3. 所有样式通过 composes 组合来实现复用
  4. 不嵌套,CSS Module 不适合使用包含选择符

建议只是建议,CSS Modules 并不强制你一定要这么做。具体的根据实际情况来

参考资料

  1. CSS Modules入门教程
  2. Vue CLI CSS Modules
  3. vue开发配置之CSS Module