当前位置:网站首页>从零开发 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,
...
},
};
边栏推荐
- 旋转框目标检测mmrotate v0.3.1 训练DOTA数据集(二)
- Why can transformer break into the CV world and kill CNN?
- Word frequency statistics (string, list)
- Codeforces C. Andrew and Stones
- Leader: who can use redis expired monitoring to close orders and get out of here!
- Tornado frame foundation
- Golden code of programmer interview
- 二十四、输入输出设备模型(串口/键盘/磁盘/打印机/总线/中断控制器/DMA和GPU)
- Database SQL language 04 subquery and grouping function
- MySQL advanced (Advanced SQL statement)
猜你喜欢

旋转框目标检测mmrotate v0.3.1 训练DOTA数据集(二)

How to prevent source code leakage in enterprises and institutions

剑指 Offer 18. 删除链表的节点

Implementation of property management system with ssm+ wechat applet

How to create a CSR (certificate signing request) file?

What indicators should safety service engineers pay attention to in emergency response?

【板栗糖GIS】global mapper—如何把栅格的高程值赋予给点

Huxiaochun came to fengshu electronics to sign a strategic cooperation agreement with Zoomlion

Vfpbs uploads excel and saves MSSQL to the database

At the age of 32, I fell into a middle-aged crisis and finally quit naked...
随机推荐
Visualization of 3D geological model based on borehole data by map flapping software
hashlips_ art_ Engine-1.0.6 usage
Database SQL language 04 subquery and grouping function
inno setup 最简单的自定义界面效果
Learning about functions QAQ
Database SQL language 03 sorting and paging
Detailed explanation of issues related to SSL certificate renewal
Xi'an Jiaotong 21st autumn "computerized accounting" online homework answer sheet (I) [standard answer]
聲網,站在物聯網的“土壤”裏
2022年,谁在推动音视频产业的新拐点?
Digital signature——
动态规划--怪盗基德的滑翔翼
Answer sheet for online assignment of "motor and drive" of Xijiao 21 autumn (IV) [standard answer]
Navigate back to fragmentpageradapter - & gt; Fragment is empty - navigating back to fragmentpageradapter - & gt; fragments are empty
After getting these performance test decomposition operations, your test path will be more smooth
图扑软件基于钻孔数据的三维地质模型可视化
企事业单位源代码防泄露工作该如何进行
强烈推荐十几款IDEA开发必备的插件
/Path/to/ idiom, not a command
Sword finger offer 22 The penultimate node in the linked list