使用CSS Modules解决权重的烦恼
CSS Modules与W3C无关,而是构建过程的一部分。它编译你的项目,重命名选择器和类,使它变得独一无二作用于各个组件。样式被锁在这些组件中,不能在其他地方使用,除非你有特别需求。
前言
现在我们已经习惯了Web技术来驱动App,Web App,移动端和PC端的应用程序。但与简单的静态网站不同的是,应用程序通常更动态、更复杂,而且通常包括组件,甚至超出了Bootstrap和ZURB Foundation提供的功能。当一个应用程序变得复杂起来,管理它的CSS可能是一个如地狱般的任务。
随着时间的推移,已经有了许多类似于OOCSS、ITCSS、BEM、Atomic CSS等CSS的方法论,用来保持CSS的组件,可重用,甚至至关重要的可伸缩性。这些方法要求你和你的团队中的每一个人都需要严格地遵守这些方法论中的规则。
然而,迟早还有更复杂的情景会再次出现,你会遇到像下面这样的样式规则:
html.progressive-image.js [data-progressive-image],html.progressive-image.js [data-progressive-image] * {
background-image: none !important;
mask-image: none !important;
opacity: 0
}
.main #section-enhanced-gallery-heroes.homepage-section.enhanced-gallery .with-single-item {
transform: translate(0, 0) !important
}
在许多大型网站和应用程序中,CSS的问题在于很难处理其权重问题,在某些时候,添加!important
是不可避免的。在大型代码库中重构CSS是非常棘手的,因为删除样式可能会破坏其他组件。
在本教程中,我们将研究CSS Modules,以及它如何帮助我们如何最简单的处理CSS那些臭名昭著的问题。
注意: 请先在Github上把示例代码克隆下来。
git clone git@github.com:tutsplus/css-modules.git
使用CSS Modules
简而言之,CSS Modules是将CSS的class
和id
重命名为唯一选择器的工具,允许它将样式规则从本地隔离到指定的元素或组件。假设我们有一个button
,我们通常可以这样写它的样式规则:
.button {
background-color: #9b4dca;
border: 0.1rem solid #9b4dca;
border-radius: .4rem;
color: #fff;
cursor: pointer;
display: inline-block;
}
.button:focus,
.button:hover {
background-color: #606c76;
border-color: #606c76;
color: #fff;
outline: 0;
}
使用CSS Modules,这些样式规则将被重新命名为:
._12We30_button {
background-color: #9b4dca;
border: 0.1rem solid #9b4dca;
border-radius: .4rem;
color: #fff;
cursor: pointer;
display: inline-block;
}
._12We30_button:focus,
._12We30_button:hover {
background-color: #606c76;
border-color: #606c76;
color: #fff;
outline: 0;
}
分割线的内容是译者自己的内容
进入从Github下载下来项目的01-basic
目录,然后运行:
npm i
安装项目必要的依赖关系,然后运行:
npm run build
你在终端可以看到运行结果:
浏览器打开index.html
页面,你可以看到build
后的页面,对应的类名重新编译了,成为唯一的class
:
从示例中看到的效果,正如我们前面介绍的内容一样。
如果你通过浏览器的DevTools查看诸如Facebook、Instagram或者Airbnb之类的大型网站,你会发现CSS的class
和id
以这种模式命名。下面是Airbnb网站的一个例子:
多个组件
如果我们只有一个组件,使用CSS模块就没有多大意义,所以让我们把我们例子扩展到三个组件,看看如何配置我们的项目来实现CSS模块。
创建一个组件
在第二个示例中,我们将构建三个组件,一个有三种不同风格的按钮。我们称它们为primary button
、outline button
(也被称为ghost button
)和clear button
。我们将把每个按钮放在一个单独的目录中。每个目录将包含index.css
和index.js
。如图所示:
在index.js
中(**注:**该文件在button-primary
目录中),我们创建一个元素,并且将类分配给元素,如下所示:
// 1. 导入`styles`中的`index.css`文件
import styles from './index.css';
/**
* 2. 创建一个`button`元素,并从`index.css`中添加类
* @type {String}
*/
const button = `<button class="${styles['button']}">Save Changes</button>`;
// 3. 将该`button`导出为在其他文件中使用
export default button;
使用ES6的import
指令,我们导入样式表并将class
和id
作为JavaScript对象读取。然后我们创建一个元素并添加类名为.button
给本地的JavaScript模板的button
元素使用。最后,我们导出元素,以便元素也可以在其他JavaScript文件中导入和重用。
我们的样式表index.css
是一个简单的CSS。它包含了许多选择器来样式化button
元素。
/* 02-components/src/button-primary/index.css */
/* 1. Primary Button */
.button {
background-color: #9b4dca;
border: 0.1rem solid #9b4dca;
border-radius: .4rem;
color: #fff;
cursor: pointer;
display: inline-block;
font-size: 1.1rem;
font-weight: 700;
height: 3.8rem;
letter-spacing: .1rem;
line-height: 3.8rem;
padding: 0 3.0rem;
text-align: center;
text-decoration: none;
text-transform: uppercase;
white-space: nowrap;
}
/* Primary Button更多样式 */
使用CSS Modules的优点之一是我们不必再担心命名约定。你仍然可以使用你喜欢的CSS方法,比如BEM或OOCSS,但这不是说要强制执行。你可以为组件编写最实用的样式规则,因为类名最终将被命名为命名空间(namespaced)。
在这个示例中,我们将按钮组件的所有类.button
用.button-primary
或.button-outline
来替代。
@Razvan Caliman:"Working with CSS inside Shadow DOM I still have the old habit of using BEM notation even though I don’t need to. Style encapsulation FTW!"
使用Webpack编译模块
当我们在一个HTML页面上加载index.js
文件,浏览器上并不会出现任何东西。在这种情况下,我们将不得不编译代码以使它具有功能。我们需要安装Babel,Babel Preset for ES2015(ES6)和Webpack,以及以下所谓的加载器(loaders),允许Webpack处理我们的源文件。
- babel-loader:使用Babel核心模块将加载
.js
文件并编译成源码 - css-loader:加载
.css
文件 - style-loader:使用
<style>
将css-loader
内部样式注入到我们的HTML页面
我们将使用npm
将需要的开发依赖关系添加到package.json
文件中。
Webpack配置
Grunt有gruntfile.js
或Gulp有gulpfile.js
,那么在Webpack中我们需要一个名为webpack.config.js
文件用来配置Webpack。以下是本项目完整的Webpack配置:
// 示例代码来源于 /02-components/webpack.config.js
var path = require('path');
module.exports = {
entry: './src/main.js', // 1.
output: {
path: path.resolve(__dirname, 'dist/js'),
filename: 'main.js'
},
module: {
loaders: [ // 4.
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[hash:base64:5]__[local]'
}
}
]
}, {
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
]
}
]
}
}
Webpack配置告诉Webpack编译出来的JavaScript文件,也就是/dist/js
目录下的main.js
。我们还启用了css-loader
中模块配置项,并将class
和id
重命名为[hash:base64:5]__[local]
。还使用babel-loader
来编译ES6文件。
一旦安装了依赖项并设置了配置,我们就将所有的三个按钮导入到main.js
文件中。
// 02-components/src/main.js
// 导入所有按钮元素
import ButtonPrimary from './button-primary';
import ButtonOutline from './button-outline';
import ButtonClear from './button-clear';
// 将元素添加到内容中
document.getElementById('content').innerHTML = `${ButtonPrimary}${ButtonOutline}${ButtonClear}`;
然后像下面一样,运行webpack
命令:
./node_modules/.bin/webpack
当我们加载/dist/js/main.js
,在浏览器应该看到按我们在css-loader
中设置的模式,把类名添加到我们的按钮。我们还可以找到页面中style
元素的样式。
回到我们从Github下载下来的示例中。进入02-components
目录下,然后执行:
npm i
执行上面命令之后,将项目所需的依赖项会安装到package.json
中,然后在命令终端执行:
npm run build
命令运行之后会根据webpack.config.js
的配置编译相关文件
这个时候在浏览器中打开index.html
文件,可以看到像下图的效果:
Composition
CSS处理器(比如LESS和Sass)允许我们通过从其他样式表扩展另一个class
或id
来重用样式。使用CSS Modules,我们可以使用compose
指令来做同样的事情。在本例中,我将三个按钮的公共样式规则放在另一个文件中,并从新文件导入类,如下所示:
.button {
/* 扩展 .button 类应用基本的按钮样式 */
composes: button from "./button.css";
color: #fff;
background-color: #9b4dca;
border: 0.1rem solid #9b4dca;
}
一旦代码被重新编译并加载到浏览器中,我们就可以找到该按钮以两个类呈现。现在也有四个style
元素被注入到页面中,其中包含了我们组件的共公样式规则的_3f6Pb__button
类。
在Vue中使用CSS Modules
在一个真实的项目中,我们很可能不会使用简单的JavaScript来使用CSS Modules。我们将使用像Vue这样的JavaScript框架。幸运的是,CSS Modules已经通过vue-loader集成到Vue中。一个Webpack加载器可以编译.vue
文件。下面是一个示例,说明我们如何将主按钮移值到一个.vue
组件中。
<template>
<button v-bind:class="$style.button"><slot></slot></button>
</template>
<script>
export default {
name: 'button-primary'
}
</script>
<style module>
.button {
/* Extend .button class to apply the basic button styles */
composes: button from "./button.css";
color: #fff;
background-color: #9b4dca;
border: 0.1rem solid #9b4dca;
}
.button[disabled] {
cursor: default;
opacity: .5;
}
.button[disabled]:focus,
.button[disabled]:hover {
background-color: #606c76;
border-color: #606c76;
}
.button:focus,
.button:hover {
background-color: #606c76;
border-color: #606c76;
color: #fff;
outline: 0;
}
</style>
在Vue项目中,我们需要给style
元素添加一个module
属性,正如上面的代码一样,开启CSS Modules。当我们编译这个代码时,我们会得到差不多相同的结果。
总结
对于很多同学而言,这将是全新的。如果CSS Modules的概念乍一看有点让人挠头,这是完全可以理解的。让我们回顾一下我们在本文中所了解到的CSS Modules。
- CSS Modules允许我们通过重命名或
namespacing
来封装样式规则,减少对选择器的约束 - 这也使我们能够更舒适地写出类名,而不是坚持一种特定的方法
- 最后,当样式规则耦合到每个组件时,当我们不再使用组件时,样式也会被移除
在本文中,我们仅仅触及了CSS Modules和其他现代Web开发工具(比如Babel、Webpack和Vue)的皮毛。我在这里整理了一些参考资料来进一步研究这些工具。
扩展阅读
- Check out the demo on Github
- Start Coding ES6 with Babel
- Get Started with Vue
- Instant Webpack 2
- Strategies for Keeping CSS Specificity Low
- Daniel Eden: Move Slow and Fix Things
本文根据@Louie Rootfield的《Solve Your Specificity Headaches With CSS Modules》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://webdesign.tutsplus.com/tutorials/solve-your-specificity-headaches-with-css-modules--cms-28973。
如需转载,烦请注明出处:https://www.fedev.cn/css/solve-your-specificity-headaches-with-css-modules.htmlAir Jordan XIV Low