PostCss使用方式

让CSS更完美:PostCSS-modules

2017/01/22 · CSS · POSTCSS

原文出处: Alexander Madyankin   译文出处:众成翻译   

译者注(GeoffZhu): 这篇适合一些使用过预处理CSS的开发者,比如less,sass或stylus,如果你都没用过,那你一定不是个好司机。在PostCSS中早就可以使用CSS Modules了,该篇作者贡献了一个新工具,可以让更多开发者方便的使用最新的CSS Modules。

我们和全局作用域的css斗争了多年,现在终于是时候结束它了。不管你用的是什么语言还是框架,CSS命名冲突将不再是个问题。我将给你展示一下PostCSS和PostCSS-modules如何使用,并且可以在服务端使用它们。 CSS起初只是一个美化文档的工具,但是事情到1996年发生了变化。浏览器中不再单单只有文档了,即时通讯,各种软件,游戏,没什么是浏览器不能承载的。

当今,我们在HTML和CSS方面已经走了很远很远,开发者们激发出了CSS所有的潜力,甚至创造出了一些CSS本身都快驾驭不了的东西。

每一个有经验的开发者都知道 — 每次使用全局命名空间都是留下了一个产生bug的隐患,因为很快就可能出现类似命名冲突之类的问题,再加上其他方面(项目越来越大等)的影响,代码越来越不易维护。

对于CSS来说,这意味着有问题的布局。CSS特异性和CSS宽泛性之间,一直存在着如史诗般的对决。仅仅是因为每个选择器都可能会影响到那些不想被影响的元素,使之产生了冲突。

基本所有编程语言都支持局部作用域。和CSS朝夕相伴的JavaScript有AMD, CommonJS和最终确定的ES6 modules。但是我们并没有一个可以模块化CSS的方法。

对于一个高质量项目来说,独立的UI组件(也就是组件化)非常重要的 — 每个组件小巧独立,可以拼合成复杂的页面,这让我们节省了很多的工作。但是我们始终有一个疑问,如何防止全局命名冲突那?

谈谈 PostCSS

2017/10/18 · CSS · POSTCSS

原文出处: laizimo   

一、PostCss简介

PostCSS 本身是一个功能比较单一的工具。它提供了一种方式用 JavaScript 代码来处理 CSS(A tool for transforming CSS with JavaScript)。它负责把 CSS 代码解析成抽象语法树结构(Abstract Syntax Tree,AST),再交由插件来进行处理。插件基于 CSS 代码的 AST 所能进行的操作是多种多样的,比如可以支持变量和混入(mixin),增加浏览器相关的声明前缀,或是把使用将来的 CSS 规范的样式规则转译(transpile)成当前的 CSS 规范支持的格式。从这个角度来说,PostCSS 的强大之处在于其不断发展的插件体系。开发人员也可以根据项目的需要,开发出自己的 PostCSS 插件。

PostCSS 不会单独使用,而是与已有的构建工具配合使用,如 Gulp、Grunt 和 Webpack 都可以。我们只需要选择需要的 PostCSS 插件进行配置。本文主要介绍在Gulp 中使用 PostCSS 的几种常用插件。

CSS Modules 详解及 React 中实践

2016/01/18 · CSS · CSS Modules, React

原文出处: pure render - camsong   

图片 1

CSS 是前端领域中进化最慢的一块。由于 ES2015/2016 的快速普及和 Babel/Webpack 等工具的迅猛发展,CSS 被远远甩在了后面,逐渐成为大型项目工程化的痛点。也变成了前端走向彻底模块化前必须解决的难题。

CSS 模块化的解决方案有很多,但主要有两类。一类是彻底抛弃 CSS,使用 JS 或 JSON 来写样式。Radium,jsxstyle,react-style 属于这一类。优点是能给 CSS 提供 JS 同样强大的模块化能力;缺点是不能利用成熟的 CSS 预处理器(或后处理器) Sass/Less/PostCSS,:hover:active 伪类处理起来复杂。另一类是依旧使用 CSS,但使用 JS 来管理样式依赖,代表是 CSS Modules。CSS Modules 能最大化地结合现有 CSS 生态和 JS 模块化能力,API 简洁到几乎零学习成本。发布时依旧编译出单独的 JS 和 CSS。它并不依赖于 React,只要你使用 Webpack,可以在 Vue/Angular/jQuery 中使用。是我认为目前最好的 CSS 模块化解决方案。近期在项目中大量使用,下面具体分享下实践中的细节和想法。

 

解决方法

因为有前人的探寻,现在我们有Object-Oriented CSS, BEM, SMACSS等等,这些都是非常棒并且非常有用的方法。他们通过增加前缀的办法,解决了命名冲突的问题。

通过增加前缀的办法解决命名冲突是个体力活(manual mangling)。我们手动的去编写长长的选择器。你也可以使用预编译的css语言,但是它们并没有从根本上解决问题(还是体力活)。下面是我们用BEM规范书写的一个独立组件(对于现有的除BEM之外的方法,思想上基本也是这样):

CSS

/* 普通 CSS */ .article { font-size: 16px; } .article__title { font-size: 24px; } /* 使用css预处理语言 */ .article { font-size: 16px; &__title { font-size: 24px; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 普通 CSS */
.article {
  font-size: 16px;
}
 
.article__title {
  font-size: 24px;
}
 
/* 使用css预处理语言 */
.article {
  font-size: 16px;
 
  &__title {
    font-size: 24px;
  }
}

前言

现在的前端,javascript的发展有目共睹,框架林立。同时,html也是齐头并进,推出了HTML5标准,并且得到了普及。这样的发展却唯独少了一个角色?

CSS,就是这个看似不起眼的家伙,却在开发中发挥着和js一样重要的作用。css,是一种样式脚本,好像和编程语言有着一定的距离,我们可以将之理解为一种描述方法。这似乎导致css被轻视了。不过,css近几年来正在经历着一次巨变——CSS Module。我记得js的井喷期应该可以说是node带来的,它带来了Module的概念,使得JS可以被工程化开发项目。那么,今天的css,也将越来越美好。如果你喜欢我的文章,欢迎评论,欢迎Star~。欢迎关注我的github博客

二、构建gulp PostCss项目

关于gulp的使用方法,请参考gulp教程,构建步骤:
<pre>
1.npm init
2.npm install gulp gulp-postcss --save-dev
3.npm install autoprefixer --save-dev(添加需要引用的插件)
</pre>
在项目文件夹下新建文件gulpfile.js、文件夹/dest & /src,然后配置gulpfile.js:
<pre>
var gulp = require('gulp');
var postcss = require('gulp-postcss');
// autoprefixer 给某些需要兼容性的属性添加浏览器私有前缀
var autoprefixer = require('autoprefixer');
// cssnext 允许使用CSS的将来版本
var cssnext = require('cssnext');
// precss 检查了@if @else,编译符合需求的代码
var precss = require('precss');
// alias 定义自己的速写或"别名"
var alias = require('postcss-alias');
// postcss-crip 有数百个属性缩写的插件
var crip = require('postcss-crip');
// 创建CSS图形 postcss-circle(创建圆) postcss-triangle(创建三角形)
var circle = require('postcss-circle');
var triangle = require('postcss-triangle');
// 使用 top: center; 来达到垂直居中, left: center; 达到水平居中
var center = require('postcss-center');
gulp.task('css', function() {
var processors = [
autoprefixer,
cssnext,
precss,
alias,
crip,
circle,
triangle,
linkcolor,
center
];
return gulp.src('./src/.css')
.pipe(postcss(processors))
.pipe(gulp.dest('./dest'));
});
gulp.task('default', ['css']);
</pre>
配置完成后,我们可以来测试下postcss插件到底能带来什么样的惊喜。
1.在/src目录下新建一个style.css的文件,随便写一个平常我们需要设置浏览器前缀的样式,例如:
<pre>
.demo {
display: flex;
transform: rotate(90deg);
}
</pre>
2.在控制台输入命令gulp css 或者gulp,查看/dest文件夹,发现文件夹下有一个style.css的文件,里面就是通过postcss的autoprefixer插件处理的css代码:
<pre>
.demo {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
</pre>
再看看其他postcss插件所起到的效果:
1.cssnext 插件允许开发人员在当前的项目中使用CSS将来版本中可能会加入的新特性。cssnext 负责把这些新特性转译成当前浏览器中可以使用的语法cssnext 是一系列与 CSS 将来版本相关的 PostCSS 插件的组合,更多方法可以参考更多cssnext方法。
• 自定义属性的名称以“--”开头。当声明了自定义属性之后,可以在样式规则中使用“var()”函数来引用,如下:
<pre>
.cssnext {
background: color(red alpha(-10%));
}
:root {
--mainColor: red;
}
a {
color: color(var(--mainColor) alpha(90%));
}
编译后的结果:
.cssnext {
background: rgba(255, 0, 0, 0.9);
}
a {
color: rgba(255, 0, 0, 0.9);
}
</pre>
• 自定义选择器,CSS 扩展规范(CSS Extensions)中允许创建自定义选择器,比如可以对所有的标题元素(h1 到 h6)创建一个自定义选择器并应用样式。通过“@custom-selector”来定义自定义选择器。
<pre>
@custom-selector :--style h1, h2, h3, h4, h5, h6;
:--style {
font-weight: bold;
}
编译后的结果:
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
PostCss使用方式。}
</pre>
• @apply,命名自定义属性中存储一组属性,然后以其他样式引用它们,类似于sass的@extend,需要引用并下载:
<pre>
在gulpfile.js中引用:
var apply = require('postcss-apply');
在控制台输入命令:
npm install postcss-apply --save-dev
</pre>
<pre>
:root {
--my-theme: {
background-color: black;
color: white;
border: 1px solid green;
};
}
.my {
@apply --my-theme;
}
编译后的结果:
.my {
background-color: black;
color: white;
border: 1px solid green;
}
</pre>
• calc()可以使用数学运算中的简单加( )、减(-)、乘(
)和除(/)来解决问题,而且还可以根据单位如px,em,rem和百分比来转化计算:
<pre>
在gulpfile.js中引用:
var calc = require('postcss-calc');
在控制台输入命令:
npm install postcss-calc --save-dev
</pre>
<pre>
:root {
--font-size-theme: 14px;
}
body {
font-size: var(--font-size-theme);
}
div {
font-size: calc(var(--font-size-theme) x 2);
height: calc(50px - 1em);
margin-bottom: calc(var(--font-size-theme) x 1.5);
}
编译后的结果:
body {
font-size: 14px;
}
div {
font-size: 28px;
height: calc(50px - 1em);
margin-bottom: 21px;
}
</pre>
2.使用纯CSS来创建形状,如postcss-circle & postcss-triangle,创建圆形和三角形的语法:
<pre>
.circle {
circle: size color
}
.triangle {
triangle: pointing-<up|down|left|right>;
width: <length>;
height: <length>;
background-color: <color>;
}
</pre>
postcss有许多有趣的插件,有兴趣的可以去github上查看相关的plugin,然后尝试使用。

CSS 模块化遇到了哪些问题?

CSS 模块化重要的是要解决好两个问题:CSS 样式的导入和导出。灵活按需导入以便复用代码;导出时要能够隐藏内部作用域,以免造成全局污染。Sass/Less/PostCSS 等前仆后继试图解决 CSS 编程能力弱的问题,结果它们做的也确实优秀,但这并没有解决模块化最重要的问题。Facebook 工程师 Vjeux 首先抛出了 React 开发中遇到的一系列 CSS 相关问题。加上我个人的看法,总结如下:

  1. 全局污染

CSS 使用全局选择器机制来设置样式,优点是方便重写样式。缺点是所有的样式都是全局生效,样式可能被错误覆盖,因此产生了非常丑陋的 !important,甚至 inline !important 和复杂的[选择器权重计数表](Selectors Level 3),提高犯错概率和使用成本。Web Components 标准中的 Shadow DOM 能彻底解决这个问题,但它的做法有点极端,样式彻底局部化,造成外部无法重写样式,损失了灵活性。

  1. 命名混乱

 

由于全局污染的问题,多人协同开发时为了避免样式冲突,选择器越来越复杂,容易形成不同的命名风格,很难统一。样式变多后,命名将更加混乱。

  1. 依赖管理不彻底

组件应该相互独立,引入一个组件时,应该只引入它所需要的 CSS 样式。但现在的做法是除了要引入 JS,还要再引入它的 CSS,而且 Saas/Less 很难实现对每个组件都编译出单独的 CSS,引入所有模块的 CSS 又造成浪费。JS 的模块化已经非常成熟,如果能让 JS 来管理 CSS 依赖是很好的解决办法。Webpack 的 css-loader 提供了这种能力。

  1. 无法共享变量

复杂组件要使用 JS 和 CSS 来共同处理样式,就会造成有些变量在 JS 和 CSS 中冗余,Sass/PostCSS/CSS 等都不提供跨 JS 和 CSS 共享变量这种能力。

  1. 代码压缩不彻底

由于移动端网络的不确定性,现在对 CSS 压缩已经到了变态的程度。很多压缩工具为了节省一个字节会把 ’16px’ 转成 ‘1pc’。但对非常长的 class 名却无能为力,力没有用到刀刃上。

上面的问题如果只凭 CSS 自身是无法解决的,如果是通过 JS 来管理 CSS 就很好解决,因此 Vjuex 给出的解决方案是完全的 CSS in JS,但这相当于完全抛弃 CSS,在 JS 中以 Object 语法来写 CSS,估计刚看到的小伙伴都受惊了。直到出现了 CSS Modules。

 

CSS模块(CSS Modules)

2015年出现了另外两种方法的实现。分别是CSS-in-JS 和 CSS Modules。我们将主要谈论后者。

CSS模块允许你将所有css class自动打碎,这是CSS模块(CSS Modules)的默认设置。然后生成一个JSON文件(sources map)和原本的class关联:

CSS

/* post.css */ .article { font-size: 16px; } .title { font-weight: 24px; }

1
2
3
4
5
6
7
8
/* post.css */
.article {
  font-size: 16px;
}
 
.title {
  font-weight: 24px;
}

上面的post.css将会被转换成类似下面这样:

CSS

.xkpka { font-size: 16px; } .xkpkb { font-size: 24px; }

1
2
3
4
5
6
7
.xkpka {
  font-size: 16px;
}
 
.xkpkb {
  font-size: 24px;
}

被打碎替换的classes将被保存在一个JSON对象中:

JavaScript

`{ "article": "xkpka", "title": "xkpkb" } `

1
`{  "article":  "xkpka",  "title":  "xkpkb"  }  `

在转换完成后,你可以直接引用这个JSON对象到项目中,这样就可以用之前写过的class名来直接使用它了。

JavaScript

import styles from './post.json'; class Post extends React.Component { render() { return ( <div className={ styles.article }> <div className={ styles.title }>…</div> … </div> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
import styles from './post.json';
 
class Post extends React.Component {
  render() {
    return (
      <div className={ styles.article }>
        <div className={ styles.title }>…</div>
        …
      </div>
    );
  }
}

更多给力的功能, 可以看看 这篇非常好的文章.

不光是保留了之前提到的几种方法的优点,还自动解决了组件css分离的问题。这就是CSS模块(CSS Modules),听起来非常不错吧!

到这里,我们有遇到了另一个问题: 我们现在的CSS Modules相关工具,只能在客户端(浏览器)使用,把它放到一个非Node.js的服务端环境中是十分十分困难的。

正文

既然作为一篇推广PostCSS的文章,我们就应该先来了解一下这是什么,和我们之前讲的CSS Module有啥关系?此处让我为你们娓娓道来。

CSS Modules 模块化方案

图片 2

CSS Modules 内部通过 [ICSS](css-modules/icss · GitHub) 来解决样式导入和导出这两个问题。分别对应 :import:export 两个新增的伪类。

JavaScript

:import("path/to/dep.css") { localAlias: keyFromDep; /* ... */ } :export { exportedKey: exportedValue; /* ... */ }

1
2
3
4
5
6
7
8
:import("path/to/dep.css") {
  localAlias: keyFromDep;
  /* ... */
}
:export {
  exportedKey: exportedValue;
  /* ... */
}

 

但直接使用这两个关键字编程太麻烦,实际项目中很少会直接使用它们,我们需要的是用 JS 来管理 CSS 的能力。结合 Webpack 的 css-loader 后,就可以在 CSS 中定义样式,在 JS 中导入。
启用 CSS Modules

JavaScript

// webpack.config.js css?modules&localIdentName=[name]__[local]-[hash:base64:5]

1
2
// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]

加上 modules 即为启用,localIdentName 是设置生成样式的命名规则。

JavaScript

/* components/Button.css */ .normal { /* normal 相关的所有样式 */ } .disabled { /* disabled 相关的所有样式 */ }

1
2
3
/* components/Button.css */
.normal { /* normal 相关的所有样式 */ }
.disabled { /* disabled 相关的所有样式 */ }

JavaScript

// components/Button.js import styles from './Button.css'; console.log(styles); buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

1
2
3
4
// components/Button.js
import styles from './Button.css';
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

生成的 HTML 是

<button class="button--normal-abc53">Submit</button>

1
<button class="button--normal-abc53">Submit</button>

 

注意到 button--normal-abc53 是 CSS Modules 按照 localIdentName 自动生成的 class 名。其中的 abc53 是按照给定算法生成的序列码。经过这样混淆处理后,class 名基本就是唯一的,大大降低了项目中样式覆盖的几率。同时在生产环境下修改规则,生成更短的 class 名,可以提高 CSS 的压缩率。

上例中 console 打印的结果是:

JavaScript

Object { normal: 'button--normal-abc53', disabled: 'button--disabled-def886', }

1
2
3
4
Object {
  normal: 'button--normal-abc53',
  disabled: 'button--disabled-def886',
}

CSS Modules 对 CSS 中的 class 名都做了处理,使用对象来保存原 class 和混淆后 class 的对应关系。

通过这些简单的处理,CSS Modules 实现了以下几点:

  • 所有样式都是 local 的,解决了命名冲突和全局污染问题
  • class 名生成规则配置灵活,可以此来压缩 class 名
  • 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
  • 依然是 CSS,几乎 0 学习成本

样式默认局部

使用了 CSS Modules 后,就相当于给每个 class 名外加加了一个 :local,以此来实现样式的局部化,如果你想切换到全局模式,使用对应的 :global

:local:global 的区别是 CSS Modules 只会对 :local 块的 class 样式做 localIdentName 规则处理,:global 的样式编译后不变。

JavaScript

.normal { color: green; } /* 以上与下面等价 */ :local(.normal) { color: green; } /* 定义全局样式 */ :global(.btn) { color: red; } /* 定义多个全局样式 */ :global { .link { color: green; } .box { color: yellow; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.normal {
  color: green;
}
 
/* 以上与下面等价 */
:local(.normal) {
  color: green;
}
 
/* 定义全局样式 */
:global(.btn) {
  color: red;
}
 
/* 定义多个全局样式 */
:global {
  .link {
    color: green;
  }
  .box {
    color: yellow;
  }
}

Compose 来组合样式

对于样式复用,CSS Modules 只提供了唯一的方式来处理:composes 组合

JavaScript

/* components/Button.css */ .base { /* 所有通用的样式 */ } .normal { composes: base; /* normal 其它样式 */ } .disabled { composes: base; /* disabled 其它样式 */ }

1
2
3
4
5
6
7
8
9
10
11
12
/* components/Button.css */
.base { /* 所有通用的样式 */ }
 
.normal {
  composes: base;
  /* normal 其它样式 */
}
 
.disabled {
  composes: base;
  /* disabled 其它样式 */
}

JavaScript

import styles from './Button.css'; buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

1
2
3
import styles from './Button.css';
 
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

生成的 HTML 变为

<button class="button--base-fec26 button--normal-abc53">Submit</button>

1
<button class="button--base-fec26 button--normal-abc53">Submit</button>

由于在 .normal 中 composes 了 .base,编译后会 normal 会变成两个 class。

composes 还可以组合外部文件中的样式。

JavaScript

/* settings.css */ .primary-color { color: #f40; } /* components/Button.css */ .base { /* 所有通用的样式 */ } .primary { composes: base; composes: primary-color from './settings.css'; /* primary 其它样式 */ }

1
2
3
4
5
6
7
8
9
10
11
12
13
/* settings.css */
.primary-color {
  color: #f40;
}
 
/* components/Button.css */
.base { /* 所有通用的样式 */ }
 
.primary {
  composes: base;
  composes: primary-color from './settings.css';
  /* primary 其它样式 */
}

 

对于大多数项目,有了 composes 后已经不再需要 Sass/Less/PostCSS。但如果你想用的话,由于 composes 不是标准的 CSS 语法,编译时会报错。就只能使用预处理器自己的语法来做样式复用了。
class 命名技巧

CSS Modules 的命名规范是从 BEM 扩展而来。BEM 把样式名分为 3 个级别,分别是:

  • Block:对应模块名,如 Dialog
  • Element:对应模块中的节点名 Confirm Button
  • Modifier:对应节点相关的状态,如 disabled、highlight

综上,BEM 最终得到的 class 名为 dialog__confirm-button--highlight。使用双符号 __-- 是为了和区块内单词间的分隔符区分开来。虽然看起来有点奇怪,但 BEM 被非常多的大型项目和团队采用。我们实践下来也很认可这种命名方法。

CSS Modules 中 CSS 文件名恰好对应 Block 名,只需要再考虑 Element 和 Modifier。BEM 对应到 CSS Modules 的做法是:

JavaScript

/* .dialog.css */ .ConfirmButton--disabled { /* ... */ }

1
2
3
4
/* .dialog.css */
.ConfirmButton--disabled {
  /* ... */
}

你也可以不遵循完整的命名规范,使用 camelCase 的写法把 Block 和 Modifier 放到一起:

JavaScript

/* .dialog.css */ .disabledConfirmButton { }

1
2
3
/* .dialog.css */
.disabledConfirmButton {
}

如何实现CSS,JS变量共享

注:CSS Modules 中没有变量的概念,这里的 CSS 变量指的是 Sass 中的变量。

上面提到的 :export 关键字可以把 CSS 中的 变量输出到 JS 中。下面演示如何在 JS 中读取 Sass 变量:

JavaScript

/* config.scss */ $primary-color: #f40; :export { primaryColor: $primary-color; }

1
2
3
4
5
6
/* config.scss */
$primary-color: #f40;
 
:export {
  primaryColor: $primary-color;
}

 

JavaScript

/* app.js */ import style from 'config.scss'; // 会输出 #F40 console.log(style.primaryColor);

1
2
3
4
5
/* app.js */
import style from 'config.scss';
 
// 会输出 #F40
console.log(style.primaryColor);

PostCSS-modules

为了在服务端和客户端都能使用CSS Modules,我写了个PostCSS-modules,它是一个PostCSS插件,让你可以在服务端使用模块化的CSS,并且服务端语言可以是Ruby, PHP, Python 或者其他语言。

PostCSS是一个CSS预处理器,它是用JS实现的。它支持静态检查CSS,支持变量和混入(mixins),能让你使用现在还未被浏览器支持的未来CSS语法,内联图像等等。例如使用最为广泛的Autoprefixer,它只是PostCSS的一个插件。

如果你使用Autoprefixer, 其实你早就在用PostCSS了。所以,添加PostCSS-modules到你的项目依赖列表,并不是一件难事。我先给你打个样(实例),用Gulp and EJS,其实你可以用任何语言做类似的事情。

JavaScript

// 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']);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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任务,就能得到转换后的CSS文件和JSON文件,然后就可以在EJS模版里面用了:

XHTML

<article class="<%= className('post', 'article') %>"> <h1 class="<%= className('post', 'title') %>">Title</h1> ... </article>

1
2
3
4
<article class="<%= className('post', 'article') %>">
  <h1 class="<%= className('post', 'title') %>">Title</h1>
  ...
</article>

如果你想看看实际的代码,我在GitHub给你准备了个example。更多的例子可以看PostCSS-modules和CSS Modules


轻松编写可维护的CSS,没有臃肿的mixins。长长的前缀将成为历史,欢迎来到未来的CSS世界。

1 赞 收藏 评论

图片 3

我想和你们说再见

图片 4

目前,在工程化开发中,使用最多的应该就是Less、Sass和Stylus。首先,还是介绍一下它们吧。它们有个统一的名字——css预处理器。何为CSS预处理器?应该就是一种可以将你根据它的规则写出来的格式转成css的东西(还是讲的通俗一点)。它们的出现可以说是恰逢其时,解决了css的一些缺憾:

  • 语法不够强大,不能够嵌套书写,不利于模块化开发
  • 没有变量和逻辑上的复用机制,导致在css的属性值中只能使用字面量形式,以及不断重复书写重复的样式,导致难以维护。

面对以上问题,css预处理器给出了非常可行的解决方案:

  1. 变量:就像其他编程语言一样,免于多处修改。
    • Sass:使用「$」对变量进行声明,变量名和变量值使用冒号进行分割
    • Less:使用「@」对变量进行声明
    • Stylus:中声明变量没有任何限定,结尾的分号可有可无,但变量名和变量值之间必须要有『等号』。但需要注意的是,如果用“@”符号来声明变量,Stylus会进行编译,但不会赋值给变量。就是说,Stylus 不要使用『@』声明变量。Stylus 调用变量的方法和Less、Sass完全相同。
  2. 作用域:有了变量,就必须得有作用域进行管理。就想js一样,它会从局部作用域开始往上查找变量。
    • Sass:它的方式是三者中最差的,不存在全局变量的概念
    • Less:它的方式和js比较相似,逐级往上查找变量
    • Stylus:它的方式和Less比较相似,但是它和Sass一样更倾向于指令式查找
  3. 嵌套:对于css来说,有嵌套的写法无疑是完美的,更像是父子层级之间明确关系
    • 三者在这处的处理都是一样的,使用「&」表示父元素

有了这些方案,会使得我们可以在保证DPY、可维护性、灵活性的前提下,编写css样式。

回到话题中,之所以会出现向预处理器这样子的解决方案,归根结底还是css标准发展的滞后性导致的。同时,我们也应该考虑一下,真的只要预处理器就够了吗?往往在项目过大时,由于缺乏模块的概念,全局变量的问题会持续困扰着你。每次定义选择器时,总是要顾及到其他文件中是否也使用了同样的命名。毕竟项目是团队的,而不是个人的。哪是否有方式可以解决这些问题呢?

CSS Modules 使用技巧

CSS Modules 是对现有的 CSS 做减法。为了追求**简单可控**,作者建议遵循如下原则:

  • 不使用选择器,只使用 class 名来定义样式
  • 不层叠多个 class,只使用一个 class 把所有样式定义好
  • 不嵌套
  • 使用 composes 组合来实现复用

上面两条原则相当于削弱了样式中最灵活的部分,初使用者很难接受。第一条实践起来难度不大,但第二条如果模块状态过多时,class 数量将成倍上升。

一定要知道,上面之所以称为建议,是因为 CSS Modules 并不强制你一定要这么做。听起来有些矛盾,由于多数 CSS 项目存在深厚的历史遗留问题,过多的限制就意味着增加迁移成本和与外部合作的成本。初期使用中肯定需要一些折衷。幸运的是,CSS Modules 这点做的很好:

1. 如果我对一个元素使用多个 class 呢?

没问题,样式照样生效。

2. 如何我在一个 style 文件中使用同名 class 呢?

没问题,这些同名 class 编译后虽然可能是随机码,但仍是同名的。

3. 如果我在 style 文件中使用了 id 选择器,伪类,标签选择器等呢?

没问题,所有这些选择器将不被转换,原封不动的出现在编译后的 css 中。也就是说 CSS Modules 只会转换 class 名相关样式。

但注意,上面 3 个“如果”尽量不要发生

前人的方法

对于css命名冲突的问题,由来已久,可以说我们前端开发人员,天天在苦思冥想,如何去优雅的解决这些问题。css并未像js一样出现了AMD、CMD和ES6 Module的模块化方案。

那么,回到问题,如何去解决呢?我们的前人也有提出过不同的方案:

  1. Object-Oriented CSS
  2. BEM
  3. SMACSS

方案可以说是层出不穷,不乏有团队内部的解决方案。但是大多数都是一个共同点——为选择器增加前缀。

这可是一个体力活,你可能需要手动的去编写长长的选择器,或许你可以使用预编译的css语言。但是,它们似乎并为解决本质的问题——为何会造成这种缺憾。我们不妨来看看,使用BEM规范写出来的例子:

JavaScript

<!-- 正确的。元素都位于 'search-form' 模块内 --><!-- 'search-form' 模块 --> <form class="search-form"> <!-- 在 'search-form' 模块内的 'input' 元素 --> <input class="search-form__input" /> <!-- 在 'search-form' 模块内的 'button' 元素 --> <button class="search-form__button"></button> </form> <!-- 不正确的。元素位于 'search-form' 模块的上下文之外 --><!-- 'search-form' 模块 --> <form class=""search-block> </form> <!-- 在 'search-form' 模块内的 'input' 元素 --> <input class="search-form__input"/> <!-- 在 'search-form' 模块内的 'button' 元素 --> <button class="search-form__button"></button>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 正确的。元素都位于 'search-form' 模块内 --><!-- 'search-form' 模块 -->
<form class="search-form">
    <!-- 在 'search-form' 模块内的 'input' 元素 -->
    <input class="search-form__input" />
    <!-- 在 'search-form' 模块内的 'button' 元素 -->
    <button class="search-form__button"></button>
</form>
<!-- 不正确的。元素位于 'search-form' 模块的上下文之外 --><!-- 'search-form' 模块 -->
<form class=""search-block>
</form>
<!-- 在 'search-form' 模块内的 'input' 元素 -->
<input class="search-form__input"/>
<!-- 在 'search-form' 模块内的 'button' 元素 -->
<button class="search-form__button"></button>

每次这样子写,估计是个程序员,都得加班吧,哈哈!

CSS Modules 结合 React 实践

className 处直接使用 css 中 class 名即可。

JavaScript

.root {} .confirm {} .disabledConfirm {}

1
2
3
.root {}
.confirm {}
.disabledConfirm {}

import classNames from 'classnames'; import styles from './dialog.css'; export default class Dialog extends React.Component { render() { const cx = classNames({ confirm: !this.state.disabled, disabledConfirm: this.state.disabled }); return <div className={styles.root}> <a className={styles.disabledConfirm}>Confirm</a> ... </div> } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import classNames from 'classnames';
import styles from './dialog.css';
 
export default class Dialog extends React.Component {
  render() {
    const cx = classNames({
      confirm: !this.state.disabled,
      disabledConfirm: this.state.disabled
    });
 
    return <div className={styles.root}>
      <a className={styles.disabledConfirm}>Confirm</a>
      ...
    </div>
  }
}

注意,一般把组件最外层节点对应的 class 名称为 root。这里使用了 [classnames](https://www.npmjs.com/package/classnames) 库来操作 class 名。

如果你不想频繁的输入 styles.**,可以试一下 [react-css-modules](gajus/react-css-modules · GitHub),它通过高阶函数的形式来避免重复输入 styles.**

CSS Modules 结合历史遗留项目实践

好的技术方案除了功能强大炫酷,还要能做到现有项目能平滑迁移。CSS Modules 在这一点上表现的非常灵活。

外部如何覆盖局部样式

当生成混淆的 class 名后,可以解决命名冲突,但因为无法预知最终 class 名,不能通过一般选择器覆盖。我们现在项目中的实践是可以给组件关键节点加上 data-role 属性,然后通过属性选择器来覆盖样式。

// dialog.js return <div className={styles.root} data-role='dialog-root'> <a className={styles.disabledConfirm} data-role='dialog-confirm-btn'>Confirm</a> ... </div>

1
2
3
4
5
// dialog.js
  return <div className={styles.root} data-role='dialog-root'>
      <a className={styles.disabledConfirm} data-role='dialog-confirm-btn'>Confirm</a>
      ...
  </div>

 

JavaScript

/* dialog.css */ [data-role="dialog-root"] { // override style }

1
2
3
4
/* dialog.css */
[data-role="dialog-root"] {
  // override style
}

因为 CSS Modules 只会转变类选择器,所以这里的属性选择器不需要添加 :global

如何与全局样式共存

前端项目不可避免会引入 normalize.css 或其它一类全局 css 文件。使用 Webpack 可以让全局样式和 CSS Modules 的局部样式和谐共存。下面是我们项目中使用的 webpack 部分配置代码:

JavaScript

著作权归作者所有。 商业转载请联系作者获得授权,非商业转载请注明出处。 作者:camsong 链接: 来源:知乎 // webpack.config.js 局部 module: { loaders: [{ test: /.jsx?$/, loader: 'babel' }, { test: /.scss$/, exclude: path.resolve(__dirname, 'src/styles'), loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true' }, { test: /.scss$/, include: path.resolve(__dirname, 'src/styles'), loader: 'style!css!sass?sourceMap=true' }] }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:camsong
链接:http://zhuanlan.zhihu.com/purerender/20495964
来源:知乎
 
// webpack.config.js 局部
module: {
  loaders: [{
    test: /.jsx?$/,
    loader: 'babel'
  }, {
    test: /.scss$/,
    exclude: path.resolve(__dirname, 'src/styles'),
    loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true'
  }, {
    test: /.scss$/,
    include: path.resolve(__dirname, 'src/styles'),
    loader: 'style!css!sass?sourceMap=true'
  }]
}

JavaScript

/* src/app.js */ import './styles/app.scss'; import Component from './view/Component' /* src/views/Component.js */ // 以下为组件相关样式 import './Component.scss';

1
2
3
4
5
6
7
/* src/app.js */
import './styles/app.scss';
import Component from './view/Component'
 
/* src/views/Component.js */
// 以下为组件相关样式
import './Component.scss';

目录结构如下:

JavaScript

src ├── app.js ├── styles │ ├── app.scss │ └── normalize.scss └── views ├── Component.js └── Component.scss

1
2
3
4
5
6
7
8
src
├── app.js
├── styles
│   ├── app.scss
│   └── normalize.scss
└── views
    ├── Component.js
    └── Component.scss

这样所有全局的样式都放到 src/styles/app.scss 中引入就可以了。其它所有目录包括 src/views 中的样式都是局部的。

一种希望

现在的网页开发,讲究的是组件化的思想,因此,急需要可行的css Module方式来完成网页组件的开发。自从2015年开始,国外就流行了CSS-in-JS(典型的代表,react的styled-components),还有一种就是CSS Module。

本篇谈及后者,需要对前者进行了解的话,自行Google即可

对于css,大家都知道,它是一门描述类语言,并不存在动态性。那么,要如何去形成module呢。我们可以先来看一个react使用postcss的例子:

//example.css .article { font-size: 14px; } .title { font-size: 20px; }

1
2
3
4
5
6
7
8
//example.css
 
.article {
    font-size: 14px;
}
.title {
    font-size: 20px;
}

之后,将这些命名打乱:

.zxcvb{ font-size: 14px; } .zxcva{ font-size: 20px; }

1
2
3
4
5
6
.zxcvb{
    font-size: 14px;
}
.zxcva{
    font-size: 20px;
}

将之命名对应的内容,放入到JSON文件中去:

{ "article": "zxcvb", "title": "zxcva" }

1
2
3
4
{
    "article": "zxcvb",
    "title": "zxcva"
}

之后,在js文件中运用:

import style from 'style.json'; class Example extends Component{ render() { return ( div classname={style.article}> div classname={style.title}>/div> /div> ) } }

1
2
3
4
5
6
7
8
9
10
11
import style from 'style.json';
 
class Example extends Component{
    render() {
        return (
            div classname={style.article}>
                div classname={style.title}>/div>
            /div>
        )
    }
}

这样子,就描绘出了一副css module的原型。当然,我们不可能每次都需要手动去写这些东西。我们需要自动化的插件帮助我们完成这一个过程。之后,我们应该先来了解一下postCSS。

总结

CSS Modules 很好的解决了 CSS 目前面临的模块化难题。支持与 Sass/Less/PostCSS 等搭配使用,能充分利用现有技术积累。同时也能和全局样式灵活搭配,便于项目中逐步迁移至 CSS Modules。CSS Modules 的实现也属轻量级,未来有标准解决方案后可以低成本迁移。如果你的产品中正好遇到类似问题,非常值得一试。

1 赞 2 收藏 评论

图片 5

我需要认识你

PostCSS是什么?或许,你会认为它是预处理器、或者后处理器等等。其实,它什么都不是。它可以理解为一种插件系统。使用它GitHub主页上的介绍:

PostCSS is a tool for transforming CSS with JS plugins. These plugins can support variables and mixins, transpile future CSS syntax, inline images, and more.

你可以在使用预处理器的情况下使用它,也可以在原生的css中使用它。它都是支持的,并且它具备着一个庞大的生态系统,例如你可能常用的Autoprefixer,就是PostCSS的一个非常受欢迎的插件,被Google, Shopify, Twitter, Bootstrap和CodePen等公司广泛使用。

当然,我们也可以在CodePen中使用它:

图片 6

这里推荐大家看一下PostCSS的深入系列

接下来,我们来看一下PostCSS的配置:

这里我们使用webpack postcss postcss-loader cssnext postcss-import的组合。

首先,我们可以通过yarn来安装这些包:

yarn add --dev webpack extract-text-webpack-plugin css-loader file-loader postcss postcss-loader postcss-cssnext postcss-import

1
2
yarn add --dev webpack extract-text-webpack-plugin css-loader file-loader postcss postcss-loader postcss-cssnext postcss-import
 

然后,我们配置一下webpack.config.js:

const webpack = require('webpack'); const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { context: path.resolve(__dirname, 'src'), entry: { app: './app.js'; }, module: { loaders: [ { test: /.css$/, use: ExtractTextPlugin.extract({ use: [ { loader: 'css-loader', options: { importLoaders: 1 }, }, 'postcss-loader', ], }), }, ], }, output: { path: path.resolve(__dirname, 'dist/assets'), }, plugins: [ new ExtractTextPlugin('[name].bundle.css'), ], };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  context: path.resolve(__dirname, 'src'),
  entry: {
    app: './app.js';
  },
  module: {
    loaders: [
      {
        test: /.css$/,
        use: ExtractTextPlugin.extract({
          use: [
            {
              loader: 'css-loader',
              options: { importLoaders: 1 },
            },
            'postcss-loader',
          ],
        }),
      },
    ],
  },
  output: {
    path: path.resolve(__dirname, 'dist/assets'),
  },
  plugins: [
    new ExtractTextPlugin('[name].bundle.css'),
  ],
};

然后在根目录下配置postcss.config.js

module.exports = { plugins: { 'postcss-import': {}, 'postcss-cssnext': { browsers: ['last 2 versions', '> 5%'], }, }, };

1
2
3
4
5
6
7
8
module.exports = {
  plugins: {
    'postcss-import': {},
    'postcss-cssnext': {
      browsers: ['last 2 versions', '> 5%'],
    },
  },
};

之后,就可以在开发中使用cssnext的特性了

/* Shared */ @import "shared/colors.css"; @import "shared/typography.css"; /* Components */ @import "components/Article.css";

1
2
3
4
5
/* Shared */
@import "shared/colors.css";
@import "shared/typography.css";
/* Components */
@import "components/Article.css";

/* shared/colors.css */ :root { --color-black: rgb(0,0,0); --color-blue: #32c7ff; } /* shared/typography.css */ :root { --font-text: "FF DIN", sans-serif; --font-weight: 300; --line-height: 1.5; } /* components/Article.css */ .article { font-size: 14px; & a { color: var(--color-blue); } & p { color: var(--color-black); font-family: var(--font-text); font-weight: var(--font-weight); line-height: var(--line-height); } @media (width > 600px) { max-width: 30em; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* shared/colors.css */
:root {
  --color-black: rgb(0,0,0);
  --color-blue: #32c7ff;
}
 
/* shared/typography.css */
:root {
  --font-text: "FF DIN", sans-serif;
  --font-weight: 300;
  --line-height: 1.5;
}
 
/* components/Article.css */
.article {
  font-size: 14px;
  & a {
    color: var(--color-blue);
  }
  & p {
    color: var(--color-black);
    font-family: var(--font-text);
    font-weight: var(--font-weight);
    line-height: var(--line-height);
  }
  @media (width > 600px) {
    max-width: 30em;
  }
}

最后使用webpack进行编译就可以了。

总结

PostCSS,国内还没有太流行起来,不过相信不久的将来也会逐渐的热门,并且国内的资源较少,不过最近新出了一本大漠老师们一起翻译的书——《深入PostCSS Web设计》。有兴趣的人也可以去看一下,学习一些前言的东西。本篇也只是大概的写了一下PostCSS的东西,鉴于国内资源较少,所以参考了一下国外的博文教材,下面会有链接。

如果你对我写的有疑问,可以评论,如我写的有错误,欢迎指正。你喜欢我的博客,请给我关注Star~呦。大家一起总结一起进步。欢迎关注我的github博客

参考链接

PostCSS-modules:
make CSS great again!

PostCSS Deep Dive: What You Need to Know

1 赞 3 收藏 评论

图片 7

本文由星彩网app下载发布于前端技术,转载请注明出处:PostCss使用方式

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。