或者能够这么说,CSS Modules为咱们解决了什么痛点。针对以往我写网页样式的经验,具体来讲能够概括为如下几点:javascript
过程是这样的:你如今有两个模块,分别为A、B,你可能会单独针对这两个模块编写本身的样式,例如a.css、b.css,看一下代码css
// A.js import './a.css' const html = '<h1 class="text">module A</h1>' // B.js import './b.css' const html = '<h1 class="text">module B</h1>'
下面是样式:html
/* a.css */ .text { color: red; } /* b.css */ .text { color: blue; }
导入到入口APP中前端
// App.js import A from './A.js' import B from './B.js' element.innerTHML = 'xxx'
因为样式是统一加载到入口中,所以实际上的样式合在一块儿(这里暂定为mix.css)显示顺序为:java
/* mix.css */ /* a.css */ .text { color: red; } /* b.css */ .text { color: blue; }
根据CSS的Layout规则,所以后面的样式会覆盖掉前面的样式声明,最终有效的就是text
的颜色为blue
的那条规则,这就是全局样式覆盖,同理,这在js
中也一样存在,所以就引入了模块化,在js中能够用当即执行函数表达式来隔离出不一样的模块node
var moduleA = (function(document, undefined){ // your module code })(document) var moduleB = (function(document, undefined){ // your module code })(document)
而在css中要想引入模块化,那么就只能经过namespace
来实现,而这个又会带来新的问题,这个下面会讲到webpack
为了解决全局样式的冲突问题,就不得不引入一些特意命名namespace
来区分scope
,可是每每有些namespace
命名得不够清晰,就会形成要想下一个样式不会覆盖,就要再加一个新的namespace
来进行区分,最终可能一个元素最终的显示样式相似如如下:git
.widget .table .row .cell .content .header .title { padding: 10px 20px; font-weight: bold; font-size: 2rem; }
在上一个元素的显示上使用了7个选择器,总结起来会有如下问题:github
content
、title
以及item
这些通用的类名时,你可能要花上老半天才知道它们究竟是用在哪一个元素上【注】CSS的渲染规则能够参看这篇文章探究 CSS 解析原理web
因为CSS不能使用相似于js的模块化的功能,可能你在一个css文件中写了一个公共的样式类,而你在另一个css也须要这样一个样式,这时候,你可能会多写一次,相似于这样的
/* a.css */ .modal { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 1; background-color: rgba(0, 0, 0, 0.7); } .text { color: red; } /* b.css */ .modal { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 1; background-color: rgba(0, 0, 0, 0.7); } .text { color: blue; }
那么在合并成app.css的时候,就会被编写两遍,虽然样式不会被影响,可是这样实际上也是一种字节浪费,固然,上述的这种状况彻底是能够经过公用全局样式来达到目的,可是,这种代码重复一般是在不知情的状况下发生的。
针对上述的一些问题,也有一些解决方案,具体以下:
Sass,Less的用法这里再也不赘述,若是不清楚,能够本身查阅相关资料去了解一下。
CSS预处理器最大的好处就是能够支持模块引入,用js的方式来编写CSS,解决了部分scope
混乱以及代码冗余的问题,可是也不能彻底避免。同时,也没有解决全局样式的冲突问题
一个SASS
的的文件是这样的:
/* app.sass */ @import './reset' @import './color' @import './font'
能够实际上编译以后,终究仍是一个文件,所以不可避免的会出现冲突样式
There are only two hard problems in Computer Science: cache invalidation and naming things — Phil Karlton
BEM
就是为了解决命名冲突以及更好的语义化而生的。
Block:逻辑和页面功能都独立的页面组件,是一个可复用单元,特色以下:
[可选]定义Block和Element的外观及行为,就像HTML属性同样,能让同一种Block看起来不同
Block
做为最小的可复用单元,任意嵌套不会影响功能和外观,命名能够为header
、menu
等等
<style> .header { color: #042; } </style> <div class="header">...</div>
Element
依附Block存在,没有单独的含义,命名上语义尽可能接近于Block,好比title
、item
之类
<style> .header { color: #042; } .header__title { color: #042; } </style> <div class="header"> <h1 class="header__title">Header</h1> </div>
Modifier
是一个元素的状态显示,例如active
、current
、selected
<style> .header--color-black { color: #000; } .header__title--color-red { color: #f00; } </style> <div class="header header--color-black"> <h1 class="header__title"> <span class="header__title--color-red">Header</span> </h1> </div>
【说明】
Block__Element-father__Element-son_Modifer
这种类名的写法,BEM只有三级<form class="form form--theme-xmas form--simple"> <input class="form__input" type="text" /> <input class="form__submit form__submit--disabled" type="submit" /> </form>
.form { } .form--theme-xmas { } .form--simple { } .form__input { } .form__submit { } .form__submit--disabled { }
参考连接:
BEM解决了模块复用、全局命名冲突等问题,配合预处理CSS使用时,也能获得必定程度的扩展,可是它依然有它的问题:
说了这么多,终于要到正文了
根据CSS Modules的repo上的话来讲是这样的:
CSS files in which all class names and animation names are scoped locally by default.
因此CSS Modules并非一个正式的声明或者是浏览器的一个实现,而是经过构建工具(webpack or Browserify)来使全部的class达到scope的一个过程。
/* App.css */ .text { color: red; } /* 编译以后多是这样的 */ .App__text___3lRY_ { color: red; }
命名惟一,所以保证了全局不会冲突。
可使用composes
来引入自身模块中的样式以及另外一个模块的样式:
.serif-font { font-family: Georgia, serif; } .display { composes: serif-font; font-size: 30px; line-height: 35px; }
应用到元素上能够这样使用:
import type from "./type.css"; element.innerHTML = `<h1 class="${type.display}"> This is a heading </h1>`;
以后编译出来的模板多是这样的:
<h1 class="Type__display__0980340 Type__serif__404840"> Heading title </h1>
从另外一个模块中引入,能够这样写:
.element { composes: dark-red from "./colors.css"; font-size: 30px; line-height: 1.2; }
由于CSS Modules只关注与组件自己,组件自己基本均可以使用扁平的类名来写,相似于这样的:
.root { composes: box from "shared/styles/layout.css"; border-style: dotted; border-color: green; } .text { composes: heading from "shared/styles/typography.css"; font-weight: 200; color: green; }
CSS Modules不局限于你使用哪一个前端库,不管是React、Vue仍是Angular,只要你能使用构建工具进行编译打包就可使用。
下面我使用webpack
为例,一步一步引入CSS Modules.
. ├── build │ └── bundle.js ├── index.html ├── node_modules ├── package-lock.json ├── package.json ├── src │ ├── index.js │ └── styles └── webpack.config.js
index.js做为程序入口,styles文件夹存放样式文件,配合webpack.config.js做为webpack配置文件。
// index.js var html = `<div class="header"> <h2 class="title">CSS Modules</h2> </div>` document.getElementById('container').innerHTML = html;
样式文件:
/* global.css */ * { margin: 0; padding: 0; } .container { padding: 20px; } /* index.css */ .header { font-size: 32px; } .title { border-bottom: 1px solid #ccc; padding-bottom: 20px; }
模板文件:
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>css modules</title> </head> <body> <div id="container" class="container"></div> <script src="./build/bundle.js"></script> </body> </html>
全局安装依赖,配置执行脚本:
npm install webpack webpack-cli --save-dev
package.json
"scripts": { "build": "npx webpack && open index.html" }
在控制台执行npm run build
, 获得的结果为:
> css-modules-demo@1.0.0 build /Users/yhhu/Documents/coding/css-modules-demo > npx webpack && open index.html Hash: 5810d2ecd760c08cc078 Version: webpack 4.17.1 Time: 78ms Built at: 2018-08-26 15:09:31 Asset Size Chunks Chunk Names bundle.js 3.97 KiB main [emitted] main Entrypoint main = bundle.js [./src/index.js] 196 bytes {main} [built]
package.json中加入可以处理css的loader
module: { rules: [ { test: /\.js/, loader: 'babel-loader', include: __dirname + '/src', exclude: __dirname + '/src/styles' }, { test: /\.css$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader', options: { } } ] } ] }
index.js中引入两个CSS文件
// index.js import './styles/global.css' import './styles/index.css' const html = `<div class="header"> <h2 class="title">CSS Modules</h2> </div>` document.getElementById('container').innerHTML = html;
编译以后的执行结果为:
在浏览器中显示为:
能够看到打包以后的build
目录下只有一个bundle.js
,咱们如今要把样式文件提取出来
./build/ └── bundle.js
npm install --save-dev mini-css-extract-plugin
webpack.config.js
var MiniCssExtractPlugin = require("mini-css-extract-plugin"); modules: { rules: [ // { // test: /\.css$/, // use: [ // { loader: "style-loader" }, // { // loader: "css-loader", // options: { // } // } // ] // }, { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: './build/styles' } }, { loader: "css-loader", options: { } } ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", chunkFilename: "[id].css" }) ],
<!-- index.html --> <!DOCTYPE html> <head> <link rel="stylesheet" href="./build/main.css"> </head> <body> <div id="container" class="container"></div> <script src="./build/bundle.js"></script> </body>
能够看到有main.css
生成
默认在css-loader
中是不开启css modules
功能的,要开启能够设置modules: true
便可,更多能够参看官方css-loader
使用方法修改webpack.config.js
,以下:
{ test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: './build/styles' } }, { loader: "css-loader", options: { modules: true } } ] }
修改index.js
文件中的引用方式:
import './styles/global.css' import Index from './styles/index.css' const html = `<div class=${Index.header}> <h2 class=${Index.title}>CSS Modules</h2> </div>` document.getElementById('container').innerHTML = html;
能够看到,以前都是直接import
一个css
文件,而如今改为了导出一个对象的形式,咱们能够把Index
对象打印出来,看看具体是些什么东西:
直接对应咱们引用的方式,而后咱们再看看生成出来的main.css
中具体有哪些东西:
* { margin: 0; padding: 0; } ._2BQ9qrIFipNbLIGEytIz5Q { padding: 20px; } ._3Ukt9LHwDhphmidalfey-S { font-size: 32px; } ._3XpLkKvmw0hNfJyl8yU3i4 { border-bottom: 1px solid #ccc; padding-bottom: 20px; }
合成一个文件以后,全部的类名都通过了哈希转换,所以确保了类名的惟一性,咱们再看看浏览器中inspector
中的样式应用,以下:
事实上,container
样式咱们是不须要转换的,由于我是把它固定写死在了容器上,那咱们应该怎么作呢?
要想一个类名不须要被装换,那么可使用:global(className)
来进行包装,这样的类不会被转换,会被原样输出,下面咱们修改global.css
/* global.css */ * { margin: 0; padding: 0; } :global(.container) { padding: 20px; }
咱们再来看看main.css
就能够发现.container
类没有被转换
CSS Modules默认是以[hash:base64]来进行类名转换的,可辨识度不高,所以咱们须要自定义
开启自定义,可使用一个配置参数localIdentName
,具体配置以下:
{ loader: "css-loader", options: { modules: true, localIdentName: '[path][name]__[local]--[hash:base64:5]' } }
若是咱们实现相似于Sass
的继承功能,咱们须要怎么作呢?CSS Modules中提供了composes
关键字让咱们来继承另一个类,修改index.css
以下:
.red { color: red; } .header { font-size: 32px; } .title { composes: red; border-bottom: 1px solid #ccc; padding-bottom: 20px; }
咱们增长了一个red
的类名,在title
中实现继承,编译以后的结果为:
发现多了一个src-styles-index__red--1ihPk
的类名,正是咱们上面继承的那个类
除了在自身模块中继承,咱们还能够继承其余文件中的CSS规则,具体以下:
咱们再styles
文件夹下新建一个color.css
/* color.css */ .red { color: red; } .blue { color: blue; }
而后在index.css
文件中导入
/* index.css */ .red { color: red; } .header { font-size: 32px; } .title { color: green; composes: blue from './color.css'; composes: red; border-bottom: 1px solid #ccc; padding-bottom: 20px; }
最终咱们会发现文字的颜色为绿色,可见自身模块声明优先级最高,若是把自身申明的color
去掉,那么自身引入和从其余文件引入的相同申明又该如何显示呢?
答案是自身引入的声明的优先级会比较高。
至此,全部的CSS Modules用法就已经介绍完毕了,至于后续的还有如何应用于React
、Vue
以及Angular
中,相信掌握了上面的内容以后就能够知道怎么写了,如何与预处理器一块儿使用相信问题也不大。
最后,本文代码仓库库为:https://github.com/Rynxiao/css-modules-demo