(翻)写出你的第一个PostCSS插件

为什么要使用PostCSS?

或许你已经在用Sass或Less来给你的CSS工作流添加点逻辑:变量、if/else语句、函数、还有混入。但是,这些预处理器都有它们的局限性。如果你需要基于其他CSS属性的存在来添加一两个CSS属性怎么办?

比如说,我们最近在DockYard疯狂地搞了一堆渐进式Web应用。我们想要那种nice的、有原生感的弹性/惯性滚动,每当我们有个元素设置了overflow: scroll(或overflow-x / overflow-y)的时候。每次我们让一个元素可滚动,我们就得加上-webkit-overflow-scrolling: touch。预处理器没有办法检测一个给定选择器块里都有哪些属性,所以我们需要一个冗长的混入解决方案。另外,我们并不是在每个项目中都使用预处理器,所以我们需要一个PostCSS的解决方案。

比较一下,这里是我们可能会实现一个Sass混入来达到这种滚动行为的方式:

scss 复制代码
@mixin overflow-scroll($direction: false) {
  $property: if($direction, 'overflow-#{$direction}', 'overflow');

  #{$property}: scroll;
  -webkit-overflow-scrolling: touch;
}

看看这个混入在Sassmeister上的使用。

这种方法行得通,但它有一些明显的缺点。首先,你不再是在写规范的CSS了:你在写一个更啰嗦的"抽象"。任何来到这个代码库的开发者都得学习另一个"抽象"。其次,这种方法不是很自动化。如果你忘了使用混入,你就得不到那个额外的属性。

另一方面,PostCSS能够完全自动化这个过程,不需要写任何抽象。一个PostCSS插件可以找到任何带有滚动溢出的选择器块,并自动插入额外的属性。

PostCSS是什么?

在我们深入插件编写过程之前,让我们先理解一下PostCSS是什么。PostCSS允许我们用JavaScript函数来操作我们的CSS。它通过三种方式来实现这一点:

  1. PostCSS将你的CSS文件转换成一个JS对象。
  2. PostCSS插件可以遍历这个对象并添加/删除/修改选择器和属性。
  3. PostCSS将那个对象转换回一个CSS文件。

编写一个PostCSS插件

PostCSS团队在写你自己的插件上做得很好,消除了障碍。假设你已经掌握了如下基本知识:

  • 你对git和命令行很熟悉,
  • 你能写JavaScript函数,以及
  • 你已经安装了node并且知道如何安装npm模块

克隆PostCSS插件样板Repo

到终端并拉取PostCSS插件样板repo:

bash 复制代码
$ git clone git@github.com:postcss/postcss-plugin-boilerplate.git

接下来,运行repo中的向导脚本:

bash 复制代码
$ node ./postcss-plugin-boilerplate/start

这个脚本会在你的终端中问你几个问题。它会从你的本地git配置(如果你设置了的话)中提取你的名字和邮箱地址,然后询问你的Github用户名。

接下来,你会选择你的插件名字。它会以postcss-开头,你来完成名字。向导之后会要求你完成描述你的插件将做什么的句子。最后,它会开始一个你来完成的逗号分隔的标签列表。

一旦你完成了这个设置,你将会有一个样板目录:向导用你在回答脚本问题时选择的插件名字创建了它。让我们前往那个目录:

bash 复制代码
$ cd postcss-test-plugin

在里面,你会找到一些基于node的项目的熟悉组件:index.js,package.json,一个node_modules目录。你将在index.js中放置你的逻辑:操作CSS的函数。如果你的插件有任何其他node模块依赖,package.json会管理它们并且在node_modules中安装它们。

index.js中的样板代码

让我们开始看看index.js提供的样板代码:

javascript 复制代码
var postcss = require('postcss');

它做的第一件事是获取必要的前提条件:PostCSS库本身。接下来的代码依赖于有访问PostCSS的权限。

javascript 复制代码
module.exports = postcss.plugin('postcss-test-plugin', function (opts) {
    opts = opts || {};
    // 在这里处理选项
    return function (root, result) {
        // 在这里转换CSS AST
    };
});

这段代码实际上包含了操作你的CSS的指令。

我们需要做的第一件事是遍历样式表中的

所有声明块。返回函数里的root参数有一个方法可以做到这一点:.walkRules()。

遍历每个选择器块

我们将用.walkRules()升级样板代码,以循环遍历每个声明块并让我们访问其中的样式:

javascript 复制代码
root.walkRules(function(rule) {
  // 一会儿我们会在这里放更多代码...
});

现在我们正在遍历每个选择器块,我们需要看看它是否包含overflow属性。要访问这些属性,我们将使用传递给上面函数的rule的.walkDecls()方法。

遍历每个属性

javascript 复制代码
rule.walkDecls(function(decl) {
  // 在这里我们处理每个`decl`对象。
});

在这个循环内部,decl是表示样式声明的对象。它包含关于属性-值对的数据以及一些操作它的方法。对我们的案例来说最重要的两件事是decl.prop(属性名)和decl.value(属性值)。

找到Overflow属性

为了检测一个decl是否与overflow相关,我们可以在这个循环内部放一个if语句:if (decl.prop.indexOf('overflow') === 0),但有一个更高效的方法。PostCSS允许我们在.walkDecls()方法中过滤特定的属性。你可以在PostCSS API文档中找到这个。如果我们像这样过滤overflow属性,我们就不需要那个if语句了:

javascript 复制代码
rule.walkDecls('overflow', function(decl) {
  // 在这里我们处理`decl`对象。
});

这还不够好。它只会找到overflow属性。如果我们也想要考虑到overflow-x和overflow-y(我们确实想),我们需要稍微调整一下那个过滤器。这个prop参数不接受属性名的数组(我试过['overflow', 'overflow-x', 'overflow-y'],但没用)。要匹配多个属性,我们将不得不使用一点RegEx:/^overflow-?/。这里有一个快速的语法解释:^意味着属性名必须以overflow开头;-?意味着"overflow这个词后面可能有也可能没有-"。注意我们不在regex周围使用''。这带我们到了:

javascript 复制代码
rule.walkDecls(/^overflow-?/, function(decl) {
  // 在这里我们处理`decl`对象。
});

防止重复的属性

我们已经走了一段路,但现在我们几乎到了:这段代码将遍历我们样式表中的所有选择器块,然后遍历这些选择器中所有与溢出相关的属性。剩下要做的就是插入我们的属性。下一块代码将检查溢出相关属性的值是否为scroll,如果是的话,添加让它感觉更原生的属性。

javascript 复制代码
if (decl.value === 'scroll') {
  rule.append({
    prop: '-webkit-overflow-scrolling',
    value: 'touch'
  });
}

在这个案例中,我们借助于一个if语句。我们之前写的循环过滤了属性,所以这个函数只在decl对象的属性以overflow-?开始时运行。现在,如果decl.value是scroll,我们将添加一个属性-值对到父规则对象。我们现在几乎完成了。有可能某人已经包含了-webkit-overflow-scrolling属性。我们不想重复它。PostCSS有一个函数让我们检查一个给定的属性是否已经在选择器块中:

javascript 复制代码
var hasTouch = rule.some(function(i) {
  return i.prop === '-webkit-overflow-scrolling';
});
if (!hasTouch) {
  rule.append({
    prop: '-webkit-overflow-scrolling',
    value: 'touch'
  });
}

现在我们有了一个更好的函数:如果开发者有意将-webkit-overflow-scrolling放在需要的地方,我们不会重复它。

结论

只用20行代码,我们就创建了一个有用的PostCSS插件。

javascript 复制代码
var postcss = require('postcss');
module.exports = postcss.plugin('postcss-test-plugin', function() {
  return function(root) {
    root.walkRules(function(rule) {
      rule.walkDecls(/^overflow-?/, function(decl) {
        if (decl.value === 'scroll') {
          var hasTouch = rule.some(function(i) {
            return i.prop === '-webkit-overflow-scrolling';
          });
          if (!hasTouch) {
            rule.append({
              prop: '-webkit-overflow-scrolling',
              value: 'touch'
            });
          }
        }
      });
    });
  };
});

当然,如果我们考虑到生产目的,还有更多的复杂情况:

  • 我们可以添加一个CSS注释语法,允许开发者排除某些选择器块,使它们不添加弹性滚动。
  • 我们可能想要允许选项参数,使得这个插件只在x轴或y轴上自动化弹性滚动。
  • 我们需要在index.test.js上工作,以便我们可以确保这通过任何代码更新都能继续工作。

但是,总的来说,我们相当快地就组装了一个工作插件。希望你能够利用这个走查,将来能够自己组装出你的PostCSS插件!

原文链接:Writing Your First PostCSS Plugin - DockYard

相关推荐
TonyH200212 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包
Fenderisfine7 天前
使用 vite 快速初始化 shadcn-vue 项目
前端·css·vue.js·前端框架·postcss
今天吃了嘛o9 天前
Vue3中使用tailwindcss插件
前端·css·postcss
vivo互联网技术10 天前
一次基于AST的大规模代码迁移实践
postcss·代码迁移·抽象语法树ast·gogocode
theMuseCatcher21 天前
Vue3+TypeScript+Vite+Less 开发 H5 项目(amfe-flexible + postcss-pxtorem)
typescript·less·postcss
你不讲 wood22 天前
postcss 插件实现移动端适配
开发语言·前端·javascript·css·vue.js·ui·postcss
valvesz661055561 个月前
QDY421F-16P-25液氨不锈钢液动紧急切断阀
安全·制造·postcss
WangConvey1 个月前
vue页面自适应 动态 postcss postcss-pxtorem
前端·vue.js·postcss
慢功夫2 个月前
👨一文讲透等比例缩放响应式-px2rem-px2vw
前端·css·postcss
TangAcrab3 个月前
最新 taro v3 运行,报错 Error: [object Object] is not a PostCSS plugin 解决办法
javascript·taro·postcss