从Babel入门到Babel入门

773 阅读8分钟

图片 1.jpg

人类互相理解,齐心协力
建巴别通天塔
上帝迁怒与人类有通天之欲
令人类有万种语言
巴别塔顷刻瓦解

——《圣经》“创世纪”第十一章

前言

平时我们开发时常常需要写类似这种功能的代码:

render(){
  return <div>
    {
      this.state.data.users.id?
        <div>{this.state.data.users.id}</div>:null
    }
  </div>
}

当然,这个代码绝对是跑不起来的,在第一次渲染时就会因为没有数据,出现Cannot read property 'users' of undefined的问题

这种问题自然是要去解决的,我们可以写成这样:

fun=()=>{
  if(this.state.data){
    if(this.state.data.users){
      return <div>{this.state.data.users.id}</div>
    }
  }
}

render(){
  return <div>
    {
      this.fun()
    }
  </div>
}

当然,这样很不优雅,我们应该写成这样:

render(){
  return <div>
    {
      this.state.data&&this.state.data.users&&this.state.data.users.id?
        <div>{this.state.data.users.id}</div>:null
    }
  </div>
}

好像可读性还是不够,如果我们能使用es10的可选链:

render(){
  return <div>
    {
      this.state.data?.users?.id?
        <div>{this.state.data.users.id}</div>:null
    }
  </div>
}

无比优雅的代码!

当然,es10自然是无法直接使用的,需要配置babel,我们接下来查一下plugin,度娘说直接在.bebelrc中plugin加入
"@babel/plugin-proposal-optional-chaining"和
"@babel/plugin-proposal-nullish-coalescing-operator"就行。

项目跑起来,意料之中的提示缺少npm包,三下五除二装完包,意料之外的问题来了,报错Error: Requires Babel “^7.0.0-0“, but was loaded with “6.26.3“
这个问题当时困扰了我,当然其实本质原因是babel/core和babel-core冲突的问题,更本质的说是babel6和babel7的问题,这也是后话了,正好我对babel的基础不是很熟悉,再去从babel的基础学一遍吧!

babel是什么

工欲善其事,必先利其器。
——鲁迅

babel 最开始叫 6to5,顾名思义,功能是 es6 转 es5。我们知道,es 版本一年一代,有了 es7(es2016)、es8(es2017)等之后,显然,6to5 的名字已经不合适了,所以 6to5 改名为了 babel。

image.png

babel的运行有三个阶段

1. parse 解析阶段对代码进行解析,生成AST语法树,这个流程主要由@babel/parser完成

2. transform 转换阶段将 AST 语法树转换为目标的 AST 语法树,主要由@babel/traverse遍历节点,具体的 AST 转化根据babel.config.json中配置的presets、plugins处理

3. generate 生成阶段将 AST 生成代码,并生成sourcemap,主要由@babel/generator完成

babel怎么用

babel的配置(babel.config.json或.babelrc)中有两种,分别是presets和plugins。

比如如需要转换箭头函数,我们可以添加
"plugins": [ "@babel/plugin-transform-arrow-functions"]
但是这么做就有一个问题,随着代码中使用的 ES6+ 规则越来越多,plugins 中的规则也会越来越多,导致plugins非常的长长长长长。这个时候 presets 就起作用了。
bebel 6 中引入了 presets 的概念,预设(presets)大大帮助我们简化了 plugins 中配置的数量。在 babel6 中,如果我们想用 es6 语法就引入babel-preset-es2015,es7 就引入 babel-preset-es2016 等等。如果是想用还没加入标准的特性,可以用 babel-preset-stage0、babel-preset-stage1 等来引入

image.png

显然,这种命名方式存在一定的问题,这些babel-preset-stage0、babel-preset-stage1等集成的未加入标准的特性会加入标准,这样命名不是很好;另外,用户的浏览器已经可以使用高级的语法,多做转化可能会多此一举。

babel7就解决了这些问题。

  • babel 7废弃了preset-2015和preset-stage-x 的 preset 包,而换成了preset-env。

  • preset-env 默认会支持所有es标准的特性,如果没进入标准的,也不再封装成preset,需要手动引入plugin-proposal-xxx。

  • babel7 允许指定浏览器版本。

  • 使用babel7后,实际开发中,大多数时候直接使用@babel/preset-env,就能将语法转换到目标环境能支持的语法。

  • 配置中可以不写target,这样会让所有es6-es10转化为es5兼容。但是官方不推荐这么做,因为指定浏览器版本是一个很强大很好用的功能(官方觉得不用太浪费了)。

更多的可以去官方文档中查看,我这边列一个比较常见的配置

"presets": [ 
  [ "@babel/preset-env", {
      "targets": "> 0.25%, not dead", //指定浏览器版本 
      "useBuiltIns": "usage", //按需引入polyfill 
      "corejs": 3 // useBuiltIns必须要配合corejs使用 
    } 
  ]
]

注:通过useBuiltIns+ browserlist实现按需引入

其他:

其实以上就是当前版本的babel的主要内容,概括的说就是去了解一下babel干了什么,怎么用babel。下面我整理了一些其他遇到的问题。

令人困扰的babel-polyfill、babel6迁移babel7指南、babel7新特性、babel8(coming soon)新特性 以及更多

  • 集成包babel-polyfill(@babel/polyfill)

babel-polyfill 其实是对 core-js 和 regenerator 的封装,与 babel/runtime 下的 core-js 区别是
babel-polyfill 支持实例方法,类似Array.includes()这种的转换会污染全局实例。所以官方建议不要在提供给他人使用的模块中使用 babel-polyfill。同时也因为这个原因,@babel/polyfill在7.4.0中已弃用

core-js:用es3 实现的包含 es5及es6+提案的库
regenerator-runtime:解析异步的库

  • babel7新特性
  1. 不再支持放弃维护的 node 版本 0.10、0.12、4、5 ;
  2. 舍弃了以前的 babel-- 的命名方式,使用 @babel 命名空间,如 @babel/core (babel6中为babel-core,这也是上文遇到问题的原因);
  3. @babel/preset-env 代替 preset-es2015 等 ;
  4. plugins内的包名改为 -proposal,代替 -transform ;
  5. 针对面向用户的包(如 babel-loader、@babel/cli)在@babel/core中引入peerDependencies(用于控制版本,使用率极低)
  6. Babel 7 在编译速度上表现的更加优秀,并且加了一些新的功能,如支持编译 TypeScript;支持 JSX Fragment(针对 React 的 Fragment 特性);支持自动引用 polyfill 等
  • .babelrc和babel.config.js区别

babel 会在根目录搜索 babel.config.js或babel.config.json(7.8.0以上)作为全局配置。然后在编译一个具体的js文件时会去搜索目录结构向上搜索最近的一个.babelrc.json,将其与全局配置合并。

image.png

如上图,.babelrc的配置管不到common/utils.js,如果换成babel.config.json,其中的配置就可以应用到common/util.js

其实大多数情况下都没什么区别...

  • babel6迁移babel7指南

虽然这个自标题很大,但是事实上迁移操作很简单,而且babel的版本迭代大多数情况下不会出问题(也可能会出,我就遇到过一个废弃的语法的plugin,babel更新后直接跑不起来)

实际操作: 进行.babelrc配置后(见上文或官方文档),按报错安装npm包(如@babel/preset-env,@babel/core, @babel/preset-react)

常见问题: Module build failed: Error: Requires Babel "^7.0.0-0", but was loaded with "6.26.3". If you are sure you have a compatible version of @babel/core, it is likely that something in your build process is loading the wrong version. Inspect the stack trace of this error to look for the first entry that doesn't mention "@babel/core" or "babel-core" to see what is calling Babel. (While processing preset: "/path")

翻译:如果您确信您有一个兼容版本的@babel/core,那么很可能是构建过程中加载了错误的版本。检查此错误的堆栈跟踪以查找第一个没有提到“@babel/core”或“babel-core”的条目,以查看调用babel的内容

原因:babel7的包更名为babel/core,有些包用的是babel6的babel-core,有所冲突,需要一个bridge

解决方案:更新babel-core至bridge版本

image.png

npm i babel-core@^7.0.0-bridge.0 @babel/core regenerator-runtime -D

  • babel8 coming soon!

We have been talking about the Babel 8 release for more than one year (we initially scheduled it about one year ago)! However, we are now closer then ever to it's release!

我们已经讨论发布Babel 8一年多了(我们大约在一年前就计划好了)!并且,我们现在离它的发布更近了!

目前的babel团队遇到的一些障碍:

  1. 考虑放弃对Node.js 10的支持,因为该版本将于2021年4月30日停止维护;
  2. 希望能将Babel作为纯ESM包发布,现在正在将babel源码与Node.js的ESM兼容,同时,正在研究如何通过Babel更容易更便捷地将ESM编译为CJS;
  3. 正在尝试使TypeScript AST与typescript-eslint保持一致。目前的 AST 几乎完全相同,但仍需一些小的突破性更改来让它们完全一致;
  4. 发布基建不支持预发布,不支持使用多个“主”分支(一个用于 Babel 8,一个用于 Babel 7);
  5. 正在考虑Babel 7的维护策略。 可能进行的改动:
    移动@babel/preset-env直接进入@babel/core
    这样改动的两大优点:
    1.在简单的项目中配置 Babel 会更容易,你只需要启用一个compileJS: true选项babel.config.json
    2.它将确保插件版本与@babel/core版本同步,避免大多数由不匹配/不兼容包版本引起的错误 解决迁移至ESM后遇到的一些问题

结语

由于笔者文笔不佳,很多地方的阐述较为混乱,后续也会逐渐修改;文中技术内容也会因为自身技术所限有失偏颇,还望大佬指点。