什么是babel

435 阅读12分钟

什么是babel

1.Babel 其实就是一个 JavaScript'编译器'2.在 新的'api' 层面我们可以使用'corejs',那么实际在现在的前端工程化的情况,需要对运行环境兼容
除了corejs 这种垫片形式的api 还需要考虑点有
   2.1.语法转换,一般是高级语言特性的降级 -- '语法层面'
   2.2.Polyfill(垫片/补丁)特性的实现和接入 -- 'api层面'
   2.3.源码转换,比如 JSX 等。
可能随着未来前端的发展出现越来越多的发展可能出现,如果一味的让某种工具必须直接具备所有功能
必然会出现臃肿代码
3.在工程化的角度上,需要秉承以下理念去设计一个工具链来解决上面问题
 3.1.可插拔(Pluggable),比如 Babel 需要有一套灵活的插件机制,召集第三方开发者力量,
 同时还需要方便接入各种工具;
 3.2.可调式(Debuggable),比如 Babel 在编译过程中,要提供一套 Source Map,来帮助使用者在编译结
 果和编译前源码之间建立映射关系,方便调试;
 3.3.基于协定(Compact),实现灵活的配置方式,开发者在'尽量还原规范''更小的编译产出体积'之间,
 找到平衡。
4.设想一个思路利用'Babel' js 编译器的功能可以将代码转换为ast 语法树,在配合各种可插拔的组件去对
其介入进行转换,随着未来新的场景的更新只需要的的插件出现配合'babel' 语法解析完成想要的可能

安装使用步骤

1.在使用'babel' 时候我们一般需要安装'@babel/core' '@babel/cli' '@babel/preset-env'
2.从 babel7 开始,所有的官方插件和主要模块,都放在了 @babel 的命名空间下。
从而可以避免在 npm 仓库中 babel 相关名称被抢注的问题。

配置文件

1.Babel也是可以配置和其他工具具有类似的配置:ESLint`.eslintrc`),Prettier`.prettierrc`)。
'babel'配置文件的优先级 'babel.config.json < .babelrc < programmatic options from @babel/cli'

2.babel是JavaScript编译器。但开箱即用的babel什么也不做。它只会把源文件复制到构建目录去。
刚才分析过需要配合一些'可插拔的工具',因此可以在这里去指定这类预设插件
简单的说你不配置这两个其中一个'babel' 也不知道你要转换的规则是什么
{
  "presets": [],  // 预设
  "plugins": []   // 插件
}

3.Babel'预设(preset)'可以被看作是一组 Babel 'plugins'插件,举个例子
'@babel/plugin-transform-arrow-functions' 的作用就是将es2015的箭头函数转换成普通函一个插件,
但是es6 往后的语法转换很多,这种对应插件数量配置无疑的巨大的
更多插件更多插件参考地址'https://babeljs.io/docs/en/plugins'
对应的'babel' 提供了 'babel-preset-env'  将最新语法集合都融入在里面
更多的预设'https://babeljs.io/docs/en/presets'
跟我一样你可以参考的文章

babel6-loose-mode.ht
你也可以看看这个

babel api

1.现在知道babel 是一个编译器,想让其对 进行语法转换需要各种插件配合,整个转换过程可以看作
    1.1.解析(Parsing):将代码字符串解析成抽象语法树。
    1.2.转换(Transformation):对抽象语法树进行转换操作。
    1.3.生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串。
    简单的说就是:为了让计算机理解代码需要先对源码字符串进行 parse,生成 AST,把对代码的修改转为对 
    AST 的增删改,转换完 AST 之后再打印成目标代码字符串'源代码 -> AST -> 转换过的AST -> 转换过的代码'

@babel/core 是个什么

1.刚才已经分析过了'babel' 帮我们转换的是语法,一般这种转换使用'抽象语法树(AST)',在babel中也不例外
整个过程:
 
2.整个复杂的过程是'@babel/core' 来做当然这个包也是依赖这些包
 '@babel/parser、@babel/traverse、@babel/generator'
3.'@babel/core'主要的作用就是编译
直接使用'@babel/core'
1.现在我想做的是利用'@babel/core'将这段代码'const a = 1'变成es5'var a = 1'
  • 案例一
1.不借助'presets''plugins' 发现使用'@babel/core' 并没有得到想要的结果能证明
一句话'babel是JavaScript编译器。但开箱即用的babel什么也不做。它只会把源文件复制到构建目录去'
const core = require('@babel/core')

const code = 'const a = 1;'
core.transform(code, {}, (err, result) => {
    console.log(result, err) // => { code, map, ast }
    // result
})
// 通过打印看到result的code字段得到的结果依旧是'const a = 1' 没有得到想要的结果
  • 案例二
1.使用了预设'@babel/preset-env'这个时候打包得到了想要的结果'const a =1'变成了'var a = 1'
const core = require('@babel/core')
const env = require('@babel/preset-env')

const code = 'const a = 1;'
core.transform(code, { presets: [env] }, (err, result) => {
    console.log(result, err) // => { code, map, ast }
    // result
})
// 通过打印看到result的code字段得到的结果'"use strict";\n\nvar a = 1;' 得到了想要的结果
// 再次验证'babel是JavaScript编译器。但开箱即用的babel什么也不做。它只会把源文件复制到构建目录去'
// 需要我们去配置告诉他进行转换的规则
  • 案例三
1.整个'@babel/core' 的能力由更底层的 '@babel/parser''@babel/code-frame''@babel/generator''@babel/traverse''@babel/types',可以通过下面截图看到
2.针对之前'解析(Parsing)''转换(Transformation)''生成(Code Generation)' 三部曲依次对应
上面的包
 2.1.'解析(Parsing)',@babel/parser,功能是把源码转成 AST
 2.2.'转换(Transformation)','@babel/traverse',可以遍历 AST,并调用 visitor 函数修改 AST'@babel/types' 包提供了对具体的 AST 节点的修改能力(AST的判断、创建、修改等)
 2.3. '@babel/generator' 对新的 AST 进行聚合并生成 JavaScript 代码,同时生成 sourcemap
 关于其他的包' @babel/code-frame',中途遇到错误想打印代码位置
  • 源码core-js/lib/index.js 部分源码截图
const parser = require('@babel/parser')
const t = require('@babel/types')
const generate = require('@babel/generator').default
const traverse = require('@babel/traverse').default

const code = 'const a = 1;'

// 功能是把源码转成 AST
const ast = parser.parse(code)
traverse(ast, {
    VariableDeclaration(node, parent) {
        // console.log(node, parent)
        console.log(node.container[0].kind)
        node.container[0].kind = 'var'
        // const { node } = path
        // // // 获取函数名称等
        // // path.replaceWith() // 替换为新的节点
        // // path.remove() // 删除当前节点
        // // path.skip() // 跳过子节点
        // const newNode = t.variableDeclaration('const', [t.variableDeclarator('var')])
        // path.replaceWith(newNode)
        // const copyNode = t.cloneNode(node) // 复制当前节点
        // traverse(copyNode, {}, {}, path) // 对子树进行遍历和替换,不影响当前的path
    },
})
const output = generate(ast)
console.log(output)
// 不依赖'@babel/core' 而是将整个核心包拆开一步一执行来看得到我们想要的结果
// { code: 'var a = 1;', map: null, rawMappings: undefined }
api 内容具体讲解

babel 的 api '@babel/parser'、'@babel/code-frame'、
'@babel/generator'、'@babel/traverse'、'@babel/types' 详细讲解

整个过程

内容来源,建议读完通篇再来看这个

语法树解析工具

ast语法树

你可以参考的文章 -- 关于ast 转换部分

聊一聊Babel -- 讲述ast 词法语法解析过程 一定要读
babel 的 AST
手把手教你如何写自定义babel代码转换 一定要读

你可以参考的文章 -- 关于手写插件部分

快速写一个babel插件
快速写一个babel插件2

你可以参考的文章 -- 关于使用方法

babel/traverse 使用方法小记

@babel/preset-env

1.'babel/preset-env'是一系列插件的集合,包含了我们在babel6中常用的es2015,es2016, es2017
等最新的语法转化插件,允许我们使用最新的js语法,比如 letconst,箭头函数等等,
但不包含低于 Stage 3JavaScript 语法提案。这样以后只要我们安装一个'babel/preset-env'就解决了大部分问题,
简单的说,它是 'babel 一个预设'主要用来帮助我们对语法进行转换,会自动的根据当前环境对js语法做转换。
结合配置文件
1.现在抛出一个问题,我随着低版本的浏览器逐步淘汰,一些新特性的语法在新浏览器已经支持,是否有必要
全部转换成'es5',我们更希望他可以根据你所配置的浏览器的列表,自动的去加载当前浏览器所需要的插件,
然后对es语法做转换
2.可以通过配置文件指定语法最低版本浏览器兼容这里其实配合的是'Browserslist',Browserslist 的数据都是来自
'https://caniuse.com/',现在我们知道各个版本浏览器支持的语法接下就是配置文件,配置文件是下面的优先级使用:
    2.1.@babel/preset-env 里的 targets
    2.2.package.json 里的 browserslist 字段
    2.3.browserslistrc 配置文件
简单案例了解
1.准备工作安装'@babel/core' '@babel/cli' '@babel/preset-env'创建好了项目结构目录    
    ├─ lib                  
    │  └─ index.js          
    ├─ src                    
    │  └─ index.js           
    ├─ babel.config.json    // 也可以是.babelrc文件
    ├─ package-lock.json    
    ├─ package.json         
    └─ yarn.lock       
2.后续执行的指令'npx babel src/index.js --out-dir lib'
3.如果下面'babel.config.json' 文件的内容仅仅是
    {
        "presets": [
            [
                "@babel/env"
            ]
        ]
    }
我们执行第二步的运行指令发现lib文件夹下的index.js输入如下:
   "use strict";
    var a = 1;
    console.log(a);
    var p = new Promise(function (res) {
      console.log(res);
    });
    p.then(function (res) {
      console.log(res);
    });
'const' 按照我们的期望变成了'var','Promise' 因为是api而不是语法因此没有转译也符合我们的预期
4.现在将'babel.config.json' 修改成下面的形式,但注意了这时候我们制定的'targets'中为 "chrome": "70"
,因为我们的项目不再想执行这令人糟心的ie了,运行指令后在lib文件夹下的index.js输入如下
    "use strict";
    const a = 1;
    console.log(a);
    const p = new Promise(res => {
      console.log(res);
    });
    p.then(res => {
      console.log(res);
    });
因为谷歌浏览器70的版本是完美支持const ,因此babel没有必要将'const'转换成'var'
5.这里更多建议配置'.browserslistrc' 因为不单单js可以这种选择形式根据配置决定,后续如果你了解
'postcss-loader' 发现他也是类似的,我们不将这些控制的文件放在他们所属指定配置文件中,而是
放到一个全局这种类似都可以统一进行识别文件'.browserslistrc'这样就可以全局统一管理
@babel/preset-env 结合 corejs
1.'@babel/preset-env' 是对'语法转换''corejs' 是对api转换,二者其实需要相互结合使用才能够完美的将
es 做到浏览器兼容
2.'@babel/preset-env'提供了一个'useBuiltIns'参数可以设置三个值'entry''usage','false'
  2.1."useBuiltIns: false"  此时不对'api 垫片做操作',你可以引入你想要的处理'es api'的垫片例如此时
  需要在文件引入'import "core-js/stable"''import "regenerator-runtime/runtime"',则无视配置的浏览器兼容,
  引入所有的 polyfill。
  2.2."useBuiltIns: entry"根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手
  文件引入'import "core-js/stable"''import "regenerator-runtime/runtime"',会自动根据 browserslist 
  替换成浏览器不兼容的所有 polyfill。这里需要指定 core-js 的版本(也就是要设置'corejs'字段版本)
  2.3."useBuiltIns: usage"会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加。
总结 推荐使用."useBuiltIns: usage" 配置他自己动会做帮我们引入'corejs垫片',不用在手动全局引入,并且会对
指定浏览器版本进行配合
3.@babel/preset-env + useBuiltins(entry) + preset-env targets,这样@babel/preset-env
 定义了 Babel 所需插件预设,同时由 Babel 根据 preset-env targets 配置的支持环境,自动按需加载 polyfills,
当 useBuiltins 配置为 usage,它可以真正根据代码情况,分析 AST(抽象语法树)进行更细粒度的按需引用

注解:'regenerator' 是 facebook 实现的 'aync''runtime' 库,babel 使用 'regenerator-runtime'来支持实现
 'async' 'await' 的支持。
  • 配置的小例子
  {
      "presets": [
        [
          "@babel/preset-env",
          {
            "modules": false, 
            "useBuiltIns": "entry", 
            targets: { chrome: 44 }
            "corejs": 3, 
          }
        ]
      ],
      "plugins": []
    }

core-js3

  • useBuiltIns: false 说明
"useBuiltIns" 如何做到引入对应的core-js api
1.为什么在配置"useBuiltIns" 时候可以帮助我们引入core-js 符合对应环境适应的api,主要原因
 '@babel/preset-env' 通过 targets 参数,按照 browserslist 规范,结 合'core-js-compat',筛选出适配环境所需的 polyfills(或 plugins),通过合理配置帮助我们按需引入垫片
关于配置文件详细配置我们可以参考的

babel源码解析之(@babel/preset-env)

这一部分的知识点来源

babel-preset-env
官网的讲解1
Babel 7 升级实践

@babel/polyfill 是什么 -- 了解就行过去时


1.@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块,
随着'core-js@3'的更新,'@babel/polyfill'无法从'core-js@2'过渡到'core-js@3',所
以'@babel/polyfill'已经被放弃,

2.在使用上 导入 "import 'babel-polyfill' ",但是在babel 7.4 版本后
@babel/polyfill 已经被废弃,需单独安装 core-js 和 regenerator-runtime 模块

3.现在已经不用了,即使你要用在配置的babel 文件讲'"corejs": 2, '这样设置即可,单单的不同仅是他们
的corejs 版本的差异的指定
更多配置项

当然了实际配置值远远大于'modules','useBuiltIns','corejs'配置项更多可以参考官网

@babel/runtime

1.刚才提到'babel' 配合'@babel/preste-env' 可以进行语法转换,以class 转换为例如图,提供了一个helpers 函数
这些提供帮忙转换的都在'@babel/runtime'
2.也就是说@babel/runtime含有 Babel 编译所需的一些运行时 helpers 函数,供业务代码引入模块化的
 Babel helpers 函数,同时它提供了 regenerator-runtime,对 generator 和 async 函数进行编译降级。
  • class 转换

@babel/plugin-transform-runtime

1.在使用'core-js'时候说过,core-js 有五个衍生包,其中'core-js-pure'(core-js的一个版本,不会污染全局变量)
 注入 polyfills,如果我们想使用这个版本的'core-js' 配合babel 需要使用'@babel/plugin-transform-runtime ' 这插件
当然这个包不仅仅是用来做这些的
 1.1.babel/plugin-transform-runtime 解决全局污染问题 因为使用了'core-js-pure'
 1.2.动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函
 数来替代。这样就减少了我们手动引入的麻烦。
babel/plugin-transform-runtime 解决全局污染问代码效果
  • 如果不使用'@babel/plugin-transform-runtime '
1.下面代码没有使用'@babel/plugin-transform-runtime ' 可以发现虽然转换了api,但是实际上也造成了全局污染
可以发现转换后帮我们导入了一个Promise

  • 使用'@babel/plugin-transform-runtime ' 配置如下
{
    "presets": [
      [
        "@babel/env",
        {
            "corejs": 3,
            "useBuiltIns": "usage"
        }
      ]
    ],
    "plugins": [
        [
          "@babel/plugin-transform-runtime",
          {
            "absoluteRuntime": false,
            "corejs": 3,
            "helpers": true,
            "regenerator": true,
            "useESModules": false,
          }
        ]
      ]
  }  

babel/plugin-transform-runtime 解决helper 代码
  • 使用'babel/plugin-transform-runtime'
1.统一引入helpers 函数

  • 没有使用babel/plugin-transform-runtime
1.如果每个有class的文件都自动引入这个那将会额外增加定义

总结新版babel 我要怎么做

1.需要让babel 配合插件帮助进行语法 转换这里选择的是'@babel/env' 
2.'api' 转换需要配合'core-js''regenerator-runtime/runtime',但是利用'@babel/preset-env '中提供
'useBuiltins' 可以不用手动的去在全局文件引入'core-js''regenerator-runtime/runtime'
3.'语法转换'的时候'@babel/runtime' 会提供一些用来帮助转换的'helper'函数 ,但是希望进一步优化
这时候需要使用'@babel/plugin-transform-runtime',可以帮助解决'core-js全局污染问题''动移除语法转换后内联的辅助函数'
参考

babel/plugin-transform-runtime 阅读这个清晰
官网对babel-plugin-transform-runtime说明

polyfill-service

1.根据浏览器缺失哪些特性来补全哪些特性,它能够根据浏览器的UA来判断当前浏览器缺失哪些特性,
进而进行补强,问题是有些浏览器修改了UA,所以部分情况也会有问题
2.这种方案'https://polyfill.io/v3/',通过引入cdn方式

在线打包

polyfill.io/v3/url-buil…

在线转移工具

在线转移工具

关于浏览器版本

浏览器版本
关于.babelrc文件一些配置讲解
关于.babelrc文件官网说明

一些参考的文章

关于抽象语法树
什么是babel,以及各个插件的区别
Babel 插件原理的理解与深入
Babel 是怎么工作的
一文搞清楚前端 polyfill
配置一些使用
关于Babel你需要知道的那些事
不容错过的 Babel7 知识 -- 推荐必读