在開始前先說一件事, postcss 的 plugin 設計其實有個不小的問題,而為了解決這個問題 postcss 8 大改了這部份的架構,然而大部份的 plugin 還是使用 postcss 7 的 plugin 結構,這邊會兩種都介紹,也會講一下 postcss 8 大改到底是為了解決什麼問題
postcss 7 以前的 plugin 必須要用 postcss.plugin
包起來才行, plugin 的結構大概會長這樣:
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', (options) => {
return (css) => {
// 處理 css
}
})
跟 babel 很像的 css
是個像 path 一樣包含了各種函式,同時也紀錄了 AST tree 的物件,在 postcss 中的 AST 大致上只有五種節點:
Root
: 根節點,就是整個檔案,上面的 css
就是 Root
Rule
: 包含 selector 與其中的設定組合起來的一組規則,如 .example { user-select: none; }
Declaration
: 一條樣式的設定,如 user-select: none;
AtRule
: 由 @
開頭的規則,像 media query 之類的Comments
: 就是註解postcss 不會再去詳細 parse 如 selector 的每個部份,舉例來說 .a .b
就只會是存在 Rule
的 selector
的字串而已,頂多會 parse 用 ,
分開的部份,如果想要詳細 parse selector 的內容的話,就要另外使用 postcss-selector-parser
,其它部份也是這樣的
知道了節點是怎麼樣後,其實再來就跟 babel 的 plugin 很像了,先去 AST Explorer 看 AST 長什麼樣,找出要處理的節點,直接修改 AST ,不過上面的 css
不是 Root
節點嗎?其它的節點要如何取得呢?這時後就要用 walk
系列的函式了:
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', (options) => {
return (css) => {
css.walk(node => {
// node 是一個節點
})
css.walkAtRules(rule => {
// 這邊的 rule 就是一個 `AtRule`
})
css.walkAtRules('tailwind', rule => {
// 這邊的 rule 是 `@tailwind` 的結點
})
}
})
除了 Root
節點外 (Root
只會有一個,根本不需要),其它種類的節點都有一個對應的 walk
的函式:
AtRule
-> walkAtRules
Rule
-> walkRules
Declaration
-> walkDecls
Comments
-> walkComments
可以用這些函式來找出自己要的節點,這邊就來一個移除掉 @tailwind
的 plugin :
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', () => {
return css => {
css.walkAtRules('tailwind', rule => {
rule.remove()
})
}
})
或者把不小心打成 @tailwindcss
變回 @tailwind
的 plugin:
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', () => {
return css => {
css.walkAtRules('tailwindcss', rule => {
// 直接改就行了
rule.name = 'tailwind'
})
}
})
另外 postcss 也有提供建立 AST 節點跟插入節點的函式:
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', () => {
return css => {
css.walkAtRules('tailwind', rule => {
// 建立一個註解的節點
const node = postcss.comment({ text: 'tailwind css' })
// 插入到這個規則前
rule.before(node)
})
}
})
可以用 plugin 的第二個參數產生 warning ,或是用在節點上的函式:
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', () => {
return (css, result) => {
css.walkAtRules('tailwind', rule => {
// 你可以選擇要不要傳入 node
result.warn('foo', { node: rule })
// 或是使用在 node 上的 `warn`
rule.warn(result, 'foo')
// error 只能用節點上的函式來拋出
throw rule.error('foo')
})
}
})
另外 postcss 的 plugin 是可以 async 的
const postcss = require('postcss')
module.exports = postcss.plugin('my-plugin', () => {
return (css, result) => {
// 可以回傳 promise
return Promise.resolve()
}
})
對於 plugin 的介紹就先到這邊了,看到這,不知道你有沒有注意到 postcss 的問題了
postcss 的 plugin 有兩個很大的問題:
所以 postcss 8 的 plugin 就變成了:
module.exports = (options) => {
return {
postcssPlugin: 'my-plugin',
// 如果還是要取得 `Root` 可以在這邊取得
Once(root) {},
// 或是取得一個 `Rule`
Rule(rule) {},
AtRule: {
// 如果要用像 postcss 7 那樣的過濾名字的功能的話
tailwind(rule) {
}
}
}
}
module.exports.postcss = true
下一篇來介紹 vite