從源碼聊 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 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 的 parseAST 的 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";{
   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 內部有這些類:

再回到開始,我們調用的方式是

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