[前端项目创新]自己写了个loader解决引入CSS导致的样式冲突

1,471 阅读4分钟

场景描述

自己在做数个项目时,经常会遇到CSS冲突的问题。主要表现在:

  1. 项目一开始就全局引入CSS库。然后自己写样式时,需要考虑到与第三方CSS库同名的类的样式会受到污染,因此要针对被污染的属性进行重写覆盖。有时还会因为该类的权重--Specificity不高而导致覆盖不了,要强行!important覆盖,非常心累。

  2. 项目已开发一段时间后,因为新增需求需要引入第三方CSS库。但由于引入的CSS库中存在与项目本身的样式同名的类。所以也会导致样式污染。

其实网上对以上的内容早有解决方法,例如在开发时,坚持使用BEM的类名规范。

但针对那些不使用BEM规范的项目,或者没有一套针对性的命名规范的项目,容易引起冲突,所以自己操起键盘写了一个loader。

命名空间

以命名空间为前缀的类名的确可以解决因类名重复而引起的样式冲突问题。

有些第三方CSS库也使用命名空间,例如MUI,以mui作为命名空间,如下所示:

<ul class="mui-table-view"> 
  <li class="mui-table-view-cell mui-collapse">
    <a class="mui-navigate-right" href="#">面板1</a>
    <div class="mui-collapse-content">
      <p>面板1子内容</p>
    </div>
  </li>
</ul>

但很多第三方库是没有命名空间的。如bootstrap,如下所示:

<div class="container">
  <h1>Hello, world!</h1>
  <div class="row" >
    <div class="col-md-6 col-md-offset-3">
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
    </div>
  </div>
</div>

因此,每次引入类似bootstrap的CSS样式,都要考虑到自身项目的类名命名的情况。

css-namespacing-loader

因此,为了完美解决上述问题,我写了css-namespacing-loader

以下内容都来自css-namespacing-loader的README_CN.md

这个loader主要有两个功能:

  • 它用于避免因第三方CSS库的引入导致的全局样式污染。

  • 在开发过程中,编译CSS代码时,它会根据配置自动将命名空间添加到指定的类名中。

快速入门

一开始,你要通过npm下载 css-namespacing-loader:

$ npm install css-namespacing --save-dev

然后把loader添加到 webpack 配置中。例如:

entry.js

import 'bootstrap/dist/css/bootstrap.min.css'

webpack.config.js

module.exports = {
  module: {
    rules:[
      {
        test: /\.css$/i,
        use: [
          "style-loader",
          "css-loader",
          {
            loader:"css-namespacing-loader",
            options:{
              namespace:[
                { value: 'bsp-', path: [/bootstrap/] }
              ]
            }
          }
        ]
      }
    ]
  }
}

在通过你偏好的方式运行webpack之后,当你使用bootstrap里面的样式时,将会以 bsp-container 替代 container,例如:

<div class="bsp-container">
  <div class="bsp-row">
    <div class="bsp-col-sm">
      One of three columns
    </div>
    <div class="bsp-col-sm">
      One of three columns
    </div>
    <div class="bsp-col-sm">
      One of three columns
    </div>
  </div>
</div>

options中, 你可以设置哪些类名不需要被添加命名空间,或者只有哪些类名需要被添加命名空间,以及命名空间的值。

另外,在CSS代码中,你可以使用@namespacing注解灵活地设置以上配置。如果你想了解更多,请查看这里

如果你想查看关于处理后的结果,请阅读css-namespacing.

Options配置

NameTypeDefaultNecessaryDescription
namespace{Array}undefinedtrue包含多个配置的数组

namespace

Type: Array Default: undefined

namespace数组中的元素是一个对象,它包含以下属性。

NameTypeDefaultNecessaryDescription
path{Array<String/RegExp>}undefinedfalse包含要添加命名空间的CSS文件的匹配路径的数组
value{String}undefinedfalse要添加前缀的名称空间的值
not{Array<RegExp>}undefinedfalse包含着不会被添加命名空间的类名的数组
only{Array<RegExp>}undefinedfalse包含着只会被添加命名空间的类名的数组

path

Type: {Array<String|RegExp>} Default: undefined

1.在数组中使用正则表达式

例如:

options:{
  namespace:[
    { value: 'bsp-', path: [/bootstrap/] }
  ]
}

它会通过 Regexp.prototype.testpath.test(filepath)的方式找到匹配的文件

2.在数组中使用字符串

例如:

options:{
  namespace:[
    { value: 'bsp-', path: [path.resolve(___dirname,'./node_modules/bootstrap/dist/css/bootstrap.min.css']) }
  ]
}

他会通过 String.prototype.includesfilepath.includes(path)的方式找到匹配的文件

注意:由于windows和linux之间的文件分隔符不同,所以最好使用path.resolve去获取文件路径。

3.path没有定义

例如:

options:{
  namespace:[
    { 
      value: 'my-',  
    }
  ]
}

这种设置下,所有被扫描的CSS文件里的类名都被添加名称空间。.

value

Type: {String} Default: undefined

若该值为undefined,例如:

options:{
  namespace:[
    { 
      path:[/bootstrap/]
    }
  ]
}

这种配置下,被扫描到的文件不会有任何处理。

not

Type:{Array<RegExp>} Default:undefined

For example:

options:{
  namespace:[
    { 
      path:[/bootstrap/],
      not:[/^box$/]
    }
  ]
}

这种配置下,在被扫描的CSS文件中,所有名为box的类名都不会被添加命名空间。

only

For example:

options:{
  namespace:[
    { 
      path:[/bootstrap/],
      not:[/^box$/]
    }
  ]
}

这种配置下,在被扫描的CSS文件中,只有名为box的类名才会被添加命名空间。

后记

自己后来还给该loader添加@namespacing注解,以达到编写css,以达到灵活修改命名空间的目的。不过可能对于很多人来说,该loader只用于给第三方CSS添加命名空间防止污染。所以这方面我就先不在本文介绍了。有兴趣可以看这里