Postcss(完)工作原理、插件(Autoprefixer、postcss-modules、postcss-cssnext、stylelint)、补充(mini-css-extract-plugin、path.resolve、webkit)
文章目录
总结:
补充
-moz代表firefox浏览器私有属性;
-ms代表IE浏览器私有属性;
-webkit代表chrome、safari私有属性;
-o代表Opera私有属性。
node-mini-css-extract-plugin
- 将css单独打包成一个文件的插件,它为每个包含css的js文件都创建一个css文件。它支持css和sourceMaps的按需加载。
- 和extract-text-webpack-plugin相比:
- 异步加载
- 无重复编译,性能有所提升
- 用法简单
- 之支持css分割
- 这个插件应该只在生产环境构建中使用,并且在loader链中不应该有style-loader,特别是我们在开发模式中使用HMR时。
Node-path.resolve
官方文档:
path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径。给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve(’/foo’, ‘/bar’, ‘baz’) 会返回 /bar/baz。
例子:
path.resolve('/foo/bar', './baz'); // 返回: '/foo/bar/baz' path.resolve('/foo/bar', '/tmp/file/'); // 返回: '/tmp/file' path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif'); // 如果当前工作目录为 /home/myself/node, // 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
自我理解:
从后向前,若字符以 / 开头,不会拼接到前面的路径;若以 …/ 开头,拼接前面的路径,且不含最后一节路径;若以 ./ 开头 或者没有符号 则拼接前面路径;
webpack-loader
使用loader需要在webpack配置文件的module.rules中配置
test: /.xxx$/,
- // 匹配后缀名为xxx的文件;正则匹配规则:^ 表示字符串的开始位置
- $ 表示字符串的结束位置,/.xxx$/中的/ /就表示正则表达式自带的
单个loader
// loader: “xxx-loader”, // options: {},
*多个loader,
- loader的处理顺序为从后往前,因此需要优先处理的loader放在数组最后面
- // use: [“xxxx-loader”, “xxx-loader”],
// 如果某个loader需要配置,写成下面的格式
use: [ { loader: "xxxx-loader", options: {} }, "xxx-loader" ],
Postcss
工作原理
我们在构建项目的时候,通过webpack会把css文件的内容传送给postcss-loader,
postcss-loader会解析postcss.config中的插件,传输给 Postcss,
Postcss 会解析传入的css,将其转换为一个AST(抽象语法树),
然后通过各种不同的插件来对这个AST进行操作,
最终序列化新的 css,
最后将结果返回到 postcss-loader,进行 webpack 下一个 loader 的操作。
Autoprefixer
Autoprefixer将使用基于当前浏览器支持的特性和属性数据去为你添加前缀。
补充
-moz代表firefox浏览器私有属性;
-ms代表IE浏览器私有属性;
-webkit代表chrome、safari私有属性;
-o代表Opera私有属性。
由下图可以看出,像没有浏览器差异已经完全符合W3C标准的css2.1属性display,position等,Autoprefixer不会为其加前缀,而像css3属性transform就会为其加前缀,其中–webkit是chrome和safari前缀,–ms则是ie的前缀,像firefox由于已经实现了对transform的W3C标准化,因此使用transform即可。
stylelint
- 用来强制开发人员按照团队css规范写css样式的工具,类似eslint。
- 有了linter,你不需要浪费时间检查代码风格,也不需要对每一个代码错误都写一大堆的注释,因此它能够极大程度地减少你花费在代码审阅上的时间。你无须检查代码究竟做了些什么,也无需关心它看起来什么样。
- 规则
- Possible errors(常见的错误写法,强烈推荐开启)
- Limit language features(弃用一些正确的写法,中等推荐开启)
- Stylistic issues(代码风格代码统一,普通建议开启)
- Possible errors ------ color-no-invalid-hex: 禁止无效的十六进制颜色
1. 前言
说起web前端,大家总不忘想起前端三大组成部分:html,js和Css。但是在多数web开发工程师的眼里,css总是一段苦涩的记忆。
就像这张图中一样,在早期进行大型的项目开发中,错综复杂的 Css 会让开发者崩溃的。
产生这些问题的原因来源于 Css 本身,比如 Css 的语法不够强大,没有嵌套层级,需要书写大量的重复性的选择器。再例如没有变量和合理的样式复用的机制,是的逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护。
为了解决这些问题,便诞生了 Css 预处理器。
2. Css预处理器
什么是css预处理器呢?
预处理器是基于 Css,在其上做了一层属于自己的DSL(Domain specific language),用来解决 Css 遇到的问题。
Css 赋予了 Web 工程师在 Css 方面新的能力,本文以Sass为例。 第一:完全兼容 Css3。在前端快速发展时候,也会有跟不上的,那就是浏览器的更新速度,浏览器的厂商太多,不同的厂商对 Css 标准的解析效果不同,进而对新的特性许多新的特性无法直接在当前浏览器中使用,使得 Css在不同浏览器中兼容就带来了很大的问题。但是在 Sass 中可以放心的使用 Css3 的新特性。
第二,扩展了 Css 的功能:增加了变量,嵌套,混合等功能。长期以来,Css 被人诟病的一个问题就是没有逻辑处理。增加了逻辑处理可以让代码复用性更强。解决了原本 Css 的语法不够强大的问题。 最容易上手的就是嵌套和变量了。
// sass
$red_color: #ea7142;
$green_color:#299e22;
#hello_sass{
color: $red_color;
.font_green_color{
color: $green_color;
}
}
===>
#hello_sass{
color: #ea7142;
}
#hello_sass .font_green_color{
color: #299e22;
}
Css 预处理器的代码是无法直接运行于浏览器的,所以我们还需要进行编译解析成为 Css 文件。这个过程中,我们就可以添加很多的构建环节,比如代码的检查,压缩,排序等等。
于是诞生了 Css 后处理器,这就是本文的重点 — Postcss
3. Postcss介绍
Postcss 是一个使用js插件来转换样式的工具,Postcss 的插件会检查你的css。
其中最常用的插件莫过于 autoprefixer 这个插件了,这个插件会添加 vendor 浏览器的前缀,让我们不需要为了兼容而不断的写-webkit-==(代表chrome、safari私有属性)==这样无聊的代码,丢掉了历史包袱。 那你可能会有疑问了,为什么我创建的vue项目也不需要写前缀啊?原来是 vue-cli 在项目创建的时候已经默认配置了 autoprefixer 这个插件了。 在 Postcss 中还有很多amazing的插件,比如可以自动转换 px 来进行不用屏幕不同宽度大小的适配的 postcss-px-to-viewport,还有强迫症的福音,可以自动对Css属性依照设定的规则进行排序的 Postcss-sorting 等等(autoprefixer,cssnext,CSS Modules,stylelint,lost)。
在PostCSS - a tool for transforming CSS with JavaScript有着这样的对PostCSS特性介绍,箭头后面是对应功能的插件及其github地址。
- increase code readability → postcss/autoprefixer
- Use tomorrow’s CSS ,today! → MoOx/postcss-cssnext
- The end of global CSS → css-modules/postcss-modules
- Avoid errors in your CSS → stylelint/stylelint
- Powerful grid CSS → lost → peterramsing/lost
当然,纸上得来终觉浅,让我们来试用一下 Postcss。
4. Postcss实践
构建一个js项目。
mkdir test-poctcss
npm init
npm install --save-dev css-loader style-loader webpack webpack-cli mini-css-extract-plugin
// 安装poctcss相关依赖
npm install --save-dev postcss postcss-loader
初始化项目,使用 webpack 来进行项目的打包,创建目录结构
我们使用 postcss.config.js 来对 Postcss 进行配置。现在我们仅仅以 autoprefixer 为例子。
npm install --save-dev autoprefixer
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
这样,我们就成功的将插件插入了 Postcss 这个方法中。接下来就是让 webpack 认识 Postcss 了。在安装Postcss 的时候我们还引入了poctcss-loader这个包,我们将打包css相关的内容时候,先调用postcss-loader。
// webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: './src/js/index.js',
output: {
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
},
module: {
rules:[
{
test: /\.css$/,// 匹配后缀名为xxx的文件;正则匹配规则:^ 表示字符串的开始位置
//$ 表示字符串的结束位置,/\.xxx$/中的/ /就表示正则表达式自带的
// 单个loader
// loader: "xxx-loader",
// options: {},
// 多个loader,loader的处理顺序为从后往前,因此需要优先处理的loader放在数组最后面
// use: ["xxxx-loader", "xxx-loader"],
// 如果某个loader需要配置,写成下面的格式
use:
[{
loader: MiniCssExtractPlugin.loader
},
'css-loader', 'postcss-loader' ]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'main.css'
})
]
}
// index.js
import '../style/index.css'
const div = document.createElement('div')
div.innerHTML = 'hello postcss'
div.className = 'hello_postcss'
document.body.append(div)
// index.css
.hello_postcss{
color: #00DD00;
display: flex;
}
配置好了 webpack , js 和 css 文件.我们使用webpack来打包
webpack --mode development
ok。已经成功打包了。让我们看看 dist 下面的 css 文件长什么样吧。
// main.css
.hello_postcss{
color: #00DD00;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
可以看见 Postcss 将我们css中的display:flex打包转换成了多条不同前缀的属性。
5. Postcss原理
为什么poctcss能够完成这些功能呢?通过阅读分析源码,得知。 我们在构建项目的时候,通过webpack会把css文件的内容传送给postcss-loader, postcss-loader会解析postcss.config中的插件,传输给 Postcss,Postcss 会解析传入的css,将其转换为一个AST(抽象语法树),然后通过各种不同的插件来对这个AST进行操作,最终序列化新的 css,最后将结果返回到 postcss-loader,进行 webpack 下一个 loader 的操作。
既然看了 PostCss 的工作原理,那我们也来看看 autoprefixer 到底是怎么工作的。就拿 display 属性来看。
class DisplayFlex extends Value {
constructor (name, prefixes) {
super(name, prefixes)
if (name === 'display-flex') {
this.name = 'flex'
}
}
...
prefixed (prefix) {
let spec, value
;[spec, prefix] = flexSpec(prefix)
// spec 为年份版本 prefix为浏览器前缀
if (spec === 2009) {
if (this.name === 'flex') {
value = 'box'
} else {
value = 'inline-box'
}
} else if (spec === 2012) {
if (this.name === 'flex') {
value = 'flexbox'
} else {
value = 'inline-flexbox'
}
} else if (spec === 'final') {
value = this.name
}
return prefix + value
}
...
}
autoprefixer 会获取我们在 Postcss 中配置的需要支持的浏览器版本。我们的 css 文件里的内容已经被 Postcss 转换成 AST ,相应的就可以拿到属性名和属性值。在 autoprefixer 里面有很多属性的 hook ,当 AST 中有hooks 中的属性时,就会进到该属性的处理中,就会进行上面代码展示的进行属性名和属性值的转换,比如为该属性加上浏览器前缀。(应对不同浏览器厂商所支持的不同,只能进行很多的特判来进行兼容,可见 css 历史包袱有多重)。
6. Postcss插件介绍
6.1 什么是Autoprefixer?
首先明确一点Autoprefixer是一个根据can i use解析css并且为其添加浏览器厂商前缀的PostCSS插件。
不加任何vender prefix的通常写法。
::placeholder {
color: gray;
}
Autoprefixer将使用基于当前浏览器支持的特性和属性数据去为你添加前缀。你可以尝试下Autoprefixer的demo:Autoprefixer CSS online
由上图可以看出,像没有浏览器差异已经完全符合W3C标准的css2.1属性display,position等,Autoprefixer不会为其加前缀,而像css3属性transform就会为其加前缀,其中–webkit是chrome和safari前缀,–ms则是ie的前缀,像firefox由于已经实现了对transform的W3C标准化,因此使用transform即可。
因此Autoprefixer是一个非常有用的加速前端开发的一个工具,但是它需要基于PostCSS去发挥作用。
如果对vender prefix存疑,可以去看我的这篇博客:rem / Vender Prefix / CSS extensions
6.2 什么是postcss-cssnext?
postcss-cssnext语法input:
:root {
--fontSize: 1rem;
--mainColor: #12345678;
--centered: {
display: flex;
align-items: center;
justify-content: center;
};
}
body {
color: var(--mainColor);
font-size: var(--fontSize);
line-height: calc(var(--fontSize) * 1.5);
padding: calc((var(--fontSize) / 2) + 1px);
}
.centered {
@apply --centered;
}
浏览器可用语法output:
body {
color: rgba(18, 52, 86, 0.47059);
font-size: 16px;
font-size: 1rem;
line-height: 24px;
line-height: 1.5rem;
padding: calc(0.5rem + 1px);
}
.centered {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
粗略看了一遍演示demo,http://cssnext.io/playground 感觉既好用又不好用。
好用的地方在于通过var()和calc()进行css属性值的计算,也有@apply这样的应用大段规则的写法,也可以借此去了解一些新的css草案特性;不好用的地方在于有一定的学习成本,而且在前期与webpack,gulp以及parcel进行结合时也需要花费一定时间,并且如果有新的前端组成员加入,必须要掌握这种cssnext的语法。
这样做有些似乎在尝试将css变为一种可以进行逻辑处理的语言,但是我个人认为这对于css这样的灵活的需要具象思维并且需要大量调试的语言来说,工作中使用cssnext不是一个很好的选择,但是工作之余可以作为一个学习新的css草案特性的一个入口,待到纳入标准再去使用。
刚开始对自己的想法不确定,因此去看了下前辈们的想法,其中顾铁灵对cssnext的想法与我的想法如出一辙:
CSS 的转译器(transpiler),根据目前仍处于草案阶段、未被浏览器实现的标准把代码转译成符合目前浏览器实现的 CSS。类似 ES6 的 Babel。
相比之下,Autoprefixer 更加具有实用价值,而 cssnext 实现的功能以后浏览器会怎么实现还存疑,感觉只能玩玩。
6.3 什么是postcss-modules?
在看postcss-modules之前,首先要明确的是CSS Modules的这个概念,关于CSS Modules,可以阅读我翻译的一篇文章:【译】什么是CSS Modules ?我们为什么需要他们? · Issue #45 · FrankKai/FrankKai.github.io
postcss-modules则是CSS Modules在PostCSS上的实现插件,这里有一篇插件作者本人写的介绍postcss-modules的文章:PostCSS-modules: Isolate ’em all! — Martian Chronicles。
在我有限的开发经验中,只在react中使用过css modules,在vue和angularjs中都没用到过,而且在react中使用时,不会去用postcss-modules这个插件,而是使用gajus/react-css-modules这个CSS Modules思想在react中的插件。
下面将给出最简单的入门例子:
在React上下文中,CSS Modules可能像下面这样写:
import React from 'react';
import styles from './table.css';
export default class Table extends React.Component {
render () {
return <div className={styles.table}>
<div className={styles.row}>
<div className={styles.cell}>A0</div>
<div className={styles.cell}>B0</div>
</div>
</div>;
}
}
最后渲染出的组件的标签会是如下形式:
<div class="table__table___32osj">
<div class="table__row___2w27N">
<div class="table__cell___1oVw5">A0</div>
<div class="table__cell___1oVw5">B0</div>
</div>
</div>
如果对为什么会把class名编译成table__table___32osj这样的形式存在疑惑,需要先把css modules搞清楚:【译】什么是CSS Modules ?我们为什么需要他们? · Issue #45 · FrankKai/FrankKai.github.io
如果需要在开发环境或者生产环境结合webpack去使用,那么可以阅读gajus/react-css-modules的官方文档寻找答案。
通过这次探索我们可以发现,前端开发在不同的生态,或者说框架体系下,同一个技术,例如CSS Modules这种将思想,会有对应的实现方式,而我们要掌握的,不仅仅是在某种框架下配置使用的方法,而是这种开发思想。因为学习的核心在于学习知识,而不是无休止的学习工具。
6.4 什么是stylelint?
这是用来强制开发人员按照团队css规范写css样式的工具,类似eslint。
若想使用,只需要去启用规则即可。
节选一段stylelint作者博文中的话:
没错,你的团队可能在某个地方的某条纯文本wiki中记录了团队的代码样式规范。但是,不容忽视的是人的因素:人总是会犯错——总是在无意之间。
而且即使你很自律地执着遵循某个规范的代码风格,但是你没办法确保你的同事或是你的开源项目的贡献者能够像你一样。没有linter的帮助,你必须人工检查代码样式和错误。而机器存在的意义就是代替人来完成能够自动化实现的任务。linter就是这样的机器,有了linter,你不需要浪费时间检查代码风格,也不需要对每一个代码错误都写一大堆的注释,因此它能够极大程度地减少你花费在代码审阅上的时间。你无须检查代码究竟做了些什么,也无需关心它看起来什么样。
事实与stylelint作者说的是一样的,即使团队有前端开发规范,也会不经意间写出不符合规范的代码,因为每次写css规则前都去规范check一遍不是谁都能做到的,如果团队再没有code review这一关的话,写出各种各样风格的css代码就是一件必然的事了,短期没有什么影响,当项目变得庞大起来,增加新功能或者重写旧功能将会是一件很痛苦的事。
我们主要去关注Rules部分:
sytlelint的规则主要有3类,我将从每一类规则中挑一个拿出来作为示例。
-
Possible errors(常见的错误写法,强烈推荐开启)
-
Limit language features(弃用一些正确的写法,中等推荐开启)
-
Stylistic issues(代码风格代码统一,普通建议开启)
-
Possible errors ------ color-no-invalid-hex: 禁止无效的十六进制颜色
完全形式的十六进制颜色可以是6或者8(7,8位是透明度的值)个字符。同样他们的缩写可以是3或者4个字符。
options : true
下面的代码违反规则:
a { color: #00; }
a { color: #fff1az; }
a { color: #12345aa; }
下面的代码符合规则:
a { color: #000; }
a { color: #000f; }
a { color: #fff1a0; }
a { color: #123450aa; }
- Limit language features ------ color-no-hex:禁止使用十六进制颜色
options : true
十六进制的颜色违反规则:
a { color: #000; }
a { color: #fff1aa; }
a { color: #123456aa; }
无效的十六进制色同样违规:
a { color: #foobar; }
a { color: #0000000000000000; }
下面的是符合规则的:
a { color: black; }
a { color: rgb(0, 0, 0); }
a { color: rgba(0, 0, 0, 1); }
- Stylistic issues ------ color-hex-case: 自动将十六进制色转换为大写或者小写
Options string: “lower”|“upper”
可以使用stylelint "foo/*.css" --fix
实现同样的功能。
“小写”
下面的代码是违规的:
a { color: #FFF; }
下面的是符合规则的:
a { color: #000; }
a { color: #fff; }
“大写”
下面的代码是违规的:
a { color: #fff; }
下面的是符合规则的:
a { color: #000; }
a { color: #FFF; }
更多的stylelint的规则可以查阅:stylelint/stylelint
6.5 什么是LostGrid?
Lost Grid是一个强大的PostCSS网格系统,可与任何预处理器甚至是原生CSS一起使用。
在这里有非常好的demo展示:Example of LostGrid
节选2个展示进行说明。
.ColumnSection__grid div {
lost-column: 1/1;
}
@media (min-width: 400px) {
.ColumnSection__grid div {
lost-column: 1/3;
}
}
@media (min-width: 900px) {
.ColumnSection__grid div {
lost-column: 1/6;
}
}
大于900px时:
小于900px时:
.NestingSection__grid {
background: #8eb19d;
}
.NestingSection__grid div {
background: #7ba48d;
lost-column: 1/3;
}
.NestingSection__grid div div {
background: #68977c;
lost-column: 1/2;
}
经过查看css样式我们发现,其实就是巧用了table布局,before/after伪元素,以及css选择器,以及border-box布局,但其实最最核心的地方还是在于很好的使用了css本身具有的流式布局以及BFC,针对各种情况,在插件内部使用了大量的样式进行约束。
在css3的flex布局和grid布局逐渐被浏览器所支持的今天,我个人建议不使用LostGrid插件。