stylelint项目实践
背景
看到项目中团队成员写CSS样式风格迥异,CSS样式的书写顺序没有鲜明的规范。想到以前看过CSS样式书写顺序的文章,决定找出来,给团队成员科普一下。查阅了好几篇文章,觉得这篇文章给出的理由最硬核,css样式的书写顺序及原理——很重要! 然而担心万一文中的观点不对,分享出去被打脸,于是决定验证一下文中的说法。发现文中的核心观点,下面这一段:
没经受起实践的检验。浏览器在渲染页面的过程中,并不是实时逐条读取样式生成CSS Rule Tree,并进行绘制,而是会将同一个文件中的同类样式先合并,再去构建样式规则树并绘制。请看下面的实验,假如浏览器是实时逐条读取样式规则并进行绘制,那么box在渲染绘制的过程中,应该在某一瞬间出现绿色的背景,实际上发现并未出现,未绘制之前,后面设置的样式就把前面设置的背景色重置了。
<style> .box { width:100vw; height: 100vh; background-color: green; } </style> <script> setTimeout(() => { document.querySelector('.item').style.backgroundColor='blue'; }, 2000); </script> <style> .box { background-color: red; } </style> <div class="box"></div>
实践证明,样式的书写顺序对页面绘制没有影响,虽然这篇文章的作者提出的观点值得商榷,但是却为我打开了学习stylelint的大门。我通过以文查文,找到了stylelint 。stylelint的价值在于能发现样式书写中的问题,并能给出一套合理的书写规范。而这,正是我想找的。
1. stylelint带来的好处
- 如下图真实项目所示,可以发现样式书写的问题,以及对样式书写方法进行优化。
- 此外,能使所有人写的样式风格都一致,看别人写的代码,就像看自己写的代码一样,立刻秒懂,易于代码维护。
- 从心中没有明确规则的书写样式,变成按照业内知名公司 (GitHub、Google、Airbnb)的样式规范要求写样式。毕竟Google就是浏览器业内的知名公司,按照Google的样式书写规则去写,不会被坑。
2. stylelint保存时自动格式化的配置方法
2.1 在VSCode应用市场,下载stylelint扩展,目前最新版是v1.2.1
2.2 在项目下的.vscode/setting.json中添加开启保存自动格式化的配置
{ "editor.defaultFormatter": "esbenp.prettier-vscode", "[less]": { "editor.defaultFormatter": "stylelint.vscode-stylelint" }, "[css]": { "editor.defaultFormatter": "stylelint.vscode-stylelint" }, "editor.codeActionsOnSave": { "source.fixAll.stylelint": true }, // 关闭vscode自带的css,less,scss报错提示 "css.validate": false, "less.validate": false, "scss.validate": false, "stylelint.validate": ["css", "less"] }
.vscode/extension.json添加推荐扩展
{ "recommendations": [ "stylelint.vscode-stylelint", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker" ] }
3. 如何批量修复项目样式文件?
3.1 安装stylelint相关的npm依赖包
yarn add -D stylelint@latest stylelint-config-standard@latest stylelint-order@latest stylelint-config-recess-order@latest postcss-less@latest
stylelint-config-standard
作用:配置 Stylelint 规则。
官方的代码风格 :stylelint-config-standard。该风格是 Stylelint 的维护者汲取了 GitHub、Google、Airbnb 多家之长生成的。
stylelint-order
该插件的作用是强制你按照某个顺序编写 css。例如先写定位,再写盒模型,再写内容区样式,最后写 CSS3 相关属性。这样可以极大的保证我们代码的可读性。
stylelint-config-recess-order
stylelint-order 插件的第三方配置
3.2 如果是vue项目,还要安装
yarn add -D postcss-html stylelint-config-recommended-vue
3.3 根目录添加.stylelintrc.js 文件
module.exports = { extends: [ "stylelint-config-standard", "stylelint-order", "stylelint-config-recess-order", "stylelint-config-recommended-vue", ], customSyntax: "postcss-less", // ===== vue项目要增加的配置项============ overrides: [ { files: ["**/*.vue"], customSyntax: "postcss-html", }, ], // ====================================== rules: { indentation: 2, "at-rule-no-unknown": [true, { ignoreAtRules: ["mixin", "extend", "content", "include"] }], "no-empty-source": null, // null是关闭规则的意思--less文件内容可以为空 "no-descending-specificity": null, //禁止特异性较低的选择器在特异性较高的选择器之后重写 "font-family-no-missing-generic-family-keyword": null, // 关闭必须设置通用字体的规则 // 动画名称前,可以加浏览器前缀 如@-webkit-keyframes bounce "at-rule-no-vendor-prefix": null, // id选择器为了兼容#__vconsole, 修改短横线命名 "selector-id-pattern": "^([#_a-z][_a-z0-9]*)(-[a-z0-9]+)*$", // class选择器修改为同时支持短横线和小驼峰 // 为了兼容css module,.coupon-backCard-modal,.czH5shouye-huodong,.czcommon_36_youjiantou_qianhui这样的类名 "selector-class-pattern": "(^([#_a-z][a-zA-Z0-9]*)(-[a-zA-Z0-9]+)*$)|(^[a-z][a-zA-Z0-9]+$)|(^([a-z][a-z0-9]*)(_[a-z0-9]+)*$)", // 动画名称命名,为了兼容这种命名btnScaleAni,添加小驼峰命名规则 "keyframes-name-pattern": "(^([a-z][_a-z0-9]*)(-[a-z0-9]+)*$)|(^([a-z][a-zA-Z0-9]+)*$)", // url地址不加引号 "function-url-quotes": null, // 保留各大浏览器不兼容的样式属性名前缀, 如 -moz-user-select: auto; "property-no-vendor-prefix": null, // 保留各大浏览器不兼容的样式属性值前缀,display: -webkit-box; "value-no-vendor-prefix": null, // 保留各大浏览器不兼容的选择器前缀,如input::-webkit-input-placeholder "selector-no-vendor-prefix": null, // 屏蔽background-color: rgba(0, 0, 0, 0.5);这种写法引起的警告 "color-function-notation": "legacy", // 屏蔽background-color: rgba(0, 0, 0, 0.5);中0.5引起的警告 "alpha-value-notation": "number", // css属性值中小数点之后数字的最大位数 "number-max-precision": 10, "property-no-unknown": [ true, { ignoreProperties: ["box-flex"], // 忽略某些未知属性的检测 }, ], "selector-pseudo-element-no-unknown": [ true, { ignorePseudoElements: ["ng-deep", "input-placeholder"], // 忽略ng-deep这种合法的伪元素选择器报警 }, ], "declaration-colon-newline-after": null, //一个属性过长的话可以写成多行 "media-feature-name-no-unknown": null, // 关闭禁止未知的媒体功能名 // 下面的排序规则是stylelint-config-recess-order的css排序规则, // 要对某个属性排序进行调整,这个属性之前的样式排序都要配置在自定义属性排序中 "order/properties-order": [ { // Must be first. properties: ["all"], }, { // Position. properties: ["position", "top", "right", "bottom", "left", "z-index"], }, { // Display mode. properties: ["box-sizing", "display"], }, { // Flexible boxes. properties: ["flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap"], }, { // Grid layout. properties: [ "grid", "grid-area", "grid-template", "grid-template-areas", "grid-template-rows", "grid-template-columns", "grid-row", "grid-row-start", "grid-row-end", "grid-column", "grid-column-start", "grid-column-end", "grid-auto-rows", "grid-auto-columns", "grid-auto-flow", "grid-gap", "grid-row-gap", "grid-column-gap", ], }, { // Align. properties: ["align-content", "align-items", "align-self"], }, { // Justify. properties: ["justify-content", "justify-items", "justify-self"], }, { // Order. properties: ["order"], }, { // Box model. properties: [ "float", "width", "min-width", "max-width", "height", "line-height", "min-height", "max-height", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "overflow", "overflow-x", "overflow-y", "-webkit-overflow-scrolling", "-ms-overflow-x", "-ms-overflow-y", "-ms-overflow-style", "clip", "clear", ], }, { // Typography. properties: [ "font", "font-family", "font-size", "font-style", "font-weight", "font-variant", "font-size-adjust", "font-stretch", "font-effect", "font-emphasize", "font-emphasize-position", "font-emphasize-style", "-webkit-font-smoothing", "-moz-osx-font-smoothing", "font-smooth", "hyphens", "color", "text-align", "text-align-last", "text-emphasis", "text-emphasis-color", "text-emphasis-style", "text-emphasis-position", "text-decoration", "text-indent", "text-justify", "text-outline", "-ms-text-overflow", "text-overflow", "text-overflow-ellipsis", "text-overflow-mode", "text-shadow", "text-transform", "text-wrap", "-webkit-text-size-adjust", "-ms-text-size-adjust", "letter-spacing", "word-break", "word-spacing", "word-wrap", // Legacy name for `overflow-wrap` "overflow-wrap", "tab-size", "white-space", "vertical-align", "list-style", "list-style-position", "list-style-type", "list-style-image", ], }, { // Accessibility & Interactions. properties: [ "pointer-events", "-ms-touch-action", "touch-action", "cursor", "visibility", "zoom", "table-layout", "empty-cells", "caption-side", "border-spacing", "border-collapse", "content", "quotes", "counter-reset", "counter-increment", "resize", "user-select", "nav-index", "nav-up", "nav-right", "nav-down", "nav-left", ], }, { // Background & Borders. properties: [ "background", "background-color", "background-image", "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", "filter:progid:DXImageTransform.Microsoft.gradient", "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", "filter", "background-repeat", "background-attachment", "background-position", "background-position-x", "background-position-y", "background-clip", "background-origin", "background-size", "background-blend-mode", "isolation", "border", "border-color", "border-style", "border-width", "border-top", "border-top-color", "border-top-style", "border-top-width", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-bottom", "border-bottom-color", "border-bottom-style", "border-bottom-width", "border-left", "border-left-color", "border-left-style", "border-left-width", "border-radius", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "border-image", "border-image-source", "border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat", "outline", "outline-width", "outline-style", "outline-color", "outline-offset", "box-shadow", "mix-blend-mode", "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", "opacity", "-ms-interpolation-mode", ], }, { // SVG Presentation Attributes. properties: [ "alignment-baseline", "baseline-shift", "dominant-baseline", "text-anchor", "word-spacing", "writing-mode", "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "flood-color", "flood-opacity", "image-rendering", "lighting-color", "marker-start", "marker-mid", "marker-end", "mask", "shape-rendering", "stop-color", "stop-opacity", ], }, { // Transitions & Animation. properties: [ "transition", "transition-delay", "transition-timing-function", "transition-duration", "transition-property", "transform", "transform-origin", "animation", "animation-name", "animation-duration", "animation-play-state", "animation-timing-function", "animation-delay", "animation-iteration-count", "animation-direction", ], }, ], }, };
命名参考:
- 短横线命名(kebab-case):
^([a-z][a-z0-9]*)(-[a-z0-9]+)*$
- 小驼峰命名(lowerCamelCase):
^[a-z][a-zA-Z0-9]+$
- 蛇形命名(snake_case):
^([a-z][a-z0-9]*)(_[a-z0-9]+)*$
- 大驼峰命名(UpperCamelCase):
^[A-Z][a-zA-Z0-9]+$
3.4 在package.json中添加stylelint样式修复命令
{ "scripts": { "fix:format": "prettier --write ./src/**/*.{js,jsx,ts,tsx}", "lint:less": "stylelint --fix src/**/*.{css,less}", // ... }, }
3.5 在终端下执行yarn lintless命令, 就能对不符合配置样式规范的代码进行修复,部分问题还需要进一步手动修复。
yarn lint:less
具体规则查询参考:
[1] stylelint规则中文翻译
[2] stylelint构造及规则了解
[4] stylelint常见问题