当前位置:网站首页>从零开发 stylelint规则(插件)
从零开发 stylelint规则(插件)
2022-06-30 05:50:00 【前端卡卡西呀】
本篇文章,我们从零开发一个自定义的 Stylelint插件工程。
完整项目地址参见
一、准备阶段
- PostCss官网 (Stylelint使用PostCss先将css内容解析成AST抽象语法树,再通过对AST进行分析处理,达成想要的结果。没错Eslint也是这样);
- Stylelint官网 (想学习一项技术,先看看它的官网)
- StylelintGitHub (照猫画虎)
- 开发环境: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
- 我们以开发
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选择器
======================================
5. 正式开发:
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;
6. 编写测试用例
测试用例 借助 jest 工具。
- 先安装对应依赖:
npm i jest jest-preset-stylelint -D
- 再配置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$"
},
...
- 根目录下创建
jest-setup.js文件,内容如下:
const getTestRule = require('jest-preset-stylelint/getTestRule');
global.testRule = getTestRule({
plugins: ["./lib"] });
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 // 提示结束位置
}
]
})
7. npm run test
package.json 配置:
...
"scripts": {
"test": "jest"
},
...
运行 npm run test
结果如上,大功告成。
8. 最终目录结构
├── 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 中生成一个软链接,链接到开发目录。
- 在上边的目录中执行
npm link生成软链接
如果 npm link 之后报错:npm ERR! Error: EACCES: permission denied,是因为权限原因,可使用 sudo npm link 重试。
cd 到测试工程,“安装” stylelint-plugin 依赖

打开测试工程的node_modules, 可以看到:
配置测试工程
.stylelintrc.js
module.exports = {
...
"plugins":[
"stylelint-plugin"
...
],
rules: {
'stylelint-plugin/selector-class-no-elements': true,
...
},
};
边栏推荐
- English grammar_ Adjective / adverb Level 3 - superlative
- Intelligent deodorizer embedded development
- 1380. lucky numbers in matrices
- MySQL事物
- The average salary of software testing in 2022 has been released. Have you been averaged?
- Who is promoting the new inflection point of audio and video industry in 2022?
- 86. separate linked list
- 09- [istio] istio service entry
- Tornado frame foundation
- MySQL存储系统
猜你喜欢

Visualization of 3D geological model based on borehole data by map flapping software

At the age of 32, I fell into a middle-aged crisis and finally quit naked...

Database SQL language 04 subquery and grouping function

token 过期后,如何自动续期?

聲網,站在物聯網的“土壤”裏

Do you know how to show the health code in only 2 steps

1380. lucky numbers in matrices

Solidity - Security - reentrancy attack

Sound net, debout dans le "sol" de l'IOT

Today, Ali came out with 35K. It's really sandpaper that wiped my ass. it showed me my hand
随机推荐
Rotating frame target detection mmrotate v0.3.1 learning configuration
[GPU] basic operation of GPU (I)
English grammar_ Adjective / adverb Level 3 - superlative
Answer sheet for online assignment of "motor and drive" of Xijiao 21 autumn (IV) [standard answer]
What do you think of the deleted chat records? How to restore the deleted chat records on wechat?
SSL证书续费相关问题详解
Finally someone can make the server so straightforward
Idea of capturing mobile terminal variant combination
How to write a thesis
Stack overflow caused by C # using protobuf stack overflow
电脑查看WiFi使用密码
ECS deployment web project
Detailed explanation of issues related to SSL certificate renewal
Database SQL language 06 single line function
Rotating frame target detection mmrotate v0.3.1 training dota data set (II)
AI大模型落地大考,浪潮交出了怎样的答卷?
What kind of answer has Inspur given in the big AI model landing test?
86. 分隔链表
Delete the repeating elements in the sorting list (simple questions)
Create priority queue