前端小纠结--babel的配置的默认套路

3,937 阅读6分钟

我是一个标题党,这篇文章介绍一点babel的默认配置套路,总结了一点遇到的项目管理问题。

babel的bug刺激了我

  • 现象:项目编译没有任何问题,打开页面才报错 问题现象console报错:_objectSpread is not defined
  • 原因:根据输出的错误,我猜测是babel的问题,因为_objectSpread就是使用了新的对象展开的语法...obj,编译之后才出现的。当然这里我也调试了编译后打代码。【如果你想看编译打包后代码,介绍一篇文章给你: webpack编译vue项目生成的代码探索
  • 分析:通过git log快速分析package.json的变更,发现只更新过babel相关。所以实锤是babel问题。

分析git log时,我们当时做的commit message style统一的作用就发挥出来了,所有的工具链相关变更,统一都是chore开头,所以才能做到快速定位。顺便看看我们项目的commit message

commit message style

真实原因:对象展开的语法,babel编译之后注入了_objectSpread或者_objectSpread2函数来处理,维护的小伙伴没有把所有的分支都覆盖。修复这个issues的小伙伴也挺幽默,估计也是被折腾的够呛,说了一句玩笑话I'll now go hiding myself somewhere and I won't touch an helper for a few months 😆

而且此bug是babel v7.5.4修复的,而且在低版本的@babel/core使用了高版本的@babel/plugin-proposal-object-rest-spread才能重现。

_objectSpread官方issues相关链接:

ReferenceError: _objectSpread is not defined after update

Missing helpers only throw once

Fix _objectSpread2 for real

找的问题的根源了,新问题来了,我怎么更新@babel/core呢?

因为不是项目直接依赖的@babel/core,而是其它工具依赖,我更新了也不一定起作用。

介绍npm两个非常有用的命令:

  • 查看项目中安装的package版本和依赖关系

    npm ls @babel/core
    `-- @vue/cli-plugin-babel@3.9.2
      `-- @babel/core@7.5.4 
    

    知道了依赖@babel/core是@vue/cli-plugin-babel,那升级@vue/cli-plugin-babel就行了。

  • 查看npm仓库的package版本和依赖

    npm show @babel/core
    
  • 推荐一个npm-check工具,专门用来更新依赖。

    npm-check

    可以执行npm-check -u来进行交互式选择更新,而且还列出了官方文档,可以直接看看release log之后再决定是否更新。

babel的plugin和preset配置默认套路

本来我就比较懒,一直在计划学习的路上,基本都没落实行动。这次项目出现问题,我不得不去了解了一下,babel配置的那些套路。Babel的v7版本所有包都重命名了,所以还是要做到了解。

babel v7

官方升级文档链接

包含的内容太多,仔细过一遍才行,很多配置或者包名称命名规则都修改了。

babel v7.4支持core-js@3

babel v7.4支持core-js@3,尤其是对@babel/polyfill@babel/preset-env又带来了很多改变。

  • 新的corejs配置项

    corejs配置项链接options corejs

    当项目中的babel升级到v7.4+时会出现下面的警告:

    WARNING: We noticed you're using the useBuiltIns option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the corejs option.

    // 使用core-js@3的babel.config.js
    module.exports = function (api) {
        api.cache(true);
    
        const presets =  [
            ["@babel/preset-env",
                {
                "useBuiltIns": "usage",
                "corejs":3, // 指定版本
                "targets":{
                    "browsers":["> 1%", "last 2 versions", "not ie <= 8"]
                    }
                }
            ]
        ];
        return {
            presets,
            // plugins
        };
    }
    

    注意: 把相应的core-js安装到项目依赖中,npm i core-js@2 或者npm i core-js@3

  • @babel/polyfill不支持从core-js2升级到core-js3,所以v7.4开始@babel/polyfill变成过时(deprecated)

    如果项目中使用core-js@3,则应该修改导入配置

    import "@babel/polyfill";
    

    替换为:

    import "core-js/stable";
    import "regenerator-runtime/runtime";
    

    直接安装依赖到项目:

    npm i --save core-js regenerator-runtime
    

因为babel和core-js绑定的很紧密,所以推荐看一下core-js作者的文章:core-js-3-babel-and-a-look-into-the-future详细介绍了core-js@3版本带来的改变,以及解决的问题。

Plugin配置套路

syntax plugins允许babel去parse特定的类型的syntax而不是去转换变形(transform)

注意:transform plugins会自动启用syntax plugins。 因此,如果已经使用了相应的transform plugins,则无需指定syntax plugins。

Plugin 路径

如果 plugin 在 npm 上,你可以传入预设名称,babel 将检查它是否已安装在 node_modules

{
  "plugins": ["babel-plugin-myPlugin"]
}

还可以指定的相对/绝对路径。

{
  "plugins": ["./node_modules/asdf/plugin"]
}

Plugin 简写(官方非常不推荐)

如果包的名称以 babel-plugin- 为前缀,可以使用简写:

{
  "plugins": [
    "myPlugin",
    "babel-plugin-myPlugin" // equivalent
  ]
}

这也适用于 scoped 包:

{
  "plugins": [
    "@org/babel-plugin-name",
    "@org/name" // equivalent
  ]
}

Plugin执行顺序

如果两个transforms都访问“程序”节点,则transforms将以plugin或preset顺序运行。

  • plugin在preset之前运行。
  • plugin执行顺序是第一个到最后一个。
  • preset顺序相反(从最后到第一个)。
{
  "plugins": ["transform-decorators-legacy", "transform-class-properties"]
}

先执行transform-decorators-legacytransform-class-properties

Plugin 选项

plugins和 presets 都可以通过将名称和选项对象放在配置中的数组中来指定选项。

对于不指定选项,这些都是等同的:

{
  "plugins": ["pluginA", ["pluginA"], ["pluginA", {}]]
}

要指定选项,请使用选项名称作为 key 传递对象。

{
  "plugins": [
    [
      "transform-async-to-module-method",
      {
        "module": "bluebird",
        "method": "coroutine"
      }
    ]
  ]
}

preset配置套路

preset是一个插件数组

module.exports = function() {
  return {
    plugins: [
      "pluginA",
      "pluginB",
      "pluginC",
    ]
  };
}

Presets 可以包含其他的 presets 以及带有选项的插件。

module.exports = () => ({
  presets: [
    require("@babel/preset-env"),
  ],
  plugins: [
    [require("@babel/plugin-proposal-class-properties"), { loose: true }],
    require("@babel/plugin-proposal-object-rest-spread"),
  ],
});

Preset 路径

如果 preset 在 npm 上,你可以传入预设名称,babel 将检查它是否已安装在 node_modules

{
  "presets": ["babel-preset-myPreset"]
}

还可以指定 presets 的相对/绝对路径。

{
  "presets": ["./myProject/myPreset"]
}

Preset 简写(官方非常不推荐)

如果包的名称以 babel-preset- 为前缀,可以使用简写:

{
  "presets": [
    "myPreset",
    "babel-preset-myPreset" // 等同
  ]
}

这也适用于 scoped 包:

{
  "presets": [
    "@org/babel-preset-name",
    "@org/name" // 等同
  ]
}
// vue-cli生成的babel.config.js
module.exports = {
  presets: [
    [
      '@vue/app', // 对应 @vue/babel-preset-app,当时害的我找来找去都没找到包
      {
        useBuiltIns: 'entry',
        corejs: 2, // @see https://babeljs.io/docs/en/babel-preset-env#corejs
      },
    ],
};

Preset执行顺序相反

Preset 的顺序是相反的(从最后一个到第一个).

{
  "presets": [
    "a",
    "b",
    "c"
  ]
}

将会按照以下顺序运行:c, b, 然后 a

Preset 选项

插件和 presets 都可以通过将名称和选项对象放在配置中的数组中来指定选项。

对于不指定选项,这些都是等同的:

{
  "presets": [
    "presetA",
    ["presetA"],
    ["presetA", {}],
  ]
}

要指定选项,请使用选项名称作为 key 传递对象。

{
  "presets": [
    ["@babel/preset-env", {
      "loose": true,
      "modules": false
    }]
  ]
}

ant-desgin-vue的依赖加载配置:

module.exports = {
  presets: [
    [
      '@vue/app',
      {
        useBuiltIns: 'entry',
        corejs: 2, // @see https://babeljs.io/docs/en/babel-preset-env#corejs
      },
    ],
  ],
 
  plugins: [
    [ // @see https://github.com/vueComponent/ant-design-vue/issues/193
      'import', // babel-plugin-import
      { libraryName: 'ant-design-vue', libraryDirectory: 'es', style: true },
    ],
  ],
};

总结

前端的知识链太长,技术更新越来越快。前端的投资风险还是很大,所以谨慎选择框架,分配一点时间投资在业界公认的工具上也是不错的,例如:webpack, babel, vs code, scss等。这些都是现代前端工程的构建工具,所以生命周期会比某一个框架更长远。

参考

babel-plugin-handbook

babel-user-handbook

强制jest使用新的babel

options corejs

core-js-3-babel-and-a-look-into-the-future

babel-preset-env官方仓库

npm 常用命令详解

A Beginner’s Guide to npm — the Node Package Manager

关注微信公众号,发现更多精彩内容