Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

css Modules 详解及应用 #1

Open
zxiaohong opened this issue Mar 16, 2018 · 0 comments
Open

css Modules 详解及应用 #1

zxiaohong opened this issue Mar 16, 2018 · 0 comments

Comments

@zxiaohong
Copy link
Owner

zxiaohong commented Mar 16, 2018

CSS Modules 详解及应用

在最近的React项目中,遇到了CSS处理的问题:

  1. 由于是多人开发,各自对样式类的命名规则不统一
  2. 样式全局有效,产生了样式覆盖,不得不重新定义样式类或者使用 !important;

平时的项目开发中,还会有一些类似的CSS问题:

  • 全局污染
  • 命名混乱
  • 依赖引入复杂
  • 无法共享变量
  • 代码冗余

使用CSS模块化可以很好的解决上述问题。目前主要分为两类:

一类是彻底抛弃 CSS,使用 JS 或 JSON 来写样式。Radium, jsxstyle ,react-style 属于这一类。优点是能给 CSS 提供 JS 同样强大的模块化能力;缺点是不能利用成熟的 CSS 预处理器(或后处理器) Sass/Less/PostCSS, :hover 和 :active 伪类处理起来复杂。

另一类是依旧使用 CSS,但使用 JS 来管理样式依赖,代表是CSS Modules。CSS Modules 能最大化地结合现有 CSS 生态和 JS 模块化能力。发布时依旧编译出单独的 JS 和 CSS。它并不依赖于 React,只要你使用 Webpack,可以在 Vue/Angular/jQuery 中使用。

相关概念

overview

CSS Modules是什么

  • CSS Modules 是使所有的类名animation 动画名称默认为局部样式的CSS文件,它既不是官方标准也不是浏览器特性,而是在构建过程中,对CSS类名限定作用域的一种方法。

原理

  • CSS Modules 产生局部作用域的原理是将每个类名编译成独一无二的哈希串

CSS Modules 仅作用于class和id选择器 对其他选择器无效

使用CSS Modules 有什么优势

  • 所有样式都是 local 的,解决了命名冲突和全局污染问题
  • class 名生成规则配置灵活,可以此来压缩 class 名
  • 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
  • 依然是 CSS,几乎 0 学习成本...

启用方法

CSS Modules 通过webpack配置引入项目,不依赖于任何框架,只要使用webpack配置后就可以用于React/Vue/Angular/jQuery 项目中.

  1. 在webpack.config.js的module中添加如下配置:

     module.exports = {
       entry: __dirname + '/index.js',
       output: {
         publicPath: '/',
         filename: './bundle.js'
       },
       module: {
         rules: [
           ...
           {
             test: /\.css$/,
             use:[
                 {loader:'style-loader'},
                 {
                     loader:'css-loader',
                     option:{
                         modules:true
                     }
                 }
             ]
           },
         ]
       }
     };
    

上面代码中,关键的是在style-loader的option里配置option:{modules:true},表示打开 CSS Modules 功能。

使用这种配置方式,css-loader默认将类名编译为唯一的hash串,但不利于class类名的语义化,如:

/*header.css*/
    .root {
        text-align: center;
    }
    .header {
        background-color: #536587;
        height: 150px;
        padding: 20px;
        color: white;
    }
    .title {
        font-size: 1.5em;
    }
    
    /*header.js*/
    
    import React fron 'react';
    import styles from './header.css';
    
    const Header =()=>{
        return (
            <div className={styles.root}>
                <header className={styles.header}>
                	<h1 className={styles.title}>Welcome to React</h1>
                </header>
            </div>
        )
    }
export default Header;

<h1/>将被编译为:<h1 class="_1yHZnBWcll0vdb7BCk5Ufm">Welcome to React</h1>

同时 style.css文件将被编译为

._1yHZnBWcll0vdb7BCk5Ufm {
    font-size: 1.5em;
}

因此,需要在配置webpack时多做一点,定制编译生成的哈希类名:

rules:[
    {
      test:/.css$/,
      use:[
            {loader:'style-loader'},
            {loader:'css-loader',
              option:{modules:true,localIdentName:'[path][name]__[local]-[hase:base64:5]'}
          ]
    }
]

配置localIdentName之后 上面的类名将生成 如下格式:

localidentname

CSS Modules的使用

  • 局部变量和全局变量

    • 局部变量 默认 :local 构建时按照 localIdentName 做规则处理

    • 全局变量 需要声明全局变量时,使用:global,全局变量,编译后类名不变

      .normal {
        color: green;
      }
      
      /* 以上与下面等价 */
      :local(.normal) {
        color: green; 
      }
      
      /* 定义全局样式 */
      :global(.btn) {
        color: red;
      }
      
      /* 定义多个全局样式 */
      :global {
        .link {
          color: green;
        }
        .box {
          color: yellow;
        }
      }
      
  • Compose 组合Class

对于样式复用,CSS Modules 只提供了唯一的方式来处理:composes 组合

复用内部样式
.userInfoBox {
  position: relative;
  padding: 0 24px 0 32px;
  height: 40px;
  color: white;
  line-height: 40px;
  background: #1f1f1f;
  transition: background .2s ease 0s;
}

.docDownloadBox {
  composes: userInfoBox;
  margin-right: 10px;
  border-right: 1px solid #444;
}

使用docDownloadBox类的HTML

 <a className={styles.docDownloadBox}>
    文档&下载
</a>

编译为两个class
composes

复用外部样式
/* settings.css */
.primary-color {
  color: #f40;
}

/* components/Button.css */
.base { /* 所有通用的样式 */ }

.primary {
  composes: base;
  composes: primary-color from './settings.css';
  /* primary 其它样式 */
}

==========

对于大多数项目,有了 composes 后已经不再需要 Sass/Less/PostCSS。但如果你想用的话,由于 composes 不是标准的 CSS 语法,编译时会报错。就只能使用预处理器自己的语法来做样式复用了。

  • 层叠多个class

配合classnames使用

npm install --save classnames

直接使用
/* components/submit-button.js */
import React, { Component } from 'react';
import classNames from 'classnames';
import styles from './submit-button.css';

export default class SubmitButton extends Component {
  render () {
    let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';
    let className = classNames({
      [`${styles.base}`]: true,
      [`${styles.inProgress}`]: this.props.store.submissionInProgress,
      [`${styles.error}`]: this.props.store.errorOccurred,
      [`${styles.disabled}`]: this.props.form.valid,
    });
    return <button className={className}>{text}</button>;
  }
};
使用 classnames/bind
/* components/submit-button.js */
import React, { Component } from 'react';
import classNames from 'classnames/bind';
import styles from './submit-button.css';

let cx = classNames.bind(styles);

export default class SubmitButton extends Component {
  render () {
    let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';
    let className = cx({
      base: true,
      inProgress: this.props.store.submissionInProgress,
      error: this.props.store.errorOccurred,
      disabled: this.props.form.valid,
    });
    return <button className={className}>{text}</button>;
  }
};
  • 定义和使用变量

CSS Moduels 本身没有变量的概念,如果需要使用变量,要借助预处理器/后处理器。

定义变量

安装 PostCSS 和 postcss-icss-values

  npm install --save postcss-loader postcss-icss-values

把postcss-loader加入webpack.config.js。

{
  test: /\.css$/,
  use: [
    require.resolve('style-loader'),
    {
      loader: require.resolve('css-loader'),
      options: {
        importLoaders: 1,
        modules:true,
        localIdentName:'[name]--[local]--[hash:base64:5]'
      } 
    },
    {
      loader: require.resolve('postcss-loader'),
      options: {
        ident: 'postcss',
        plugins: () => [
          require('postcss-flexbugs-fixes'),
          autoprefixer({
            browsers: [
              '>1%',
              'last 4 versions',
              'Firefox ESR',
              'not ie < 9', // React doesn't support IE8 anyway
            ],
            flexbox: 'no-2009',
          }),
          require('postcss-icss-values')
        ],
      },
    },
  ],
},

然后,定义变量,如variable.css

@value primary  #0c77f8;
@value green    #aaf200;
/*变量和值之间 写不写冒号都可以,但如果和sass配合使用,不要写分号
一次只定义一个变量
*/
在其他css文件中使用变量,

App.css中引入变量

@value variables: "./variable.css";
@value primary from variables;

//或者直接 @value primary from "variables.css"

.title {
  background-color: primary;
}

注意:red,blue,pink,green等颜色不会被当做变量处理

css和js共享变量
//variable.css

@value primary #0c77f8;

//App.js
import { primary } from './variable.css';
console.log(primary); // -> #0c77f8
  • 模块化样式和全局样式共存

webpack.conf.js中添加exclude/include配置,定义不同的解析规则即可

  // webpack.config.js 局部
  {
    test: /\.css$/,
    exclude:/common.css/,
    use: [
      require.resolve('style-loader'),
      {
        loader: require.resolve('css-loader'),
        options: {
          importLoaders: 1,
          modules:true,
          localIdentName:'[name]--[local]--[hash:base64:5]'
        } 
      },
      {
        loader: require.resolve('postcss-loader'),
        options: {
          // Necessary for external CSS imports to work
          // https://github.com/facebookincubator/create-react-app/issues/2677
          ident: 'postcss',
          plugins: () => [
            require('postcss-flexbugs-fixes'),
            autoprefixer({
              browsers: [
                '>1%',
                'last 4 versions',
                'Firefox ESR',
                'not ie < 9', // React doesn't support IE8 anyway
              ],
              flexbox: 'no-2009',
            }),
            require('postcss-icss-values')
          ],
        },
      },
    ],
  },
  {
    test:/\.css$/,
    include:/common.css/,
    use:['style-loader','css-loader','postcss-loader']
  },

设置为全局规则的css类,不会被模块化处理。

  • 使用babel-plugin-react-css-modules

babel-plugin-react-css-modules 可以实现使用styleName属性自动加载CSS模块。只需要把className换成styleName即可获得CSS局部作用域的能力,babel插件来自动进行语法树解析并最终生成className。改动成本极小,不会增加JSX的复杂度,也不会给项目带来额外的负担。

import React from 'react';
import './table.css';

  export default class Table extends React.Component {
    render () {
      return <div styleName='table'>
        <div styleName='row'>
          <div styleName='cell'>A0</div>
          <div styleName='cell'>B0</div>
        </div>
      </div>;
    }
  export default Table;

使用babel-plugin-react-css-modules:

  1. 不必使用驼峰命名;
  2. 不用使用styles Object
  3. 可以和全局变量自由组合;

<div className='global-css' styleName='local-module'></div>

参考文献:

  1. CSS Modules 详解及 React 中实践
  2. CSS Modules 用法教程 - 阮一峰的网络日志
  3. CSS Modules 入门及 React 中实践 | AlloyTeam
  4. GitHub - css-modules/css-modules: Documentation about css-modules
  5. GitHub - css-modules/postcss-icss-values: Pass arbitrary constants between your module files
  6. GitHub - gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant