css模块化方案

628 阅读7分钟

什么是css模块化

模块化的意思是将一个复杂问题自顶向下逐层分成若干模块的过程。换句话说就是把一个大功能拆解成小功能。

css发展

css随着时代的发展也发生了很大的转变,主要经历了以下几个历程

  1. 手写原生CSS 手写原生css指的是在html文件中写行内样式,内嵌样式,link引入外部样式,@import导入样式的时代,那时候主要还是使用jquery,操作DOM的时代。
  2. 使用预处理器SASS/LESS 后来人们发现手写原生CSS的时候很多样式中的属性值不能复用,为了定位某个元素需要写长长的一段选择器,于是有了SASS/LESS,它支持变量,也支持嵌套,给开发人员提供了一种用js的思维去写css样式的方式。
  3. 使用后处理器postCSS

PostCSS是一个用JS插件转换CSS的工具。这些插件可以支持变量和mixin,转换未来的CSS语法,内联图片等等。

使用预处理器SASS的时候必须安装node-sass,而node-sass是基于webpack来安装的,这种安装方式特别慢,而且sass会污染全局变量,也不支持未来的css。因此出现了PostCSS。它具有以下几个方面的功能

  • 使用下一代css语法
  • 自动补全浏览器前缀
  • 自动把px转成rem
  • css代码压缩

这些功能都是基于安装PostCSS相关的插件就可以实现,PostCSS只是一个工具,本身不会对css进行操作,因此对css是没有影响的。

  1. 使用css modules css modules是我们现在开发大部分都在用的方式,将css拆解成很多小的模块,这样就可以复用,方便代码的维护和处理。下面会重点介绍css模块化

  2. 使用css in js css in js的思想是用js对象来描述样式,而不是css样式表。因为我们现在在写前端页面的时候基本上都是使用的框架,如vue.js或者react.js。目前国内使用的都是这两个框架。里面的页面都是一个个的组件构成的。使用css in js有利于组件的隔离,将展示组件和逻辑组件分离开。

不使用css模块化存在的问题

现在项目都是团队开发的,当代码耦合在一起,代码量特别大的时候,很不好维护。因此就提出了css模块化的思想。 以下是几个不使用css模块化存在的问题:

  1. css全局作用域污染
  2. js和css之间无法共享变量
  3. vue.js里面虽然可以给样式里面定义一个scoped,变成组件化,但是对于css模块化没有一个统一的方案

css模块化的作用

  • 提高代码重用率
  • 降低耦合
  • 提高开发效率、减少沟通成本
  • 易于维护

css模块化方案

要实现一套css模块化方案,主要可以围绕三个方面去进行:命名, css module, css in js.

命名

良好的命名可以解决css全局作用域被污染的问题。我们常用的一种命名方式是BEM,当然除了BEM,还有其他的css命名方法论,比如OOCSS, SMACSS, ITCSS, SUITCSS, Atomic CSS等。

BEM

它的命名规矩就是block-name__element-name--modifier-name,也就是.模块名__元素名--修饰器名。

<div class='test'>
  <div class="test__header">我是头部元素</div>
  <div class="test__body">
    <div class="test__body--item">我是内容</div>
  </div>
  <div class="test__footer">我是底部盒子</div>
</div>

使用sass/less/stylus的父元素选择器&可以更高效的编写BEM

.test {
  &__header {}
  &__body {
    $--item {}
  }
  &__footer {}
}

OOCSS

OOCSS(Object-Oriented CSS)即面向对象的 CSS,它借鉴了 OOP(面向对象编程)的抽象思维,主张将元素的样式抽象成多个独立的小型样式类,来提高样式的灵活性和可重用性。 它遵循两个基本原则

  • 独立的结构和样式:不要将定位,尺寸等布局样式和字体、颜色等表现样式写在一个选择器中
  • 独立的容器和内容:避免对位置的依赖,子元素即使离开了容器也能正确显示
<div class="size1of4 bgBlue solidGray mt-5 ml-10 mr-10 mb-10"></div>
.size1of4 { width: 25%; }
.bgBlue { background: blue; }
.solidGray { border: 1px solid #ccc; }
.mt-5 { margin-top: 5px; }
.mr-10 { margin-right: 10px }
.mb-10 { margin-bottom: 10px; }
.ml-10 { margin-left: 10px; }

这种方式要合理应用,不然会导致出现太多类名。

SMACSS

SMACSS按照部件的功能特性,将其划分为五类

  • 基础:定义html元素的默认样式。不需要前缀
  • 布局:布局将页面分为几部分,可作为高级容器包含一个或多个模块。前缀是l-或者layout-
  • 模块:对象或块,是可重用的模块化部分。前缀是m-或模块的名字
  • 状态:任意模块或布局在特定状态下的外观。前缀是is-
  • 主题:也就是换肤,描述了页面的外观。前缀是theme-
<form class="layout-grid">
  <div class="field">
    <input type="search" id="searchbox" />
    <span class="msg is-error">There is an error!</span>
  </div>
</form>

ITCSS

它兼容BEM, OOCSS, SMACSS关于选择器的命名方法论。使用分层的思想来管理我们的css文件。这种思想其实在我们实际项目开发中经常会用到。 ITCSS将css的样式规则划分为以下几个层次

  1. settings: 这个层次包含项目中用到的变量,比如颜色,字体大小
  2. tools: 这个层次包含项目中使用的mixins和functions
  3. generic: 这个层次是最基本的设定,比如reset.css,normalize.css
  4. base: 这个层次包含最基础的元素。比如img, link, p
  5. Objects: 这个层次包括设计模式。 比如水平居中,垂直居中,多列布局
  6. components: 这个层次包括UI组件,比如button, switch, slider
  7. trumps: 这个层次用于辅助和微调的样式

css modules

前面介绍的这些基于命名和分文件都是我们以前经常会使用的,但是随着自动化的环境下,这种方式已经不是那么适用了,因此出现了css modules的概念。 css modules允许我们像import js Module一样可以import css Module。每一个css Module都是一个独立的模块,每一个类名都是该模块导出对象的一个属性,并且css modules在打包时会自动将id和class加上全局唯一的hash值,从而避免命名冲突的问题。

css modules具有以下几个主要特性

  • 作用域:模块中的名称默认都属于本地作用域,定义在 :local 中的名称也属于本地作用域,定义在 :global 中的名称属于全局作用域,全局名称不会被编译成哈希字符串。
  • 命名:对于本地类名称,CSS Modules 建议使用 camelCase 方式来命名,这样会使 JS 文件更干净,即 styles.className。
  • 组合:使用 composes 属性来继承另一个选择器的样式,这与 Sass 的 @extend 规则类似。
  • 变量:使用 @value 来定义变量,不过需要安装 PostCSS 和 postcss-modules-values 插件。
/* style.css */
:global(.card) {
  padding: 20px;
}
.article {
  background-color: #fff;
}
.title {
  font-size: 18px;
}

// App.js
import React from 'react'
import styles from './style.css'

export default function App() {
  return (
    <article className={styles.article}>
      <h2 className={styles.title}>Hello World</h2>
      <div className="card">Lorem ipsum dolor sit amet.</div>
    </article>
  )
}

编译结果

<style>
  .card {
    padding: 20px;
  }
  .style__article--ht21N {
    background-color: #fff;
  }
  .style__title--3JCJR {
    font-size: 18px;
  }
</style>
<article class="style__article--ht21N">
  <h2 class="style__title--3JCJR">Hello World</h2>
  <div class="card">Lorem ipsum dolor sit amet.</div>
</article>

可以看出,其实编译过程也是使用了BEM的命名方法。

在vue项目中使用css modules

在vue项目中,实现css模块化有两种方式。vue loader支持scoped css和css modules。 scoped css是vue loader默认支持的,不需要额外配置和安装。直接在.vue文件中的就可以使用了。这个我们应该很熟悉了。添加scoped意味着当前style里面的样式只能在当前组件作用域中使用,其他的组件是没法使用这个样式。 css modules被vue loader视为scoped css的替代方法,需要加一些配置才能生效。

// vue.config.js
css: {
  // 开启CSS Modules
  requireModuleExtension: true;
  loaderOptions: {
    // 向loader传递配置选项
    css: {
      modules: {
        // 自定义类命名规则
        // 在我们的项目中,对类的命名是根据环境不同而改变的,开发环境中会展示该类的文件路径方便调试,其他环境中会                      使用hash值封装
        localIdentName: process.env.NODE_ENV === 'development' ? '[path][name]__[local]' :                          '[hash:base62:8]',
      }
    }
  }
}

在vue项目中使用

<h1 :class="$style.colorBlack">我是标题1</h1>
<h2 :class="$style.colorBlue">我是标题2</h2>
<h3 :class="$style.colorRed">我是标题3</h3>

<style module>
  .colorRed {
    color: red;
  }
  .colorBlue {
    color: blue;
  }
  .colorBlack {
    color: rgb(19, 18, 18);
  }
</style>

css modules还有一些其他的特性,比如:composes, :global选择器, :local选择器等等,感兴趣的可以查看官网

scoped css 和 css modules的比较

既然谈到了css modules是scoped css的替代方案,那我们就来介绍一下它们之间的区别 Vue Loader默认使用CSS后处理器PostCSS来实现Scoped CSS,原理就是给声明了scoped的样式中选择器命中的元素添加一个自定义属性,再通过属性选择器实现作用域隔离样式的效果。注意通过v-html创建的DOM内容是不受Scoped CSS控制的,如果希望修改其中的样式,可以通过深度作用选择器。 使用深度选择器的方式

  • /deep/: 已废弃
  • >>>: 不使用sass预处理器的时候生效
  • ::v-deep: 使用sass预处理器的时候生效

scoped css 和 css modules的区别

CSS ModulesScoped CSS
需要在vue.config.js中额外配置Vue Loader默认支持,无需额外配置
通过根据配置的类命名规则,为元素生成独一无二的类名来实现作用域隔离通过给元素自定义hash属性,再使用属性选择器选中元素来实现作用域隔离
在style标签中声明module在style标签中声明scoped
支持导入其他module的样式,支持样式组合/
通过:global()来解除作用域的隔离,使样式在全局生效1. 可以定义全局样式,使样式不受作用域约束;2. 可以通过深度作用选择器命中子组件,从而控制子组件的样式

css in js

css in js是把css写在js里的一种模式,类似于jsx(把html写在js里,html in js)。这些方式看起来好像和以前所说的结构,样式, JS要分离(关注点分离)的理念好像不是很符合。其实不是的,这个要围绕当前技术环境来说。因为现在基于vue.js和react.js框架开发项目的时候都是组件化开发,这样可以实现组件复用。慢慢地这种方式就变成了一种主流。 这种css in js的方式在react中使用的比较多,在vue中用的比较少,因为在vue中每一个.vue文件中就可以写一个单独的style了,已经实现了相对成熟的组件化了。 现在css in js的库在市面上有很多,比较推荐使用的是styled-components库。这个组件库目前只能使用在react项目中,如果想要在vue项目中使用类似的库,可以使用vue-styled-components,这是专门为vue打造的一款css in js 库。

styled-components的使用

  1. 安装
npm install --save styled-components
  1. 基本使用
// styles.js
import styled, { css } from 'styled-components'

// 创建一个名为 Wrapper 的样式组件 (一个 section 标签, 并带有一些样式)
export const Wrapper = styled.section`
  padding: 10px;
  background: deepskyblue;
`

// 创建一个名为 Title 的样式组件 (一个 h1 标签, 并带有一些样式)
export const Title = styled.h1`
  font-size: 20px;
  text-align: center;
`

// 创建一个名为 Button 的样式组件 (一个 button 标签, 并带有一些样式, 还接收一个 primary 参数)
export const Button = styled.button`
  padding: 10px 20px;
  color: #333;
  background: transparent;
  border-radius: 4px;

  ${(props) => props.primary && css`
    color: #fff;
    background: blue;
  `}
`

// App.js
import React from 'react'
import { Wrapper, Title, Button } from './styles'

// 然后,像使用其他 React 组件一样使用这些样式组件
export default function App() {
  return (
    <Wrapper>
      <Title>Hello World, this is my first styled component!</Title>
      <Button>Normal Button</Button>
      <Button primary>Primary Button</Button>
    </Wrapper>
  )
}
  1. 高级使用
  • 可以通过插值的方式给样式组件传递参数(props),这在需要动态生成样式规则时特别有用。
  • 可以通过构造函数 styled() 来继承另一个组件的样式。
  • 使用 createGlobalStyle 来创建全局 CSS 规则。
  • styled-components 会为自动添加浏览器兼容性前缀。
  • styled-components 基于 stylis(一个轻量级的 CSS 预处理器),你可以在样式组件中直接使用嵌套语法,就像在 Sass/Less/Stylus 中的那样。
  • 强烈推荐使用 styled-components 的 Babel 插件 babel-plugin-styled-components(当然这不是必须的)。它提供了更好的调试体验的支持,比如更清晰的类名、SSR 支持、压缩代码等等。
  • 你也可以在 Vue 中使用 styled-components,vue-styled-components,不过好像没人会这么做~
  • 默认情况下,模版字符串中的 CSS 代码在 VSCode 中是没有智能提示和语法高亮效果的,需要安装 扩展。

参考官方文档

vue-styled-components的使用

  1. 安装
npm i -S vue-styled-components
  1. 基本使用
// Flex.js
import styled from 'vue-styled-components'

const Flex = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
`

export {
    Flex
}

// App.vue
<template>
  <flex>
    <p>styled</p>
    <span>components</span>
  </flex>
</template>

<script>
import { Flex } from '@/components/Flex'

export default {
    name: 'child1',
    components: {
        Flex
    }
}
</script>
  1. 高级使用

参考官方文档