webpack 学习笔记:使用 babel(下)

本篇文章进一步探讨,如何在 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 协议》,转载必须注明作者和本文链接