Bable原理

为什么需要学习Bable?

作为前端开发者,虽然经常能听到Bable,但是我们在实际开发中,很少会直接接触Bable。那我们学习Bable有什么用呢?
因为Bable主要作用于代码的编写到线上的转变。这个过程是不可或缺的。
比如说编写ES6、TypeScript、开发React项目时使用到的JSX语法等,这些都是需要bable帮助我们进行代码转化。

Bable底层原理

Bable的本质,可以看成是一种编译器。Bable的将我们的源代码转化为浏览器可以识别的代码。而编译器则是将一种语言转化成目标语言。它们的作用十分相似,而且其转化过程也非常相似。

Bable是以微内核架构的(与postcss类似),虽然下载了Bable,但是Bable本身并不会做任何处理。Bable依赖于下载需要的plugins插件去完成转化。

Bable的执行过程:
wps1.jpg

bable在Webpack中使用

安装:
@bable/core是bable的核心,必须安装才能使用bable

  1. npm install bable-loader @bable/core --dev--save

前面提到babel想要进行代码转化是依赖于一个个的plugins的,所以这里需要手动下载每一个plugins然后引用

安装要用到的plugins

  • @babel/plugin-transform-arrow-functions(箭头函数转化)
  • @babel/plugin-transform-block-scoping(块级声明const、let转化)
    npm install @babel/plugin-transform-arrow-functions @babel/plugin-transform-block-scoping --save-dev
    

配置:

//webpack.config.js      
{  
  test: /\.jsx?$/,  
  exclude: /node_modules/,  
  use: {  
    loader: "babel-loader",  
    options: {  
      plugins: [  
        "@babel/plugin-transform-arrow-functions",  
        "@babel/plugin-transform-block-scoping"  
      ]  
    }  
  }  
}

Babel的两种配置文件

在前面我们学习了babel在webpack里是怎么进行配置的。其中,babel-loader是需要另外下载很多插件,然后在options里进行配置的。
其实我们还可以把这里放在options里的配置抽取到配置文件里。

两种配置文件:babel.config.js和bablerc.js。(我们在项目中选择其中一种就可以了,推荐使用babel.config.js这种方式。)
推荐原因:

    1. 在babel7之后都是使用的babel.config.js
    1. 现在很多项目都是采用了分包管理的方式(比如Element plus、umi),而babel.config.js的配置是可以直接作用到所有的子包。但是bablerc.js要想配置到所有子包上,就会非常的麻烦。

preset-env

根据上边配置就可以实现用bable把箭头函数以及块级声明(const、let)这些代码进行转化。

但是其实这样是不合理的。试想下,我们也不清楚我们的项目需要转化哪些方面,也就不知道需要下载以及配置哪些plugins。
判断要使用bable转化哪些方面,是得根据我们项目所需要适配的浏览器进行配置所需的plugins,而不是现在手动下载配置plugins(低效且不合理)。

那有什么好方法解决吗?
使用@babel/preset-env这个插件:这是一个预设的插件集合,包含了一组相关的插件

安装:

npm install @bable/preset-env --save-dev

使用:

//webpack.config.js      
{  
  test: /\.jsx?$/,  
  exclude: /node_modules/,  
  use: {  
    loader: "babel-loader",  
    options: {  
      "@babel/preset-env"  
    }  
  }  
}

@babel/preset-env做什么事?
它会根据browserslistrc文件共享出来的我们的项目需要适配哪些版本的浏览器信息,然后自动根据这些浏览器预设所需要适配的plugins,这样就可以完美实现项目所需要的代码转化了。
(browserslistrc等相关浏览器适配知识,可以看webpack解决浏览器兼容性问题这篇笔记回顾)

如果我们想了解更多的babel相关的plugins,可以到babel官方网站查阅https://babeljs.io/

Polyfill

polyfill什么是?
Polyfill是一个补丁,可以帮助我们更好的使用JavaScript。
(我们在使用Js时,不确定浏览器支不支持我们使用的js语法,比如ES6。而Polyfill,就会帮我们打上对应的补丁。使我们的js代码被浏览器正常解析)

那polyfill的作用是否跟preset-env冲突了呢?
@babel/preset-env只是提供了语法转换的规则(例如箭头函数、解构赋值、class),但是它并不能弥补浏览器缺失的一些新的功能,如一些内置的方法和对象,(如Promise,Array.from等),此时就需要polyfill来做js的补丁,弥补低版本浏览器缺失的这些新功能。
所以它们并不会冲突,polyfill只会对preset-env仍旧无法让浏览器兼容我们的代码的情况下,去打需要的补丁。它们是相辅相成的。

(参考出处:https://zhuanlan.zhihu.com/p/138108118)

安装:
在babel7之前:使用@bable/polyfill(这个包已经被弃用,不推荐)

npm install @bable/polyfill --save

在babel7之后:core-js 或 regenerator-runtime (官方推荐使用这两个包)
(在很多第三方库,都是使用 regenerator-runtime这个包。
因为polyfill的垫片是在全局变量上挂载目标浏览器缺失的功能,就有可能会造成全局污染。而 regenerator-runtime不会污染全局,它会给需要转化的方法另起一个名字)

npm install core-js regenerator-runtime --save

使用:(使用@babel/preset-env的useBuiltIns属性配置polyfill,Polyfill有三种模式可以选择。)

//webpack.config.js      
{  
  test: /\.jsx?$/,  
//排除node_modules,因为我们安装各种包时,可能这些包已经配置了polyfill,这会发生冲突  
  exclude: /node_modules/,  
  use: {  
    loader: "babel-loader",  
  }  
} 
//bable.config.js  
module.exports = {  
  presets: [  
    ["@babel/preset-env", {  

      //useBuiltIns的三个属性,选择使用polyfill的类型  
      // false: 不用任何的polyfill相关的代码  
      // usage: 项目中涉及到需要哪些内容需要polyfill,就添加哪些(polyfill包含很多东西,我们在项目中只用到一部分)
      // entry: 手动在入口文件中导入 core-js/regenerator-runtime, 根据browserslistrc配置的目标浏览器引入所需要的polyfill  
      useBuiltIns: "usage",  
      // 指定corejs的版本号为3,默认为2,版本太低可能会报错
      corejs: 3  
    }],  
   ]  
}

注意点:如果是useBuiltIns:”entry”,那需要在入口js文件添加用到的两个包的导入。

//js入口文件,比如main.js

import "core-js/stable";

//或
//import "regenerator-runtime/runtime";

bable转化react:Preset-react

我们知道React是使用jsx语法的,jsx语法需要经过转化才能给浏览器解析。那babel的哪个plugins可以帮助我们完成呢?

对react jsx代码进行处理需要如下的插件:

  • @babel/plugin-syntax-jsx
  • @babel/plugin-transform-react-jsx
  • @babel/plugin-transform-react-display-nam

总共需要下载并配置这三个plugins。(但是实际上的项目开发中,并不会一个个地去安装这些plugins,直接使用preset来配置就可以了。)

安装:

npm install @babel/preset-react -D

使用:

//bable.config.js  
module.exports = {  
  presets: [  
    ["@babel/preset-env", {  
      useBuiltIns: "usage",  
      corejs: 3  
    }],  
    ["@babel/preset-react"]  //配置preset-react
  ],  
}

bable转化TypeScript

TypeScript需要转化为JavaScript才能被浏览器解析。
其实在我们安装TypeScript到全局时,也自动帮我们安装了TypeScript Compiler(ts编译器)。我们写的ts代码,就可以使用这个tsc来手动编译。
但是真实开发中我们总不可能每个ts文件都进行手动编译,所以就可以让我们的webpack帮我们实现。

两种方式:ts-loader 或 preset-typescript

ts-loader的使用

安装:npm install ts-loader -D
ts-loader是依赖于TypeScript包的,本质上是使用了TypeScript Compiler
(要是此时没有安装TypeScript,其实也不需要专门安装的。这涉及node的知识:我们安装包时,如果这个包对其它包有依赖的话,node会帮我们自动安装其它包的)。

配置:

//webpack.config.js        
{  
  test: /\.ts$/,  
  exclude: /node_modules/,  
  use: "ts-loader"  
}

如果这时候我们项目的根目录下没有tsconfig.json这个配置文件,我们此时打包就会报错。
因为ts-loader本质还是去用tsc,所以需要用到tsconfig.json里的配置。
我们需要生成这个文件 。这个文件我们一般不会手动创建,使用tsc帮我们创建即可。

//命令行输入指令,自动生成tsconfig.json
tsc --init

这样我们就实现了ts的转化。但是其实这样还是存在问题的。
这里我们是用ts-loader,设想如果我们的项目还用到了Polyfill去处理代码,部分代码肯定是既要用到polyfill又要用到ts-loader的,那这种情况代码还能正确地被转化吗?
答案是不行的。
那如果babel也支持对TypeScript进行编译,那就可以实现TypeScript转化+polyfill了。

preset-typescript的使用

安装:

npm install @babel/preset-typescript -D

使用:
Webpack.config.js的配置:

//webpack.config.js  
{   
  test: /\.ts$/,  
  exclude: /node_modules/,  
  // babel-loader不用依赖于tsc就能编译  
  use: "babel-loader"  
}

Babel.config.js的配置:

//babel.config.js  
module.exports = {  
  presets: [  
    ["@babel/preset-env", {  
      useBuiltIns: "usage",  
      corejs: 3  
    }],  
    ["@babel/preset-typescript"]  
  ],  
}

Ts-loader vs babel?

它们的区别:
ts-loader(TypeScript Compiler)

  • 本质上是使用了TypeScript Compiler;
  • 无法与Babel的polyfill共同作用;
  • 会对类型错误进行检测;

使用babel-loader(Babel)

  • 无需借助TypeScript Compiler,Babel是非常强大的,无需依赖于tsc就可以对我们的ts进行编译;
  • 可以实现TypeScript转化+polyfill;
  • babel-loader在编译的过程中,不会对类型错误进行检测;

补充(关于类型错误的检测):
当我们使用了ts-loader,如果我们的代码没有使用ts规定的类型,就会在编译的时候直接报错,运行失败。
而使用了babel-loader,则是可以正常运行起来的,babel-loader并不会对类型错误进行检测。(在敲代码时,也会有错误提示。但是这个是因为vscode对TypeScript的支持。如果是其它编辑器,比如webstorm,默认不支持的,需要进行额外的配置。)

最佳实践

这样看来,它们各自都有一些不完美的地方,那我们在真实开发中应该如何选择呢?
我们可以看到TypeScript官方文档的说明:
https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html

wps2.jpg

官方的说法是:
如果我们的代码编译前后差别不大(就是并没有使用promise等特性),推荐使用tsc。
如果我们还需要用到ts之外的其它转化,官方推荐使用babel进行转化,并且还使用tsc进行类型检查。

看完官方的解释,现在很多项目都是会用到诸如promise等新特性,所以polyfill是不可或缺的。我们可以考虑第二种解决方案。
使用babel进行转化,并且还使用tsc进行类型检查这要怎么做到呢?

//package.json   
 "scripts": {  
    "build": "webpack --config webpack.config.js",  
    //这样配置的话,命令行输入npm run type-check就可以使用tsc进行类型检查  
    //--noEmit:不输出额外的文件,tsc编译默认输出对应js文件  
    "type-check": "tsc --noEmit",  

    //--watch:对代码变化进行监听,这样我们就无需手动输入npm run type-check  
    "type-check-watch": "tsc --noEmit --watch"  
 },

参考出处:

https://babeljs.io/
https://zhuanlan.zhihu.com/p/138108118
https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html