多年来,我们一直与全局CSS作斗争。现在是时候结束它了。不管你使用哪种语言或框架,CSS命名的冲突不再是问题。我将向你展示如何使用PostCSS和PostCSS-modules在服务端自动处理它。
CSS最初只是用来美化文档的一种工具。自1996年以来,许多事情发生了变化。浏览器不再是文档查看器了。聊天、工作、游戏,几乎没有任何浏览器不能做到的。
现在,我们比在HTML中标记文本和使用CSS开发内容的网站要多得多。我们使用CSS来充分发挥它的潜力,创造出它很难处理的东西。
每个经验丰富的软件开发人员都知道,你旦你使用了全局命名空间(Global namespaces),就相当于打开了一扇麻烦的大门。麻烦就很快的到来。如果在代码中没有为对象提供唯一的名称,那么你将不可避免地面临命名冲突、各种副作用以及无法维护的代码问题相继到来。
对于CSS来说,这意味着有布局的Bug,有CSS权重的烦恼,很长的选择器和无法调试的CSS。因为每个选择器都可以针对不需要的元素,并与其他选择器发生冲突。
几乎所有的编程语言都支持带有作用域隔离(Isolated scope)的模块。即使是一直与CSS密切相关的JavaSript,也具有AMD、CommonJS和ES6模块。然而CSS并没有这样的模块。
组件的隔离对于任何一个应用程序来说都是非常重要的。组件很小,很独立,可以使用它们作为砖块一样,让我们快速构建出更为复杂的应用。但我们还有一个问题:如果防止全局命名的冲突?
方法
因为我们的社区有很多有才华的人,让我们有了OOCSS、BEM、SMACSS和其他类似的方法。这些都是非常有用的方法。他们通过预先设置好的类来解决命名冲突的问题。
他们的主要问题是手工操作,我们必须自己编写这些很长的选择器。但你也可以使用处理器来处理,不过它只是消除效果,而不是原因。这个问题仍然存在。下面是我们如何使用BEM(对于其他方法,命名可能不同,查想法是相通的)来分离组件的示例:
/* 想要的CSS */
.article {
font-size: 16px;
}
.article__title {
font-size: 24px;
}
/* 使用处理器(SCSS) */
.article {
font-size: 16px;
&__title {
font-size: 24px;
}
}
CSS Modules
2015年有两种方法问世。一种是CSS-in-JS,另一种是CSS Modules。我们今天主要聊CSS Modules。
CSS Modules允许你自动处理所有的CSS类,默认情况这些类都是本地的(局部的),也是唯一的。然后生成一个JSON文件来存储原始类和映射出来的类。
/* post.css */
.article {
font-size: 16px;
}
.title {
font-weight: 24px;
}
上面的代码将被转换成像下面这样的代码:
.xkpka {
font-size: 16px;
}
.xkpkb {
font-size: 24px;
}
变换后的类将保存为一个JSON对象:
{
"article": "xkpka",
"title": "xkpkb"
}
在转换之后,你可以读取生成的JSON对象并使用它,而不是某些类:
import styles from './post.json';
class Post extends React.Component {
render() {
return (
<div className={ styles.article }>
<div className={ styles.title }>…</div>
…
</div>
);
}
}
如果想获得更多有关于这方面的知识,可以阅读这篇文章。
不仅保留了方法的优点,而且隔离的模块也被自动转换了。听起来让人难以置信,不是吗?
在这一点上,我们还有一个问题,我们只有在客户端使用CSS Modules的工具,但是在服务器端不使用Node.js,那就不容易了。至少目前为止是如此。
PostCSS-modules
为了在客户端和服务端都有CSS Modules,我编写了PostCSS-modules,一个PostCSS插件,在服务端Ruby、PHP、Python或任何其他语言中使用CSS Modules。
PostCSS是一个使用JS插件来编译CSS样式的处理器。这些插件可以检测你的CSS,也支持变量、混合宏、未来的CSS特性,内联图像等等。例如,Autoprefixer只是一个PostCSS插件。
如果你使用了Autoprefixer,那么表示你已经使用了PostCSS。因此,在插件列表中添加PostCSS-modules应该不是什么大问题。我将向你展示在Gulp和EJS中如何实现,但是你也可以使用其他任何语言来做同样的事情。
// Gulpfile.js
var gulp = require('gulp');
var postcss = require('gulp-postcss');
var cssModules = require('postcss-modules');
var ejs = require('gulp-ejs');
var path = require('path');
var fs = require('fs');
function getJSONFromCssModules(cssFileName, json) {
var cssName = path.basename(cssFileName, '.css');
var jsonFileName = path.resolve('./build', cssName + '.json');
fs.writeFileSync(jsonFileName, JSON.stringify(json));
}
function getClass(module, className) {
var moduleFileName = path.resolve('./build', module + '.json');
var classNames = fs.readFileSync(moduleFileName).toString();
return JSON.parse(classNames)[className];
}
gulp.task('css', function() {
return gulp.src('./css/post.css')
.pipe(postcss([
cssModules({ getJSON: getJSONFromCssModules }),
]))
.pipe(gulp.dest('./build'));
});
gulp.task('html', ['css'], function() {
return gulp.src('./html/index.ejs')
.pipe(ejs({ className: getClass }, { ext: '.html' }))
.pipe(gulp.dest('./build'));
});
gulp.task('default', ['html']);
然后,在命令终端运行gulp
,执行Gulp中配置的任务,它会使用编译出来的CSS和JSON文件,并且在CSS中使用转换后的(JSON文件列表)类名。现在我们可以在EJS模板中使用生成的JSON文件中的值:
<article class="<%= className('post', 'article') %>">
<h1 class="<%= className('post', 'title') %>">Title</h1>
...
</article>
如果你希望看到这段代码的作用,请在Github上查看这个例子。有关更多的使用示例,请参见PostCSS-modules在Github的示例和关于CSS Modules的文章。
有关于CSS Modules更多的教程,将会在这里不断的提供。另外在Github创建了一个有关于CSS Modules的仓库,将在这个仓库里整理各种环境和框架中使用CSS Modules的示例和工程化配置。
从没有感觉在编写可维护的CSS有这么容易过。不再需要庞大的混合宏,也不需要再写很长的前缀。同时也欢迎到未来的世界中。
本文根据@evilmartians的《PostCSS-modules:make CSS great again!》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://evilmartians.com/chronicles/postcss-modules-make-css-great-again。
如需转载,烦请注明出处:https://www.w3cplus.com/css/postcss-modules-make-css-great-again.html