從源碼聊 postcss 和 babel 的 api 風格的不同
babel 是一個 js 到 js 的轉譯器,可以通過語法插件支持 es next、typescript、flow 等語法,支持 AST 轉換插件,最後生成目標代碼和 sourcemap。
postcss 是一個 css 到 css 的轉譯器,可以通過語法插件支持 less、sass、stylus 等語法,也支持 AST 轉換插件,最後生成目標代碼和 sourcemap。
postcss 之於 css,就像 babel 之於 js 一樣,postcss 的插件體系也很繁榮,比如 autoprefixer、stylelint 等。
這倆工具在定位上類似,都是用於代碼的轉譯和靜態分析。但是在 api 設計上有所不同。
學完這篇文章你會了解到:
-
babel、postcss 的 api 的簡易使用
-
babel、postcss 的編譯流程
-
postcss 的源碼架構
-
postcss 和 babel 在 api 設計上的不同
-
鏈式和集中式的 api 風格各有什麼好處
babel api
我們先從熟悉的 babel 開始,babel 7 的 api 一般這樣用:
const babel = require('@babel/core');
const code = `
console.log('hello world');
`;
const { code, map } = babel.transformSync(code, {
plugins: [
[
pluginA,
{
// options
}
]
],
sourceMaps: true
});
使用 babel core 包的 transformSync 的 api,傳入源碼和轉換插件。babel 會在內部完成源碼到 AST 的 parse,AST 的 transform,以及目標代碼和 sourcemap 的 generate 三個階段。
我們可以用 babel3 的源碼來了解下內部做了什麼(babel3 的源碼比較清晰,感興趣可以去看看):
首先,babel 通過 File 對象來保存處理的文件的源碼等信息,然後調用 parse
parse 方法裏面會完成 parse、transform、generate 這 3 步:
而 parse 在 babel 3 的時候還是直接使用了 acorn(babel 7 的 parser 是 fork 了 acorn 做的修改):
transform 階段會調用所有內置的 transformer 插件對 AST 進行轉換:
而 generate 階段會生成目標代碼和 sourcemap:
babel 後續做了很多的迭代,比如分成了很多包,但是這個流程是沒有變的。
我們有的時候也會直接用更底層的包,而不使用 babel core:
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const code = `
console.log('hello world');
`;
const ast = parser.parse(code);
traverse(ast, {
visitor: {
CallExpression(path) {
//xxx
}
}
});
const { code, map } = generate(ast, {
sourceMaps: true
});
其實 @babel/core 是基於 @babel/parser、@babel/traverse、@babel/generator 等包做的封裝,支持了插件能力。如果不需要插件,那麼直接用這些包就行。
postcss api
postcss 的 api 是這樣的:
const postcss = require('postcss');
const autoprefixer = require('autoprefixer')
const css = `
@import "aaa";
a {
background: color;
}`;
postcss([autoprefixer]).process(css).then(result => {
console.log(result.css)
})
首先調用 postcss 函數傳入插件數組,然後調用 process 方法處理傳入的 css,再調用 then 就可以在回調裏面拿到結果(目標代碼和 sourcemap)。
爲什麼會是這樣的順序呢,我們從源碼角度看一下:
首先調用的 postcss 方法返回 Processor 對象,而 Processor 對象有 use(應用插件)、process(處理 css) 等方法,所以纔是 postcss([]).process
然後我們可以看到 process 返回的值是 LazyResult 對象,而 LazyResult 對象會調用 parser 對傳入的 css 進行 parse,提供了 then 方法來返回結果,所以是 postcss([]).process(css).then(() => {})。
then 裏面會調用 aync,會應用插件,處理 AST(root),最終返回 stringifier 的結果。
stringifier 裏會調用 MapGenerator 把 AST 打印成目標 css,並生成 sourcemap。
這就是 postcss 的編譯流程。
postcss 其實依然是 parse、transform、generate 3 步,只不過 api 不同。
postcss 內部有這些類:
-
Processor
:傳入插件,返回 LazyResult,有 use(傳入插件)、process(返回結果) 方法 -
LazyResult
:傳入源碼,調用 Parser 對源碼進行 parse,有 then(異步獲取結果)方法,會用插件對 AST 轉換,然後調用 MapGenerator 打印 AST -
Parser
:傳入源碼,返回 AST -
MapGenerator
:傳入 AST,打印成目標代碼,並生成 sourcemap。
再回到開始,我們調用的方式是
postcss([autoprefixer]).process(css).then(result => {
console.log(result.css)
})
其實 postcss 就是個工廠函數,用於創建 Processsor 對象,Processor 對象的 process 方法返回 LazyResult 對象,LazyResult 對象有 then 方法來返回結果。
鏈式和集中式的 api 風格
postcss 和 babel 都是對代碼(js、css)的轉譯,但是 api 卻差別挺大,來對比下:
babel 是:
const { code, map } = babel.transformSync(code, {
plugins: [
[
pluginA,
{
// options
}
]
],
sourceMaps: true
});
postcss 是:
postcss([autoprefixer]).process(css).then(result => {
console.log(result.css)
})
可以看到 babel 只有一個 api,一次性傳入源碼和插件,在內部完成 parse、transform、generate 3 個階段。
postcss 則是提供了鏈式的 api,分多步傳入插件和源碼。
postcss 這種鏈式的風格的好處是代碼緊湊,每一步做了什麼都比較明確。babel 這種一次性傳入的好處是使用簡單,但是 option 耦合在一塊,複雜度高。
鏈式的多次傳入,還是集中式的一次傳入,都有自己的好處,比如 jquery、chalk、jest、webpack-chain 都是鏈式的,而 webpack、babel 等都是一次性傳入的。
jest api 風格(鏈式):
test(`jest`, () => {
expect(a + b).not.toBeLessThan(expected);
}
chalk api 風格(鏈式):
chalk.rgb(123, 45, 67).underline('Underlined reddish color')
webpack-chain api 風格(鏈式):
config
.entry('index')
.add('src/index.js')
.end()
.output
.path('dist')
.filename('[name].bundle.js');
webpack api 風格(集中式):
webpack({
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
})
總結
本文我們通過 babel 和 postcss 的 api 用法分別探究了下內部的實現流程,知道了爲什麼 postcss 是 postcss([plugins]).process(css).then 的調用鏈。
之後分別梳理了 babel 的集中式風格 api、postcss 的鏈式風格 api 的優缺點,並且引申出了其他的一些庫的 api 風格。知道了其實做同一件事情是可以設計出不同的 api 的。
希望這篇文章能夠讓你清楚 postcss 和 babel 內部的流程都是啥,爲什麼 postcss 的 api 調用鏈條是那樣的,它們都是什麼 api 風格。鏈式和集中式都有什麼優缺點。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ONmOD2Tpi0ABXAwowIhYdQ