摘要:预处理器最大的好处就是可以支持模块引入,用的方式来编写,解决了部分混乱以及代码冗余的问题,但是也不能完全避免。
或者可以这么说,CSS Modules为我们解决了什么痛点。针对以往我写网页样式的经验,具体来说可以归纳为以下几点:
过程是这样的:你现在有两个模块,分别为A、B,你可能会多带带针对这两个模块编写自己的样式,例如a.css、b.css,看一下代码
// A.js
import "./a.css"
const html = "module A
"
// B.js
import "./b.css"
const html = "module B
"
下面是样式:
/* 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)显示顺序为:
/* mix.css */
/* a.css */
.text {
color: red;
}
/* b.css */
.text {
color: blue;
}
根据CSS的Layout规则,因此后面的样式会覆盖掉前面的样式声明,最终有效的就是text
的颜色为blue
的那条规则,这就是全局样式覆盖,同理,这在js
中也同样存在,因此就引入了模块化,在js中可以用立即执行函数表达式来隔离出不同的模块
var moduleA = (function(document, undefined){
// your module code
})(document)
var moduleB = (function(document, undefined){
// your module code
})(document)
而在css中要想引入模块化,那么就只能通过namespace
来实现,而这个又会带来新的问题,这个下面会讲到
为了解决全局样式的冲突问题,就不得不引入一些特地命名namespace
来区分scope
,但是往往有些namespace
命名得不够清晰,就会造成要想下一个样式不会覆盖,就要再加一个新的namespace
来进行区分,最终可能一个元素最终的显示样式类似如以下:
.widget .table .row .cell .content .header .title {
padding: 10px 20px;
font-weight: bold;
font-size: 2rem;
}
在上一个元素的显示上使用了7个选择器,总结起来会有以下问题:
content
、title
以及item
这些通用的类名时,你可能要花上老半天才知道它们到底是用在哪个元素上【注】CSS的渲染规则可以参看这篇文章探究 CSS 解析原理
由于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
等等
...
Element
依附Block存在,没有多带带的含义,命名上语义尽量接近于Block,比如title
、item
之类
Header
Modifier
是一个元素的状态显示,例如active
、current
、selected
Header
【说明】
Block__Element-father__Element-son_Modifer
这种类名的写法,BEM只有三级
.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 =
`
This is a heading
`;
之后编译出来的模板可能是这样的:
Heading title
从另一个模块中引入,可以这样写:
.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 = `
CSS Modules
`
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;
}
模板文件:
css modules
全局安装依赖,配置执行脚本:
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 = `
CSS Modules
`
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"
})
],
可以看到有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 = `
CSS Modules
`
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
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/1878.html
摘要:示例库通过记录来查看定制类名默认的哈希算法是,从前面我们可以发现被编译成了这样的字符串。与上面不加等价显式的局部作用域语法通过示例库的记录来查看下的样式复用对于样式复用,提供了组合的方式来处理。 showImg(https://segmentfault.com/img/bV9WfX?w=800&h=274);前端发展越来越快,这应该是每个前端开发者的切身感受,但是CSS 是前端领域中进...
摘要:前端日报精选精读引擎特性带来的的性能变化中的艺术译你是如何拆分组件的高级技巧如何遍历中对象属性一个可视化并且能快速生成模拟数据的持久化服务中文第期中的执行上下文和调用栈是什么译谁更适合前端开发掘金实战桌面计算器应用我们日夜期盼的 2017-09-03 前端日报 精选 精读《V8 引擎特性带来的的 JS 性能变化》CSS中的艺术[译] 你是如何拆分组件的?JS高级技巧如何遍历JavaSc...
摘要:能最大化地结合现有生态预处理器后处理器等和模块化能力,几乎零学习成本。编码相关的所有样式上例中打印的结果是注意到是按照自动生成的名。实践手动引用渲染结果使用可以实现使用属性自动加载模块。 文章同步于Github Pines-Cheng/blog 随着前端这几年的风生水起,CSS作为前端的三剑客之一,各种技术方案也是层出不穷。从CSS prepocessor(SASS、LESS、Styl...
摘要:通过这个教程学习如何使用打包工具配合来取代或处理样式文件。使用这个命令安装插件更新。如果你没有项目的副本,你可以通过这条命令克隆在结束这个状态下的项目为添加监听插件。在代码块内,添加如下内容简单起见我省略了文件的大部分内容 通过这个教程学习如何使用JavaScript打包工具Rollup配合PostCSS来取代Grunt或Gulp处理样式文件。 上一篇文章中,我们完成了使用Rollup...
摘要:之前在简书上看到一个入门入门,看这篇就够了,讲得确实很清楚,但是因为博主用的是的版本,和现在普遍默认安装的版本有一些细节上的差距,所以实际使用的时候就会遇到一些坑,对于想入门的小白如我,造成了不小的困扰。 之前在简书上看到一个webpack入门(入门Webpack,看这篇就够了),讲得确实很清楚,但是因为博主用的是1.x的版本,和现在普遍默认安装的2.x版本有一些细节上的差距,所以实际...
阅读 2957·2021-11-08 13:20
阅读 1030·2021-09-22 15:20
阅读 659·2019-08-30 15:53
阅读 1964·2019-08-30 15:43
阅读 1277·2019-08-29 17:21
阅读 539·2019-08-29 12:15
阅读 2374·2019-08-28 17:51
阅读 3142·2019-08-26 13:26