du blog
Hello, welcome to my blog
babel笔记(一)-使用入门
created: Jan 17 21updated: Jan 20 21

babel是什么

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中. babel能做的事情:

1. 语法转换

2. 通过polyfill方式在目标环境中添加缺失的特性(7.4版本之后@babel/polyfill已经废弃, 可以通过core-js/stable + regenerator-runtime/runtime 的方式替代)

3. 源码转换

4. 更多

es2015及更新的版本

babel通过插件转换来支持新版的javascript语法, 这些插件让你现在就能使用新的语法,无需等待浏览器支持

jsx与react

babel能够转换jsx语法

1 npm install --save-dev @babel/preset-react 2

类型注释 (Flow 和 TypeScript)

Babel 可以删除类型注释!务必牢记 Babel 不做类型检查,你仍然需要安装 Flow 或 TypeScript 来执行类型检查的工作

1 npm install --save-dev @babel/preset-flow 2 npm install --save-dev @babel/preset-typescript 3

简单的使用指南

安装

@babe/cli是babel的命令行工具 是一种在命令行下使用 Babel 编译文件的简单方法

1 npm i @babel/cli -D 2

@babel/core是babel的核心转换工具,包含了解析(@babel/parser)、转换(@banel/traverse)、生成(@babel/generator)

1 npm i @babel/core -D 2

babel默认不会进行转换

babel虽然开箱即用,但是什么动作都不会做, 相当于code => code.

建立src/index.js 文件

index.js

1 const a = () => { 2 console.log('hello') 3 } 4

执行命令

1 npx babel src -d dist8 2

output

dist8/index.js

1 const a = () => { 2 console.log('hello'); 3 }; 4

可以看到转换后的代码与转换之前是一样的

使用插件

babel是由大量工具链组成的, 默认babel只负责 解析=>ast=>生成 这样一个转换通道, 而想要在这个通道中进行语法转换就需要用的插件, 比如需要转换箭头函数就需要用到 @babel/plugin-transform-arrow-functions

1 npm i @babel/plugin-transform-arrow-functions -D 2

然后在根目录下建立配置文件 babel.config.json

1 { 2 "plugins": [ 3 "@babel/plugin-transform-arrow-functions" 4 ] 5 } 6

然后执行 npx babel src -d dist8

output:

1 const a = function () { 2 console.log('hello'); 3 }; 4

这样就实现了高版本的语法到es5的转换

此时有两个问题:

1. 每个语法都要转换 那需要配置的插件不是非常之多

2. 如果我的目标兼容浏览器发生改变, 那么每次我都需要梳理一遍需要的插件么

先暂时搁置着两个问题, 此时如果我想使用es6+的api 比如

index.js

1 + Promise.reject() 2

此时运行,显然这行代码不会有任何改变,因为它使用的是新的特性

output:

1 const a = function () { 2 console.log('hello'); 3 }; 4 Promise.reject(); 5

那么此时就有另外一个问题: 我想要使用新的api怎么办,比如es2020的的 [import()](https://github.com/tc39/proposal-dynamic-import) [Promise.allSettled](https://github.com/tc39/proposal-promise-allSettled) es2021的[Promise.any](https://github.com/tc39/proposal-promise-any) [String.prototype.replaceAll](https://github.com/tc39/proposal-string-replaceall)怎么办

preset-env

预设是babel中一个很重要的东西, 可以理解为一组插件的集合, 实际上它也确实是一组插件的集合.

preset-env是babel提供一个强大的预设

1. 它内置了包含es2021在内的语法插件(目前)#11864

2. 支持core-js的polyfill

3. 同时支持根据目标环境转换所需的语法和polyfill

安装

1 npm install --save-dev @babel/preset-env 2 npm install core-js 3

此时我们更改配置

1{ 2 "presets": [ 3 [ 4 "@babel/preset-env", 5 { 6 "targets": { 7 "chrome": "50" 8 }, 9 "bugfixes":true, 10 "useBuiltIns": "usage", 11 "corejs":{ 12 "version":3.8, 13 "proposals":true 14 } 15 } 16 ] 17 ] 18} 19

tragets为目标浏览器

bugfixes为启用 [@babel/preset-modules](https://github.com/babel/preset-modules) 对目标浏览器存在的bug进行修复

corejs为使用corejs的3.8版本 以及启用提案转换

useBuiltIns: useage 根据目标浏览器是否支持 且源码中是否使用 导入polyfill

此时我们来写一些代码验证一下:

src/index.js

1 const firstPromise = new Promise(res => res(123)) 2 3 4 // es2021的promise新静态方法 5 Promise.any(firstPromise) 6 7 8 const obj = {}; 9 10 11 // 提案2阶段的set的新方法 12 const setNewMethod = new Set([1,2,3,4]); 13 console.log(setNewMethod.find(i => i===1)) 14 15 16 // es2021的逻辑分配语法 17 const a = '1'; 18 const b = '2'; 19 a ||= b; 20 a || (a = b); 21

output

1 "use strict"; 2 require("core-js/modules/es.array.iterator.js"); 3 require("core-js/modules/es.promise.js"); 4 require("core-js/modules/es.set.js"); 5 require("core-js/modules/esnext.aggregate-error.js"); 6 require("core-js/modules/esnext.promise.any.js"); 7 require("core-js/modules/esnext.set.add-all.js"); 8 require("core-js/modules/esnext.set.delete-all.js"); 9 require("core-js/modules/esnext.set.difference.js"); 10 require("core-js/modules/esnext.set.every.js"); 11 require("core-js/modules/esnext.set.filter.js"); 12 require("core-js/modules/esnext.set.find.js"); 13 require("core-js/modules/esnext.set.intersection.js"); 14 require("core-js/modules/esnext.set.is-disjoint-from.js"); 15 require("core-js/modules/esnext.set.is-subset-of.js"); 16 require("core-js/modules/esnext.set.is-superset-of.js"); 17 require("core-js/modules/esnext.set.join.js"); 18 require("core-js/modules/esnext.set.map.js"); 19 require("core-js/modules/esnext.set.reduce.js"); 20 require("core-js/modules/esnext.set.some.js"); 21 require("core-js/modules/esnext.set.symmetric-difference.js"); 22 require("core-js/modules/esnext.set.union.js"); 23 require("core-js/modules/web.dom-collections.iterator.js"); 24 25 const firstPromise = new Promise(res => res(123)); 26 Promise.any(firstPromise); 27 const obj = {}; 28 const setNewMethod = new Set([1, 2, 3, 4]); 29 console.log(setNewMethod.find(i => i === 1)); 30 const a = '1'; 31 const b = '2'; 32 a || (a = b); 33 a || (a = b); 34

可以看到语法已经被转换,不支持的api也导入了polyfill, 而且仅导入了我们使用到的

至于根据目标浏览器进行转换, 我们改一下代码

config

1 { 2 "presets": [ 3 [ 4 "@babel/preset-env", 5 { 6 "targets": { 7 "chrome": "80" 8 }, 9 "bugfixes":true, 10 "useBuiltIns": "usage", 11 "corejs":{ 12 "version":3.8, 13 "proposals":true 14 } 15 } 16 ] 17 ] 18 } 19

这里把目标浏览器改为了chrome80

src/index.js

1 Promise.reject() 2

output

1 "use strict"; 2 Promise.reject(); 3

可以看到代码并没有改变 因为chrome是支持promise.reject的

抽离辅助代码

此时仍有一个隐藏问题:

input:

src/index.js

1 class Person1 { 2 3 } 4

src/index2.js

1 class Person2{ 2 constructor(){ 3 this.name=13 4 } 5 } 6

config:我们加上目标浏览器 ie 11

1 "ie":11 2

output:

dist8/index.js

1 "use strict"; 2 3 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 4 5 var Person1 = function Person1() { 6 _classCallCheck(this, Person1); 7 }; 8

dist8/index2.js

1 "use strict"; 2 3 require("core-js/modules/es.function.name.js"); 4 5 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 6 7 var Person2 = function Person2() { 8 _classCallCheck(this, Person2); 9 10 11 this.name = 13; 12 }; 13

我们可以看到两个文件都有同样的代码

1 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 2

那我们的一个项目中的class不计其数,文件也很多,岂不是每个文件都要有一个这样的函数, 这显然是有很大的问题的

此时我们就需要一个插件来帮助抽离这些辅助代码 @babel/plugin-transform-runtime 同时还需要安装运行时的代码 @babel/runtime

1 npm i @babel/plugin-transform-runtime -D 2 npm i @babel/runtime 3

注意@babel/runtime是运行时的代码 不能安装的dev依赖里

config:

1 { 2 "presets": [ 3 [ 4 "@babel/preset-env", 5 { 6 "targets": { 7 "chrome": "50", 8 "ie":11 9 }, 10 "bugfixes":true, 11 "useBuiltIns": "usage", 12 "corejs":{ 13 "version":3.8, 14 "proposals":true 15 } 16 } 17 ] 18 ], 19 "plugins": [ 20 "@babel/plugin-transform-runtime" 21 ] 22 } 23

transform result:

src/index.js

1 "use strict"; 2 3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 5 var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 6 7 var Person1 = function Person1() { 8 (0, _classCallCheck2.default)(this, Person1); 9 }; 10

src/index2.js

1 "use strict"; 2 3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 5 require("core-js/modules/es.function.name.js"); 6 7 var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); 8 9 var Person2 = function Person2() { 10 (0, _classCallCheck2.default)(this, Person2); 11 this.name = 13; 12 }; 13

可以看到辅助代码已经改为从@babel/runtime导入了

更多transform-runtime的用法 以及一些小问题 可以看我的这篇博客