对babel不是很了解并不会影响日常开发,但是一旦想要学点新知识,比如搭一个脚手架,引入一个新的包,常常会因为babel而变得手忙脚乱,所以了解babel还是十分有益的。掌握babel的使用
babel是什么?
现在流行的脚手架一般都会使用babel,那么babel是什么呢?它是一个js编译器,即将ES5+版本的语法转换为向后兼容的js语法,使得其当前和旧的版本的浏览器中都能够正常执行。
babel工作的三个阶段:
解析:将代码字符串解析成抽象语法树
变换:对抽象语法树进行变换操作
再建:根据变换后的抽象语法树再生成代码字符串
使用babel
核心库:@babel/core
命令行工具:@babel/cli
因为 Babel 虽然开箱即用,但是什么动作也不做(即不转换代码),如果想要 Babel 做一些实际的工作,就需要为其添加插件(plugin)。
如果使用的新特性的代码很少,比如只使用了箭头函数,那可以只针对箭头函数做转换,安装特定的插件:@babel/plugin-transform-arrow-functions
,但是如果使用的新特性很多,一个一个的配置就很繁琐,这种情况下可以使用预设,通过使用或创建一个 preset 即可轻松使用一组插件。
1 | yarn add @babel/core -D |
@babel/preset-env
@babel/preset-env 主要作用是对我们所使用的并且目标浏览器中缺失的功能进行代码转换和加载 polyfill,在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的JS特性(ES2015,ES2016等,不包含 stage 阶段),将其转换成ES5代码。例如,如果你的代码中使用了可选链(目前,仍在 stage 阶段),那么只配置 @babel/preset-env,转换时会抛出错误,需要另外安装相应的插件。
@babel/preset-env 会根据你配置的目标环境,生成插件列表来编译。
如果不是要兼容所有的浏览器和环境,最好指定目标环境,这样编译后代码体积会小一些。
另外,语法转换只是将高版本的语法转换成低版本的,但是新的内置函数、实例方法无法转换。这时,就需要使用 polyfill 了,顾名思义,polyfill的中文意思是垫片,所谓垫片就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。
@babel/polyfill
@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块,可以模拟完整的 ES2015+ 环境。polyfill 将添加到全局范围和类似 String 这样的内置原型中(会对全局环境造成污染,后面我们会将不污染全局环境的方法)。
1 | yarn add @babel/polyfill |
@babel/polyfill需要作为生产依赖,因为需要在源码运行之前做转换
转换前:
1 | import '@babel/polyfill'; |
转换后:
1 | ; |
这样引入的是整个polyfill,我们希望的是使用了某个特性,就引入对应的polyfill,更改配置:
1 | yarn add core-js@3 |
core-js: JavaScript 的模块化标准库,包含 Promise、Symbol、Iterator和许多其他的特性,它可以让你仅加载必需的功能。
1 | { |
编译后代码:
1 | ; |
@babel/plugin-transform-runtime
可以避免编译后的代码中出现重复的帮助程序,有效减少包体积。另外,@babel/plugin-transform-runtime 需要和 @babel/runtime 配合使用。
Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。,如果使用多个class,会在每个打包文件里,都实现一遍createClass的方法,并且注入到每个文件中,如果你有10个文件中都使用了这个 class,是不是意味着 _classCallCheck、_defineProperties、_createClass 这些方法被 inject 了10次。这显然会导致包体积增大,最关键的是,我们并不需要它 inject 多次。
所以好的解决方案是,不使用@babel/polyfill,而是直接使用@babel/plugin-transform-runtime,并且使用@babel/runtime-corejs3,这样既能处理帮助函数,又能加载ployfill,而不需在入口文件引入整个@babel/polyfill
1 | yarn add @babel/plugin-transform-runtime -D |
1 | { |
插件和预设的处理顺序
如果两个转换插件都将处理“程序(Program)”的某个代码片段,则将根据转换插件或 preset 的排列顺序依次执行。
插件在 Presets 前运行。
插件顺序从前往后排列。
Preset 顺序是颠倒的(从后往前)。
例如:
1 | { |
先执行 @babel/plugin-proposal-class-properties,后执行 @babel/plugin-syntax-dynamic-import
1 | { |
preset 的执行顺序是颠倒的,先执行 @babel/preset-react, 后执行 @babel/preset-env。
升级所有babel的一些问题
- eslint以前配置的不生效了,抛出eslint错误,按照网上给出方法,添加parserOptions,无效。
发生的原因是,升级了babel-eslint,但是对应的eslint没有升级,解决方案是版本一致 - test失败,报错Cannot read property ‘cwd’ of undefined,发生原因同上,babel-jest升级,但是jest未升级,版本对应不上,解决方案是,jest向babel-jest版本靠拢,原因是升级到了babel7之后,再降回低版本会不兼容。
- 项目中因为引入了react-hot-loader,所以打包后的体积反而都变得很大,去掉之后就正常了????
modules设置问题
单测的时候要把bablerc的module设置为false外的其他值,而平时项目部署的时候却要设置为false
答案:因为我们正常运行jest的时候是不走webpack的,而且jest是不能使用ES6+的语法的,所以引入babel,使用babel对模块处理的方法,需要将modules设置为’commonjs’,而我们项目中,使用了wepack自带的ES6的模块处理功能(处理时会做代码优化,把没有用到的代码部分删除掉),需要关闭babel提供的ES6模块转commonjs模块,通过设置为false,就把Babel 解析模块语法的功能关掉了,模块相关的语法就由webpack处理