我不段重複 plugins 是 postcss 最重要的設定,並在「無需學習的天賦,Vite 與生俱來的 PostCSS 魔法」提到:plugins 設定接受兩種寫法:[] 與 {},這篇將分享這兩種寫法的使用方式,為方便觀察效果,我們先寫幾個檔案:
plugin/my-plugin.js
const plugin = (opt = {hi: ':)'}) => {
return {
postcssPlugin: 'my-plugin',
Comment: comment => comment.text = opt[comment.text] ?? comment.text,
}
}
plugin.postcss = true
export default plugin
當註解文字是我想攔截的內容時,替換成對應的字串。
index.html
<style>
/* hi */
/* hello */
/* world */
</style>
即將被我修改的 css 註解。
[]接收三種參數:
plugins: [{ postcssPlugin: '??' }]。plugins: [() => ({ postcssPlugin: '??' })]。.postcss = true。postcss 會在內部執行該函式,以獲取插件設定物件。plugins: ['npm 包名', '/xxx/plugin.js']。postcss-load-config 中明確寫著 When using an {Array}, make sure to require() each plugin,也就是不支持 string[]。postcss-loader 官方文件中可以找到這種寫法,因為 postcss-loader 的設定檔獲取機制是自己寫的,不是用 postcss-load-config,當解析到插件為字串時,會嘗試 require() 該字串來讀取內容,所以當傳入 npm 包名時,會去 node_modules 查找。postcss.config.js
import myPlugin from './plugin/my-plugin.js'
/** @type {import('postcss-load-config').Config} */
export default {
plugins: [
myPlugin,
myPlugin({hello: ':))'}),
],
}
plugin:
postcss 會自動執行該函式,此時 opt 為預設值 {hi: ':)'}。hi 時,改為 :)。plugin({hello: ':))'}):
plugins 的是返回的插件設定物件。hello 時,改為 :))。結果
% npx vite build && cat dist/index.html
# ...
dist/index.html 0.05 kB │ gzip: 0.05 kB
<style>
/* :) */
/* :)) */
/* world */
</style>
hi 變成 :)。hello 變成 :))。key:npm 包名或插件絕對路徑。
the key can be a Node.js module name, a path to a JavaScript file that is relative to the directory of the PostCSS config file, or an absolute path to a JavaScript file.
value:傳給插件的參數。
boolean,表示啟用或不啟用插件的意思,不會將 boolean 傳入插件中。postcss.config.js
import {dirname, join} from 'path'
import {fileURLToPath} from 'url'
const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url)) ?? ''
const myPluginPath = join(__dirname, './plugin/my-plugin.js')
/** @type {import('postcss-load-config').Config} */
export default {
plugins: {
[myPluginPath]: {
hi: ':)',
hello: ':))',
},
},
}
結果
% npx vite build && cat dist/index.html
# ...
dist/index.html 0.05 kB │ gzip: 0.05 kB
<style>
/* :) */
/* :)) */
/* world */
</style>
hi 變成 :)。hello 變成 :))。string[]為了版面的簡潔,就不額外寫完整的 webpack 範例了,有興趣可到「讓 webpack 老大哥,學會使用 postcss 魔法熔爐」中拿該篇的範例來改~
import {dirname, join} from 'path'
import {fileURLToPath} from 'url'
const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url)) ?? ''
const myPluginPath = join(__dirname, './plugin/my-plugin.js')
/** @type {import('postcss-load-config').Config} */
export default {
plugins: [
myPluginPath,
],
}
總之,這樣寫在 webpack + postcss-loader 是可行的。
以上就是 plugins 兩種寫法的具體使用方式,我個人推薦 [],因為 array 的執行順序較為穩定可控。
我們經歷了一段漫長的 postcss 分享後,大概知道 postcss 的使用方式了,下篇我們將開始嘗試解決最初的目標:「在 css 屬性值寫函式呼叫,並將函式運算後,用結果替換函式呼叫的字串」,下篇見囉~
string[]上面提到 postcss-loader 支持 string[] 來動態載入插件,這邊補充相關程式碼片段讓你參考~
function loadPlugin(plugin, options, file) {
try {
let loadedPlugin = require(plugin);
if (loadedPlugin.default) {
loadedPlugin = loadedPlugin.default;
}
if (!options || Object.keys(options).length === 0) {
return loadedPlugin;
}
return loadedPlugin(options);
} catch (error) {
throw new Error(
`Loading PostCSS "${plugin}" plugin failed: ${error.message}\n\n(@${file})`,
);
}
}
// ...
if (typeof plugin === "string") {
return loadPlugin(plugin, options, file);
}
當插件為字串時,他會試著 require() 來用。
{} 啟用與否不論在 postcss-load-config 或是 postcss-loader 中,都會將 {} 值為 false 的 key 給移除,而不是執行該插件後傳入 false。
postcss-loader 程式碼片段
if (Array.isArray(config.plugins)) {
list = config.plugins.filter(Boolean)
} else {
list = Object.entries(config.plugins)
.filter(([, options]) => {
return options !== false
})
.map(([plugin, options]) => {
return load(plugin, options, file)
})
list = await Promise.all(list)
}
postcss-load-config 程式碼片段
const objectPlugins = Object.entries(plugins);
for (const [name, options] of objectPlugins) {
if (options === false) {
listOfPlugins.delete(name);
} else {
listOfPlugins.set(name, options);
}
}
{} 的執行順序{} 在內部使用 Object.entries(plugins) 依序執行(可參考「{} 啟用與否」的程式碼片段),雖說大部分情況下都會依既定的排序規則,但非常有可能在某個運行環境不是這樣,此時也只能問天問地問 safari 😃。
另外補充冷知識:Object.entries、Object.keys、Object.value 的既定排序規則:
number:按數值大小排在最前面。string:按寫入順序排在數字後面。Symbol:
Object.entries、Object.keys、Object.value 都會排除非字串類型的 key,所以 Symbol 不會被列在裡面。Reflect.ownKeys() 來獲取所有 key,而 Symbol 會按寫入順序排在字串後面。const obj = {
b: {},
[Symbol(2)]: {},
2: {},
a: {},
[Symbol(1)]: {},
c: {},
1: {},
}
console.log(
Object.keys(obj),
// ['1', '2', 'b', 'a', 'c']
Reflect.ownKeys(obj),
// ['1', '2', 'b', 'a', 'c', Symbol(2), Symbol(1)]
)