从零开发 stylelint规则(插件)

12 篇文章 1 订阅
6 篇文章 1 订阅

本篇文章,我们从零开发一个自定义的 Stylelint插件工程。
完整项目地址参见

一、准备阶段

  1. PostCss官网 (Stylelint使用PostCss先将css内容解析成AST抽象语法树,再通过对AST进行分析处理,达成想要的结果。没错Eslint也是这样);
  2. Stylelint官网 (想学习一项技术,先看看它的官网)
  3. StylelintGitHub (照猫画虎)
  4. 开发环境:macOs-12.1 Apple M1 Pro(不太重要,可能跟后期的插件有关)

二、开发阶段

1. 创建目录

打开终端工具(我用的iterm),cd 到平时存储项目的目录;
键入mkdir stylelint-plugin 创建一个名为 stylelint-plugin 的文件夹;
在这里插入图片描述

2. npm 初始化

cd 进 stylelint-plugin目录,键入npm init ,接着一路回车,根据提示 输入内容即可;
这一步后,项目中会新增一个package.json文件;

3. 构造项目结构

仿照StylelintGitHub 构造项目结构如下:

├── lib  // 规则相关内容都放在`lib`目录
│   ├── index.js
│   ├── rules
│   │   ├── index.js
│   │   └── selector-class-no-elements  // 每个文件夹下是一个规则,文件夹名即为规则名
│   │       └── index.js   // 规则内容
│   └── utils
├── package.json
└── README.md
4. 安装部分依赖
npm install stylelint postcss-selector-parser -D
5. 我们以开发 selector-class-no-elements 规则为例,编写代码。

这个规则意义为:class选择器不能以’elements-'开头
因为第三方组件库(ElementUI、Ant Design等),组件名称是以’elements-'开头,开发过程中不要对这些三方组件覆盖样式,以免造成难以预料的bug。

======================================

这里我们插一下,先看看css解析后的内容是什么样的

步骤4中 安装的postcss-selector-parser 插件,就是解析css中选择器的,

先在根目录中创建一个 test.js 文件,内容如下:

const SelectorParser = require('postcss-selector-parser');

const transform = selectors => {
  selectors.walk(node => {
    console.log(node);
  })
}

SelectorParser(transform).processSync('#app');

然后运行 node test.js, log结果如下:
在这里插入图片描述
前者 “Selector” 类型 表示 选择器;
后者 “ID” 类型 表示 ID选择器

======================================

6. 正式开发:

lib/rules/selector-class-no-elements/index.js 内容如下:

/*
 * @Desc: class选择器不能以"elements-" 开头
 * @Author: 前端卡卡西
 * @Date: 2022-06-25 17:10:14
*/

const { nameSpace, newMessage, validateOptions, report } = require('../../utils');
const parseSelector = require("../../utils/parseSelector");

const ruleName = nameSpace('selector-class-no-elements');
const messages = newMessage(ruleName, {
  expected: (selector => `Expected CLASS selector ".${selector}" not to contain "elements-"`)
})

const rule = primary => {

  return (postcssRoot, postcssResult) => {
    const validOptions = validateOptions(postcssRoot, postcssResult, {
      actual: primary
    })

    if (!validOptions) return;

    // walkRules 为遍历所有 Rule类型节点
    postcssRoot.walkRules(ruleNode => {

      const selector = ruleNode.selector;

      parseSelector(selector, postcssResult, ruleNode, (fullSelector) => {
        // walk为遍历,同forEach
        fullSelector.walk(selectorNode => {
          // 筛选class选择器
          if (selectorNode.type !== 'class') return;

          const {value, sourceIndex} = selectorNode;

          // 筛选以 "elements-" 开头的选择器
          if (value.indexOf('elements') !== 0) return;

          // 标记提示结束为止
          const endIndex = sourceIndex + value.length;

          // 提示用户信息
          report({
            result: postcssResult,
            ruleName,
            message: messages.expected(value),
            node: ruleNode,
            index: sourceIndex,
            endIndex
          })
        })
      })
    })
  }
}

rule.ruleName = ruleName;
rule.messages = messages;

module.exports = rule;

lib/utils/index.js 内容如下

const stylelint = require("stylelint")

function nameSpace(ruleName) {
  // 前缀视个人情况而定
  return `stylelint-plugin/${ruleName}`;
}

function newMessage(ruleName, options) {
  return stylelint.utils.ruleMessages(ruleName, options);
}

function validateOptions(result, ruleName, options) {
  return stylelint.utils.validateOptions(result, ruleName, options);
}

function report(problem) {
  console.log({problem});
  return stylelint.utils.report(problem);
}

module.exports = {
  nameSpace,
  newMessage,
  validateOptions,
  report
}

lib/utils/parseSelector.js 内容如下:

const SelectorParser = require("postcss-selector-parser");

module.exports = function parseSelector(selector, result, node, callback) {
  try {
    return SelectorParser(callback).processSync(selector);
  } catch(err) {
    result.warn(`Cannot parse selector (${err})`, { node, stylelintType: 'parseError' });

    return undefined;
  }
}

lib/rules/index.js 内容如下:

const selectorClassNoElements = require('./selector-class-no-elements');

const rules = {
  "selector-class-no-elements": selectorClassNoElements
}

module.exports = rules;

lib/index.js 内容如下:

const { createPlugin } = require("stylelint");
const { nameSpace } = require("./utils");

const rules = require("./rules");

// 通过createPlugin 创建 plugin
const rulesPlugins = Object.keys(rules).map(ruleName => {
  return createPlugin(nameSpace(ruleName), rules[ruleName])
})

module.exports = rulesPlugins;
7. 编写测试用例

测试用例 借助 jest 工具。

  1. 先安装对应依赖:
npm i jest jest-preset-stylelint -D
  1. 再配置jest,在package.json 中增加如下配置:
...
 "jest": {
    "clearMocks": true,
    "collectCoverage": false,
    "collectCoverageFrom": [
      "lib/**/*.js"
    ],
    "coverageDirectory": "./.coverage/",
    "coverageReporters": [
      "lcov",
      "text"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 75,
        "functions": 75,
        "lines": 75,
        "statements": 75
      }
    },
    "setupFiles": [
      "./jest-setup.js"
    ],
    "testEnvironment": "node",
    "roots": [
      "lib"
    ],
    "testRegex": ".*\\.test\\.js$|lib/.*/__tests__/.*\\.js$"
  },
...
  1. 根目录下创建 jest-setup.js 文件,内容如下:
const getTestRule = require('jest-preset-stylelint/getTestRule');

global.testRule = getTestRule({ plugins: ["./lib"] });

  1. lib/rules/selector-class-no-elements/__tests__/index.js 中编写测试代码:(每个规则下边都加一个__tests__/index.js 测试用例文件)
const { messages, ruleName} = require("..");

testRule({
  ruleName,
  config: true,

  // 可通过用例
  accept: [
    { code: '.app {}' }
  ],
  // 不可通过用例
  reject: [
    { code: '.elements-app {}',
      message: messages.expected('elements-app'),
      line: 1, // 提示开始行
      column: 1, // 提示开始位置
      endLine: 1, // 提示结束行
      endColumn: 13 // 提示结束位置
    }
  ]
})
8. npm run test

package.json 配置:

...
"scripts": {
    "test": "jest"
},
...

运行 npm run test
在这里插入图片描述
结果如上,大功告成。

9. 最终目录结构
├── jest-setup.js
├── lib
│   ├── index.js
│   ├── rules
│   │   ├── index.js
│   │   └── selector-class-no-elements
│   │       ├── __tests__
│   │       │   └── index.js
│   │       └── index.js
│   └── utils
│       ├── index.js
│       └── parseSelector.js
├── package-lock.json
├── package.json
├── test.js
└── README.md

三、项目覆盖测试

规则开发完成,需要找项目进行覆盖测试。一般这种工具类项目都是直接发布到 NPM仓库,使用时通过 npm install xxx 安装。

要想不发布npm包,本地测试 可以通过 npm link 命令,在本地的 node_modules 中生成一个软链接,链接到开发目录。

  1. 在上边的目录中执行 npm link 生成软链接
    在这里插入图片描述

如果 npm link 之后报错:npm ERR! Error: EACCES: permission denied,是因为权限原因,可使用 sudo npm link 重试。

  1. cd 到测试工程,“安装” stylelint-plugin 依赖
    在这里插入图片描述
    打开测试工程的 node_modules, 可以看到:
    在这里插入图片描述

  2. 配置测试工程 .stylelintrc.js

module.exports = {
  ...
  "plugins":[
  	"stylelint-plugin"
  	...
  ],
  rules: {
    'stylelint-plugin/selector-class-no-elements': true,
    ...
  },
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端卡卡西呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值