Skip to main content

TailwindCSS x Stylelint x Prettier

在使用 TailwindCSS 進行開發時,常需要在一個 DOM 上套用很多 CSS class 已達到所需效果,往往到了最後,CSS class 會不小心堆疊成:

<button
class="group relative w-full flex justify-center py-2 px-4 border
border-transparent text-sm font-medium rounded-md text-white
bg-primary hover:bg-primary-dark focus:outline-none
focus:ring-2 focus:ring-offset-2 focus:ring-primary"
@click="submit"
>

上述有可能會造成的問題有:

  1. 不小心加了重複的屬性
  2. 閱讀查找不易
  3. CSS class 順序不易維持

對我來說,實在滿難維護,在研究後決定搭配使用 Stylelint 和 Prettier 來整理,

套用以後會長成:

<button
class="group relative flex w-full justify-center rounded-md border border-transparent bg-primary py-2 px-4 text-sm font-medium text-white hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
@click="submit"
>

目的

在使用 TailwindCSS 的專案導入 Stylelint 和 Prettier,

主要是為了依賴自動化整理來解決以下困擾:

  • 因為眼花沒看到或多人協作一時疏忽,重複套用樣式
  • 根據規範進行有意義的排序,易於閱讀和修改

參考資料

官方提供了安裝方法和介紹文章,按圖索驥一步一步安裝即可:

專案環境

版本迭代更新快速,套件和 stylelint 規則也有很多不同選擇,可以根據需求安裝不同套件

以下是我在將其套用進專案時的 package.json

"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@vue/eslint-config-prettier": "^7.0.0",
"eslint": "^8.22.0",
"eslint-config-lcs-vuelint": "^1.1.4",
"eslint-plugin-vue": "^9.4.0",
"stylelint": "^14.10.0",
"stylelint-config-standard": "^27.0.0",
"stylelint-config-standard-scss": "^5.0.0",
"stylelint-order": "^5.0.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
}

stylelint 設定

CSS class 的排序和其他規則等,可透過 .stylelintrc.json 進行設定

至於要如何規範,需看專案環境和個人開發習慣而定

個人在開發時,滿在意 CSS class 排序,由外而內由大到小的規則較不會出現 side effect

關於 TailwindCSS 對於排序的相關規範可以看這篇

最後,以下是我自己在某個專案下的設定,供參考:

{
"extends": ["stylelint-config-standard", "stylelint-config-standard-scss"],
"plugins": ["stylelint-order"],
"rules": {
"indentation": 2,
"color-hex-case": "upper",
"color-no-invalid-hex": true,
"color-hex-length": "long",
"color-no-hex": null,
"color-function-notation": "legacy",
"alpha-value-notation": "number",
"value-no-vendor-prefix": null,
"property-no-vendor-prefix": null,
"order/order": ["custom-properties", "declarations"],
"selector-class-pattern": null,
"keyframes-name-pattern": null,
"scss/dollar-variable-pattern": "^([a-z][a-z0-9]*)(_[a-z0-9]+)*$",
"scss/at-function-pattern": "^([a-z][a-z0-9]*)(_[a-z0-9]+)*$",
"scss/percent-placeholder-pattern": "^([a-z][a-z0-9]*)(_[a-z0-9]+)*$",
"order/properties-order": [
"content",
"position",
"inset",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"vertical-align",
"flex",
"flex-grow",
"flex-shrink",
"flex-basis",
"flex-direction",
"flex-flow",
"flex-wrap",
"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",
"gap",
"row-gap",
"column-gap",
"place-content",
"align-content",
"justify-content",
"place-items",
"align-items",
"justify-items",
"place-self",
"align-self",
"justify-self",
"order",
"float",
"clear",
"object-fit",
"object-position",
"overflow",
"overflow-x",
"overflow-y",
"overflow-scrolling",
"clip",

"box-sizing",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"margin",
"margin-inline",
"margin-block",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"padding",
"padding-inline",
"padding-block",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"border",
"border-spacing",
"border-collapse",
"border-width",
"border-style",
"border-color",
"border-top",
"border-top-width",
"border-top-style",
"border-top-color",
"border-right",
"border-right-width",
"border-right-style",
"border-right-color",
"border-bottom",
"border-bottom-width",
"border-bottom-style",
"border-bottom-color",
"border-left",
"border-left-width",
"border-left-style",
"border-left-color",
"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",
"border-top-image",
"border-right-image",
"border-bottom-image",
"border-left-image",
"border-corner-image",
"border-top-left-image",
"border-top-right-image",
"border-bottom-right-image",
"border-bottom-left-image",

"background",
"background-color",
"background-image",
"background-attachment",
"background-position",
"background-position-x",
"background-position-y",
"background-clip",
"background-origin",
"background-size",
"background-repeat",
"color",
"box-decoration-break",
"box-shadow",
"outline",
"outline-width",
"outline-style",
"outline-color",
"outline-offset",
"table-layout",
"caption-side",
"empty-cells",
"list-style",
"list-style-position",
"list-style-type",
"list-style-image",

"font",
"font-weight",
"font-style",
"font-variant",
"font-size-adjust",
"font-stretch",
"font-size",
"font-family",
"src",
"line-height",
"letter-spacing",
"quotes",
"counter-increment",
"counter-reset",
"-ms-writing-mode",
"text-align",
"text-align-last",
"text-decoration",
"text-emphasis",
"text-emphasis-position",
"text-emphasis-style",
"text-emphasis-color",
"text-indent",
"text-justify",
"text-outline",
"text-transform",
"text-wrap",
"text-overflow",
"text-overflow-ellipsis",
"text-overflow-mode",
"text-shadow",
"white-space",
"word-spacing",
"word-wrap",
"word-break",
"overflow-wrap",
"tab-size",
"hyphens",
"interpolation-mode",

"opacity",
"visibility",
"filter",
"resize",
"cursor",
"pointer-events",
"user-select",

"unicode-bidi",
"direction",
"columns",
"column-span",
"column-width",
"column-count",
"column-fill",
"column-gap",
"column-rule",
"column-rule-width",
"column-rule-style",
"column-rule-color",
"break-before",
"break-inside",
"break-after",
"page-break-before",
"page-break-inside",
"page-break-after",
"orphans",
"widows",
"zoom",
"max-zoom",
"min-zoom",
"user-zoom",
"orientation",
"fill",
"stroke",

"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",
"animation-fill-mode"
]
}
}