webpack 學習筆記:使用 babel(下)

zhangbao發表於2020-09-29

本篇文章進一步探討,如何在 webpack 中使用 babael 對原始碼做處理。

回顧

上一節中,我們使用瞭如下的配置來處理原始碼:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // ......
    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        plugins: ['@babel/plugin-proposal-class-properties']
                    }
                }
            }
        ]
    },
    mode: 'none'
};

babel 提供的 @babel/preset-env 的預置包,只能處理包含在 ECMAScript 2020 標準中(截止到2020.09)的所有新語法特性,如果原始碼中包含 class (public) field declarations 特性(Stage 3),就要額外引入 @babel/plugin-proposal-class-properties 來實現該特性的正確轉碼。

預設 @babel/preset-env 會將 ES2015-ES2020 原始碼全部轉換為 ES5 程式碼

那麼是否掌握這些就足夠了呢?不是。還有兩個最佳化點,值得我們學習。

  1. 一個是與 browserslist 的整合
  2. 還有一個,就是要解決 polyfills 的引入問題

與 browserslist 的整合

@babel/preset-env 提供了一個 targets 選項,支援以 browserslist query 的形式,指定專案要相容的瀏覽器版本(或者 Node 版本)。這樣就可以依據要相容的瀏覽器範圍,有選擇性的轉碼,達到最小化轉換程式碼,得到更小的打包體積。

比如,像下面這樣設定:

presets: [
    [
        '@babel/preset-env',
        {
            targets: '> 0.25%, not dead',
        }
    ]
],

不過,與 browserslist 行為不同的是,如果未指定 targets,@babel/preset-env 並不會使用預設的 defaults 關鍵字,而只是粗暴地將所有 preset-latest 所包含的語法特性全部轉換為相容 ES5 的,沒有考慮瀏覽器範圍。

當然,不指定這樣 targets 是非常不推薦的。推薦在專案中顯式指定 targets 的值,即使是 defaults:

{
    // 等同於 browserslist 的 `defaults`,等價於設定了
    // targets: '> 0.5%, last 2 versions, Firefox ESR, not dead',
    targets: 'defaults',
}

defaults 還可與其他 query 一起使用:

{
    targets: 'defaults, not ie 11, not ie_mob 11',
}

解決 polyfills 的引入

@babel/preset-env 解決了語法轉碼的問題,但是 API 的支援還需要引入額外的 polyfill。舉一個例子,如果專案程式碼需要相容 IE9+ 瀏覽器,但程式碼中使用了 Primise API,這個 API 是 IE9 環境中不支援的,語法轉碼時,也不會做處理,如果不引入 Promise polyfill,那麼執行到此的時候,程式碼必然是會報錯的。

@babel/preset-env 中兩個選項,專門負責 polyfill 引入。

  1. useBuiltIns:配置 @babel/preset-env 如何處理 polyfill 引入,最常用的值是 ‘usage’,即按需引入
  2. corejs:引入的 polyfill 的程式碼來源

舉一個例子:

presets: [
    [
        '@babel/preset-env',
        {
            targets: 'defaults, not ie 11, not ie_mob 11',
            useBuiltIns: 'usage',
            corejs: { version: 3, proposals: true }
        }
    ]
],

useBuiltIns: 'usage' 表示根據原始碼,按需引入 corejs 提供的 polyfill 檔案;corejs: { version: 3, proposals: true } 則表示使用的是 corejs 的 v3 版本,而且支援對 ECMAScript proposals 中涉及到的特性的 polyfill 的引入。

實戰案例

接下來,我透過幾個例子來說明 targetsuseBuiltInscorejs 這三個選項的使用。

先來看看專案結構:

webpack-demo/
│  webpack.config.js
│
└─src
        index.js
        template.html    

安裝依賴:

$ npm i -D webpack webpack-cli html-webpack-plugin
$ npm i -D babel-loader babel/core @babel/preset-env @babel/plugin-proposal-class-properties core-js

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            // See: https://webpack.js.org/loaders/babel-loader/
            {
                test: /\.m?js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            [
                                '@babel/preset-env',
                                {
                                    // 稍後填入
                                }
                            ]
                        ],
                        plugins: [
                            '@babel/plugin-proposal-class-properties'
                        ]
                    }
                }
            }
        ]
    },
    plugins: [
        // See: https://webpack.js.org/plugins/html-webpack-plugin/
        new HtmlWebpackPlugin({ template: './src/template.html' })
    ],
    mode: 'none'
};

index.js:

class Bar {
    set publicVar(x) { console.log(x) }
}

class Foo extends Bar {
    publicVar = 0
    #privateVar = 0

  getPrivateVar() {
      return this.#privateVar
  }
}

const foo = new Foo()

foo.publicVar
foo.getPrivateVar()

template.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body></body>
</html>

一、先配置如下:

{
    // See: https://babeljs.io/docs/en/babel-preset-env#no-targets
    targets: 'defaults',
}
$ npx webpack --watch

......
Built at: 2020-09-28 11:26:25 ├F10: PM┤
     Asset       Size  Chunks             Chunk Names
 bundle.js   7.97 KiB       0  [emitted]  main
index.html  244 bytes          [emitted]  

列印出來的 bundle.js t大小為 7.97 KiB。

二、接下來按需引入 polyfills:

{
    targets: 'defaults',
    // https://babeljs.io/docs/en/babel-preset-env#usebuiltins
    useBuiltIns: 'usage',
    // https://babeljs.io/docs/en/babel-preset-env#corejs
    corejs: { version: 3, proposals: true }
}
......
Built at: 2020-09-28 11:28:13 ├F10: PM┤
     Asset       Size  Chunks             Chunk Names
 bundle.js    106 KiB       0  [emitted]  main
index.html  244 bytes          [emitted]

因為 corejs 會引入一些內部的核心的工具函式,打包體積一下子變大到 106 KiB。

三、去掉對 IE 的支援

{
    targets: 'defaults, not ie 11, not ie_mob 11',
    useBuiltIns: 'usage',
    corejs: { version: 3, proposals: true }
}
Built at: 2020-09-28 11:30:55 ├F10: PM┤
     Asset       Size  Chunks             Chunk Names
 bundle.js   47.9 KiB       0  [emitted]  main
index.html  244 bytes          [emitted]  

看到,包體積一下子回落到 47.9 KiB 的大小。

四、構建生產環境包

基於【三】中的配置,我們再調整下 webpack mode 為 ‘production’,來看看最終構建出來的生產包的尺寸:

module.exports = {
    // ......
    mode: 'production'
}
Built at: 2020-09-28 11:34:50 ├F10: PM┤
     Asset       Size  Chunks             Chunk Names
 bundle.js   14.3 KiB       0  [emitted]  main
index.html  209 bytes          [emitted]

尺寸減少為 14.3 KiB,算是可以接受的範圍了。

五、取消 corejs 的 proposals

如果我們沒有使用新的 ECMAScript proposals API 的需求,可以再進一步修改配置,不啟用 corejs 的 proposals 選項(置為 false)。

{
    targets: 'defaults, not ie 11, not ie_mob 11',
    useBuiltIns: 'usage',
-    corejs: { version: 3, proposals: true }
+    corejs: { version: 3, proposals: false }
}
Built at: 2020-09-28 11:42:26 ├F10: PM┤
     Asset       Size  Chunks             Chunk Names
 bundle.js   13.8 KiB       0  [emitted]  main
index.html  209 bytes          [emitted]

最終尺寸可達到 13.8 KiB!

(完)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章