Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中. babel能做的事情:
1. 语法转换
2. 通过polyfill方式在目标环境中添加缺失的特性(7.4版本之后@babel/polyfill已经废弃, 可以通过core-js/stable + regenerator-runtime/runtime 的方式替代)
3. 源码转换
4. 更多
babel通过插件转换来支持新版的javascript语法, 这些插件让你现在就能使用新的语法,无需等待浏览器支持
babel能够转换jsx语法
1 npm install --save-dev @babel/preset-react 2
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虽然开箱即用,但是什么动作都不会做, 相当于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)
怎么办
预设是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的用法 以及一些小问题 可以看我的这篇博客