手把手教你如何配置Babel系列(一)

目标

本文的目的是帮助对Babel不熟悉的人了解 如何书写Babel的配置文件,设计一个“最完美”的 Polyfill 方案

由于内容比较多,我会把下面的目录中的内容分成四个章节去讲解

第一章节

  • 什么是Babel
  • 插件和预设
  • 配置文件
  • 简单Demo演示

第二章节

  • 什么是core-js?

第三章节

  • 什么是Polyfill?
  • @babel/polyfill介绍
  • @babel/preset-env实战

第四章节

  • @babel/runtime实战
  • @babel/plugin-transform-runtime实战
  • @babel/core介绍
  • @babel/cli介绍

文章中展示的demo都会添加源码链接

├── README.md
├── lerna.json
├── package.json
└── packages
    ├── tutor-basic
    ├── tutor-polyfill01
    ├── tutor-polyfill02
    ├── tutor-polyfill03
    ├── tutor-polyfill04
    ├── tutor-polyfill05
    ├── tutor-preset_env
    ├── tutor-preset_env02
    ├── tutor-preset_env03
    ├── tutor-preset_env04
    ├── tutor-preset_env05
    ├── tutor-preset_env06
    ├── tutor-preset_env07
    ├── tutor-runtime01
    ├── tutor-runtime02
    ├── tutor-runtime03
    └── tutor-runtime04

前言

本章节内容只要介绍一下 老生长谈东西——Babel的一些概念,思考以下问题:需要记住的内容:

  • 为什么要编译?
  • 插件和预设的区别和关系?
  • 插件和预设解析顺序?
  • 没有配置预设和插件转换结果是什么?
  • 目标环境配置表是什么东西?

什么是Babel?

Babel is a JavaScript compiler.

Babel是一个Javascript编译器。Babel官网解释很简单,但是想要真正了解Babel确实不简单。

为什么要编译转换?

究其原因,前端语言特性(esnexttypescriptflow等)和宿主环境(浏览器/Node.js)高速发展,宿主环境无法及时支持新的语言特性。因此,需要把新的语言特性降级处理,转换成目标环境支持的语法,并且对目标环境不支持的API添加Polyfill

Babel对代码降级处理,可以概括为两部分:

语法转换——例如:箭头函数语法、async函数语法、class定义类语法、解构赋值等。
补齐新的API——例如:Array.prototype.includes,String.prorotype.includes,Promise、Map、Symbol等

Babel的编译流程

Babel是源代码目标环境源代码的转换,仍然是高级语言到高级语言的转换,整个过程可分为三个阶段:

  • 解析(parse)—— 通过@babel/parser把源代码字符串转成抽象语法树(AST)
  • 转换(transform)——通过@babel/traverse遍历抽象语法树(AST),并调用Babel配置文件中的插件,对抽象语法树(AST)进行增删改
  • 生成(generate)——通过@babel/generator把转换后的抽象语法书(AST)生成目标代码

图片

Babel官方的插件和预设的数量非常多,不过常用的插件和预设只有几个,接下来重点讲解这几个插件和预设,其他的可以举一反三,触类旁通。

插件(Plugin)

在转换的过程中,Babel插件会对抽象语法树(AST)进行增删改,如果不给Babel装上插件,抽象语法树将会不变,代码就会原样输出。正是因为有插件的存在, Babel才能对抽象语法书(AST)进行增删改,继而产生目标源代码。

插件分类

Babel插件大致分为以下两种:

  • 语法插件(syntax plugin)——作用于@babel/parser,解析特定类型的语法,将代码解析为抽象语法树(AST)。jsx,flow 转换插件(transform plugin)—— 负责对抽象语法树进行增删改

  • 语法插件虽名为插件,但其本身并不具有功能性。语法插件所对应的语法功能其实都已在@babel/parser里实现,插件的作用只是将对应语法的解析功能打开。

转换插件会自动启用相应的语法插件。因此,如果已经使用了相应的转换插件,则无需指定语法插件。是不是对这两种插件的区别有点懵逼?不用担心,继续往下看。

下文提及的 Babel 插件将专指转换插件

Babel@7官方有90多个插件,不过大半已经整合在@babel/preset-env@babel/preset-react@babel/preset-typescript等预设里了,一般情况下,我们在开发的时候直接使用预设就可以了。

预设是什么?

预设(Presets)
插件(plugin)只是对单个功能进行转换,当配置的插件比较多的时候,就可以封装成预设(Presets),来简化插件(Plugins)的使用的,预设即一组预先设定的插件。

目前Babel官网推荐的preset,有下面四个:

  • @babel/preset-env 所有项目都会用到的
  • @babel/preset-react react框架需要的
  • @babel/preset-flow flow需要的。Flow 是一个静态类型检测工具,进行类型检查,类似于ts
  • @babel/preset-typescript typescript需要的

其它的preset,如在Babel@6的时代,常见的babel-preset-es2015、babel-preset-es2016、babel-preset-es2017、babel-preset-latest、babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2 等这些从Babel@7开始已经不推荐使用

插件顺序

如果两个转换器都访问同一个“ Program ”节点,则转换器会按照以下顺序运行

  • 插件在 Presets 前运行
  • 插件可以指定从头到尾的顺序(数组坐标0权重最大)
  • Preset 顺序是相反的 (从后到前).

例如:

{
  "plugins": [
    "transform-decorators-legacy",
    "transform-class-properties"
  ]
}

将会运行 transform-decorators-legacy 然后是 transform-class-properties

关于 presets 一定要记住,顺序是相反的。如下:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

按以下顺序运行:@babel/preset-react 再运行 @babel/preset-env。

接下来我们会学习@babel/polyfill、@babel/preset-env、@babel/plugin-transform-runtime与等内容 从配置文件内容方面区分,Babel提供了两种配置内容。一种是由js 编写,通过module.exports={}方式输出对象。例如Vue的Babel配置文件内容

const babelPresetFlowVue = {
  plugins: [
    require('@babel/plugin-proposal-class-properties'),
    // require('@babel/plugin-syntax-flow'), // not needed, included in transform-flow-strip-types
    require('@babel/plugin-transform-flow-strip-types')
  ]
}

module.exports = {
  presets: [
    require('@babel/preset-env'),
    // require('babel-preset-flow-vue')
    babelPresetFlowVue
  ],
  plugins: [
    require('babel-plugin-transform-vue-jsx'),
    require('@babel/plugin-syntax-dynamic-import')
  ],
  ignore: [
    'dist/*.js',
    'packages/**/*.js'
  ]
}

这种有js编写的文件后缀都是.js。比如babel.config.js.babelrc.js

另外一种是直接书写 json内容 。例如 Element-UI的配置文件

{
  "presets": [],//省略了内容
  "plugins": ["transform-vue-jsx"]
}

此时文件后缀一般为.json或者没有 例如:babel.config.json.babelrc

两种类型的文件,一般都会放到项目根目录下面或者同package.json同目录。如果是一个由Lerna 管理的Monorepo类型的项目,这两种方式会有一些区别。如下

配置文件类型:

Babel 有两种并行的配置文件方式:

  • 项目范围的配置(Project-wide)
  • 文件相关的配置(File-relative)
    • .babelrc(和 .babelrc.js)文件
    • 带有 “babel” 键的 package.json 文件
Version Changes
v7.8.0 支持 .babelrc.mjs , babel.config.mjs
v7.7.0 支持 .babelrc,.babelrc.json, .babelrc.cjs, babel.config.json, babel.config.cjs

两种配置文件的区别,如果不明白可以暂时略过,不影响后面的学习,最后结束之后,在回来仔细推敲

项目范围的配置

Babel 7.x 中的新功能,Babel 具有 “root” 目录的概念,"root"目录默认为当前的工作目录。编译时,Babel将自动搜索相对于此根目录下的babel.config.js文件,或其Babel认可的文件,比如:babel.config.json,babel.config.cjs,babel.config.mjs等

优点

它们是非常合适并值得广泛应用的配置,它甚至允许plugins 和 presets 可以轻松应用于node_modules或符号链接包中的文件

缺点

因为它依赖于工作目录,如果一个项目属于Monorepo类型项目,当工作目录不是的根目录(root),编译时就找不到配置文件,如此一来在Monorepo中使用会比较痛苦。

babel.config.js
package.json
packages/
  mod1/
    package.json
    src/index.js
  mod2/
    package.json
    src/index.js

各个子模块构建的时候,用户将需要通过rootMode手动设置它的路径,以此来加载babel.config.js文件

CLI

babel --root-mode upward src -d lib
Webpack

module: {
  rules: [
    {
      loader: "babel-loader",
      options: {
        rootMode: "upward",
      },
    },
  ];
}

详细内容可以参考官方文档的演示地址: https://babeljs.io/docs/en/config-files#monorepos

目标环境配置表

大家可能在项目的根目录见过这么一个文件.browserslistrc,或者在package.json中见browserslist字段吧?

他的中文名字叫目标环境配置表,他的用户很大,需要引起足够的重视。

例如:Autoprefixer和Babel都是根据目标环境配置表提供的的目标浏览器的环境来,自动的添加css前缀,js的polyfill垫,而不是无脑的添加兼容,以提高代码的编译质量。

通俗的说,目标环境配置表告诉babel 目标浏览器有哪些,如果目标浏览器不支持某种API或者语法,babel会自动处理。

怎么修改和配置目标环境配置表呢?答案是看官网文档 browserslist ,或者更简单的方法去看一下vue-cli或create-react-app生成的项目里面怎么配置的。了解一下字段的意思,用到的时候在去改。

如果项目中没有配置目标环境配置表 browserslist ,可以指定默认配置

目标环境配置表 browserslist:https://github.com/browserslist/browserslist

默认配置:https://babeljs.io/docs/en/babel-preset-env#targets

文件相关配置

编译时,Babel 从 正在被编译的 文件 所在的 目录开始 去搜索 .babelrc.json或其他Babel认可的配置文件。比如:babelrc,.babelrc.js,/package.json#babel。有了这个功能,就可以为 package的子模块 创建独立的配置。同时,文件相关配置和项目相关配置可以共同使用。下面是官方的解释:

File-relative configurations are also merged over top of project-wide config values, making them potentially useful for specific overrides,
though that can also be accomplished through “overrides”.

通俗的翻译就是以下两点:

  • 不同的配置:文件相关配置 和 项目相关配置 可以合并到一起
  • 相同的配置:文件相关配置 会覆盖 项目相关配置
babel.config.js
package.json
packages/
  mod1/
    package.json
    src/index.js
    .babelrc
  mod2/
    package.json
    src/index.js
    .babelrc

实际项目中,Babel需要工程化协作,需要和各种工具(如Webpack)相互配合,因为Babel一定是庞大复杂的。
这里先配置一个最简单的Babel编译工程,熟悉一下整个过程。

源码地址:

Demo代码地址:https://github.com/rupid/tutor-babel/tree/master/packages/tutor-basic

安装

创建 tutor-basic项目。安装npm包

npm install --save-dev @babel/core @babel/cli @babel/preset-env

配置和编译

step1:在根目录下面 新建一个.babelrc文件(或babel.config.js,.babelrc.js)。添加如下内容

{
  "presets": ["@babel/preset-env"]
}

step2:在package.json文件的 scripts里面添加如下配置

"scripts": {
    "build": "babel src/index.js --out-file dist/index.js"
  }

step3:在新建 lib/index.js文件,并添加如下内容

let number1 = 10
let number2 = 20
const sum = (num1, num2) => num1 + num2

step4:运行命令

npm run build
#或者
yarn build
#或
npx babel src/index.js --out-file dist/index.js

step5:查看编译后的结果

"use strict";

var number1 = 10;
var number2 = 20;

var sum = function sum(num1, num2) {
  return num1 + num2;
};

以上展示了一个最简单的Babel使用工程,输出结果和Babel介绍的例子结果是一样的

其他

  • @babel/core: Babel实现转换的核心,它可以根据配置(.babelrc)中的规则,进行源码的转换,提供基础的转换能力
  • @babel/cli: Babel提供的命令行功能,依赖@babel/core,在终端中通过命令行方式运行,编译文件或目录。
  • @babel/preset-env 允许 去配置 需要支持的目标环境,提供ES6转换ES5的语法转换规则。如果不使用它,也可以完成转换,但是转换之后仍旧是E66,说白了就是没有转换。
  • .babelrc Babel配置文件,编译时,默认会寻找当前项目根目录下面的配置文件。转换规则都写在这里面。后面会详细介绍

你可能感兴趣的:(学习,webpack,babel)