如何开发一个基本的PostCSS插件

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.jsoptions = 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")

利用这两个值,我们就可以随意处理样式声明。
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编译成了12pxfamily("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时候遇到困扰我们的小问题的时候,以尝试用自己的解决方案去解决它。

おすすめ

転載: blog.csdn.net/kang_k/article/details/131397199