PostCSS简介
- 介绍
PostCSS 是一个翻译样式的js插件。它能帮你对css做静态分析。支持变量和混入.编译尚未被浏览器支持的css预发,内联图片等。业界被广泛地应用,其中不乏很多有名的行业领导者,如:维基百科,Twitter,阿里巴巴, JetBrains。PostCSS 的 Autoprefixer 插件是最流行的 CSS 处理工具之一。
- 发展
PostCSS 是 Autoprefixer 的开发者 Andrey Sitnik 开发的,最初是一个通过 JavaScript 来处理 CSS 的方法。PostCSS 本身只是一个 API,不过加上庞大的插件生态体系,作用就非常强大了。为了提供有用的调试功能,PostCSS 还生成了源码的 map,而且还提供了抽象语法树(AST)来帮助我们理解代码是如何被转换的。
- 作用
JavaScript 能做到比其他处理方式更快的转换我们的样式。通过Gulp或Webpack这样的task工具,我们可以在 build 过程中对样式进行转换,这与 Sass 和 LESS 的编译过程非常类似。React 和 AngularJS 这样的库和框架还允许我们在 JavaScript 中直接编写 CSS 代码,这为使用 JavaScript 来转换样式打开了一扇大门。
PostCSS介绍
PostCSS API
Autoprefixer
插件介绍
到目前为止是PostCSS欣欣向荣的插件生态系统使得PostCSS如此惊艳。主要的原因是PostCSS开发插件对于有一些JavaScript开发经验的人来说非常容易。
开发PostCSS插件,不需要特别的许可;下面开发一个基本的PostCSS插件为例。
它可以做什么?
我们将要创建插件,这个插件能插入一些默认样式。编译函数,添加前缀,转换进制等功能。
- 输入
a {
font-family: "Open Sans", family("helloworld");
font-size: 1rem;
flex: 1;
}
- 输出
html, body, ul{
margin: 0;
padding: 0;
/* 用户自定义样式 */
}
a {
color: black;
background-color: white;
font-family: "Open Sans", Arial, Helvetica, sans-serif;
font-size: 12px;
-webkit-flex: 1;
-ms-flex: 1;
/* 用户自定义样式 */
}
工程搭建
虽然我们是在创建自己的插件,但是仍然需要先创建一个空的Gulp或Webpack项目。
PostCSS介绍中有完整的项目搭建说明。如果你不想自己搭建环境,你也可以使用PostCSS样例,这里有完整的gulp与webpack已搭建好的环境以及运行文档。
编写PostCSS插件
在node_modules
中创建一个文件夹命名为 postcss-plugin-demo
。常见的命名方式是使用postcss-前缀,明确插件是PostCSS插件。由于某些编辑器node_modules是隐藏文件夹,不易编写代码,我们也可以把postcss-plugin-demo
移动到与node_modules
并列文件夹。
在postcss-plugin-demo
目录下中创建名为index.js
的文件,并且加载postcss的主模块。
const postcss = require('postcss')
接下来是基本的包装器,用来包装我们的插件处理代码:
const postcss = require('postcss');
module.exports = postcss.plugin('myplugin', function myplugin(options) {
return function (css) {
options = options || {};
// Processing code will be added here
}
});
读取插件
现在你可以加载你刚刚创建的插件了。但是插件里面没有任何代码,我们仅仅想得到必要的设置。
通过Gulp加载
什么是gulp
gulp是可以自动化执行任务的工具 在平时开发的流程里面,一定有一些任务需要手工重复得执行,比如:
- 把文件从开发目录拷贝到生产目录
- 把多个 JS 或者 CSS 文件合并成一个文件
- 对JS文件和CSS进行压缩
- 把sass或者less文件编译成CSS
- 压缩图像文件
- 创建一个可以实时刷新页面内容的本地服务器
只要你觉得有些动作是要重复去做的,就可以把这些动作创建成一个gulp任务 然后在指定的条件下自动执行
gulp中的流
- gulp正是通过代码优于配置的策略来尽量简化任务编写的工作。
- 类似jquery里的链式操作,把各个方法串连起来构建完整的任务。
- 用gulp编写任务也可看作是用Node.js代码编写任务。
- 当使用流时,gulp不需要生成大量的中间文件,只将最后的输出写入磁盘,整个过程因此变得非常快。
gulp的使用流程一般是
- 首先通过gulp.src()方法获取到想要处理的文件流
- 然后把文件流通过pipe方法导入到gulp的插件中
- 最后把经过插件处理后的流再通过pipe方法导入到gulp.dest()中
- gulp.dest()方法则把流中的内容写入到文件中
如果你使用Gulp,gulp的任务要放到一个叫gulpfile.js
的文件里面 先在项目的根目录下面创建一个这样的文件,导入刚才的插件:
const myplugin = require('../postcss-plugin-demo');
const processors = [ myplugin() ]
可以使用gulp的task方法 我们去创建一个叫 css 的任务,它要做的事就是取到想要处理的文件流,进行我们自定义的插件转换后,输出到目的地
- 第一个参数是任务的名称
- 第二个参数是任务的定义,是一个匿名函数
gulp.task('css', function () {
return gulp.src('./src/*.css') // 获取文件的流的api
.pipe(postcss(processors))
.pipe(gulp.dest('./dest')); // 写文件的api
});
然后运行
$ gulp css
即可在dest
目录下生成新的编译后文件style.css
gulp后面跟着的是任务的名称 不输入任务名称的话会默认找default任务,找不到会报错
通过Webpack加载
webpack不能直接编译css文件,必须通过js引入css才能编译。webpack.config.js
导入刚才的插件:
const myplugin = require('../postcss-plugin-demo');
const processors = [ myplugin() ]
并且把css封装成rules规则:
{
...
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [{
loader: 'css-loader'
}, {
loader: 'postcss-loader',
options: {
plugins() {
return processors
}
}
}]
})
},
]
},
...
}
然后运行:
$ webpack
即可在dest
目录下生成新的编译后文件style.css
。
编写插件功能
添加css
开始编写插件之前,我们先创建一段插件编译的样式测试代码。
在你的src/style.css
下添加:
a {
font-family: "Open Sans", family("helloworld");
font-size: 1rem;
flex: 1;
}
现在,因为你的插件并没有做任何事情,如果你编译你的css文件你会在dest
文件夹下面看到完全一样的复制代码dest/style.css
。
开始编写插件
在你的插件postcss-plugin-demo/index.js
里options = options || {}
下添加:
/* 插入初始化html, body属性 */
const base = postcss.parse(`html, body, ul{
margin: 0;
padding: 0;
}`)
css.prepend(base)
返回到gulp(或webpack)运行编译命令:
$ gulp css
查看你的编译后文件,插入了一段语句:
html, body, ul{
margin: 0;
padding: 0;
/* 用户自定义样式 */
}
你已经成功的编写了一段插件代码。
更多API请查看:PostCSS API
遍历你的css样式表
在css中,每个选择器以及后面的样式叫做rule
规则,每行样式叫做decl
声明,例如:
a {
color: red;
}
那么这个css就一条规则a{ color: red; }
,这个规则有一个声明color: red;
。
如果我们想遍历查询我们的样式文件,我们可以在options = options || {}
下面添加以下代码:
css.walkRules(function (rule) {
rule.walkDecls(function (decl, i) {
});
});
使用walkRules
来遍历css文件每一条规则,接着,在每条规则里面,使用walkDecls
遍历你的每一条声明。
给某些选择器增加样式
walkRules
的回调函数里有两个参数,第一个参数就是规则,第二个参数是规则的索引。如果感兴趣,可以手动把规则全部打印出来看一下。
rule.selector
用来获取规则的选择器名称。我们把所有的文字选择器增加两条css声明,黑色文字,白色背景。在walkRules
回调函数下面添加这段代码:
const texts = ['label', 'a', 'span']
if(texts.includes(rule.selector)) {
// 插入样式属性: color, background-color
const color = postcss.decl({ prop: 'color', value: 'black' })
const bgColor = postcss.decl({ prop: 'background-color', value: 'white' })
rule.prepend(color, bgColor)
}
console.log(`${rowIndex + 1 }.处理选择器:`, rule.selector)
返回到gulp(或webpack)运行编译命令:
$ gulp css
查看你的编译后文件:
html, body, ul{
margin: 0;
padding: 0;
/* 用户自定义样式 */
}
a {
color: black;
background-color: white;
font-family: "Open Sans", family("helloworld");
font-size: 1rem;
flex: 1;
}
a
选择器的规则里添加了color,background-color
两个样式。
同时,控制台打印出来:
$ 1.处理选择器: html, body, ul
$ 2.处理选择器: a
处理具体样式声明
walkDecls
的回调函数里同样有两个参数。
- 第一个参数是样式声明,例如
font-family: "Open Sans", family("helloworld");
- 第二个参数是声明的索引。
- 声明里有两个重要的属性
prop, value
。- prop: 样式名称,例如
font-family
- value: 样式值,例如
"Open Sans", family("helloworld")
。
- prop: 样式名称,例如
利用这两个值,我们就可以随意处理样式声明。
在walkDecls
回调函数之内添加以下代码:
// 转换rem为px
if(value.includes('rem')) {
decl.value = value.replace(/(.)rem/, (matched, catched) => {
return Number(catched) * 12 + 'px'
})
}
// 增加前缀
if(prefixs.includes(prop)) {
decl.prop = decl.clone({ prop: '-webkit-' + prop }).prop
rule.append(decl.clone({ prop: '-ms-' + prop }))
}
// 转换关键字
if (value.includes('family')) {
decl.value = replaceValues(value);
}
function replaceValues(str) {
const mapper = {
helloworld: 'Arial, Helvetica, sans-serif'
}
return str.replace(/(.*)family\(\"(.*)\"\)/, (all, prefix, matched, index, input) => {
const mapped = mapper[matched]
return mapped ? `${prefix}${mapped}` : input
})
}
返回到gulp(或webpack)运行编译命令:
$ gulp css
查看你的编译后文件:
html, body, ul{
margin: 0;
padding: 0;
}
a {
color: black;
background-color: white;
font-family: "Open Sans", Arial, Helvetica, sans-serif;
font-size: 12px;
-webkit-flex: 1;
-ms-flex: 1;
}
可以看到,flex
属性增加了前缀,1rem
编译成了12px
,family("helloworld")
编译成Arial, Helvetica, sans-serif
。
同时控制台准确的打印出每条规则与每个声明:
1.处理选择器: html, body, ul
1.1.处理选择器属性: margin
1.2.处理选择器属性: padding
2.处理选择器: a
2.1.处理选择器属性: color
2.2.处理选择器属性: background-color
2.3.处理选择器属性: font-family
2.4.处理选择器属性: font-size
2.5.处理选择器属性: flex
2.6.处理选择器属性: -ms-flex
3.处理选择器: label
3.1.处理选择器属性: color
3.2.处理选择器属性: background-color
3.3.处理选择器属性: font-family
根据外界参数处理样式
有时候,我们需要根据参数来做判断或者编译。比如刚才family("helloworld")
是定死在插件内的。如果用户需要编译其他样式,肯定不能去修改插件。
这种情况我们可以让用户传入要编译的内容。然后在插件内与helloworld
合并。
修改gulp/package.json
,添加用户自定义编译内容到最外层json:
"myConfig": {
"myHelloworld": "Arial, Helvetica Neue, Helvetica, sans-serif"
}
把用户定义的编译内容传入插件内:
const gulp = require('gulp')
const postcss = require('gulp-postcss')
const config = require('./package.json')
const myplugin = require('../postcss-plugin-demo')
const processors = [ myplugin(config.myConfig) ]
gulp.task('css', function () {
return gulp.src('./src/*.css')
.pipe(postcss(processors))
.pipe(gulp.dest('./dest'));
});
插件的包装器回调传入的options
就是刚才我们传入的myConfig
:
module.exports = postcss.plugin('myplugin', function (options) {
return function (css) {
options = options || {}
我们把这个属性传入replaceValues
函数中,与helloworld合并:
function replaceValues(str, options) {
const mapper = Object.assign({
helloworld: 'Arial, Helvetica, sans-serif',
}, options)
return str.replace(/(.*)family\(\"(.*)\"\)/, (all, prefix, matched, index, input) => {
const mapped = mapper[matched]
return mapped ? `${prefix}${mapped}` : input
})
}
然后我们修改src/style.css
,添加一个新规则label
,引用刚才定义的myHelloworld
:
a {
font-family: "Open Sans", family("helloworld");
font-size: 1rem;
flex: 1;
}
label {
font-family: "Open Sans", family("myHelloworld");
}
返回到gulp(或webpack)运行编译命令:
$ gulp css
查看你的编译后文件,这就是我们最终的代码了:
html, body, ul{
margin: 0;
padding: 0;
}
a {
color: black;
background-color: white;
font-family: "Open Sans", Arial, Helvetica, sans-serif;
font-size: 12px;
-webkit-flex: 1;
-ms-flex: 1;
}
label {
color: black;
background-color: white;
font-family: "Open Sans", Arial, Helvetica Neue, Helvetica, sans-serif;
}
family('myHelloworld')
被编译成了Arial, Helvetica Neue, Helvetica, sans-serif
。
最后
基于上面的PostCSS插件相关学习,希望大家迸发出一些其他插件的想法,或者项目中写css时候遇到困扰我们的小问题的时候,以尝试用自己的解决方案去解决它。