当前位置: 首页 > 科技观察

你可能不明白babel配置的原理

时间:2023-03-15 16:25:05 科技观察

babel是一个JS、TS编译器,它可以将新语法编写的代码转换成目标环境支持的语法代码,自动转换不支持的API目标环境polyfill。几乎每个项目都会用到Babel。@babel/preset-env和@babel/plugin-transform-runtime你可能很熟悉,但你真的了解它们吗?相信很多同学只是知道它能做什么,并不知道它是如何实现的,本文就来深入了解一下它们的实现原理。首先我们来试试preset-env和plugin-transform-runtime的功能:功能测试@babel/preset-env的作用是根据targets的配置引入相应的插件,实现编译和polyfill。比如这段代码:classDong{}在低版本的浏览器中是不支持的,会做语法转换。我们指定targets为低版本的浏览器,比如chrome30,并开启debug选项,会打印出使用的插件。{presets:[['@babel/preset-env',{targets:'chrome30',debug:true,useBuiltIns:'usage',corejs:3}]]}执行babel你会发现它使用了这些插件:这就是@babel/preset-env的意思,根据target自动导入需要的插件,不然手动写这么一堆插件也不麻烦。开启polyfill功能需要指定其导入方式,即useBuiltIns。设置为usage是在导入各个模块时使用,设置为entry需要在入口处统一引入target。polyfill的实现是core-js,需要指定corejs版本,一般指定3,这样会polyfill实例方法,corejs2不会。上面这段代码会转换成这样:注入了3个helper,也就是下划线开头的辅助方法_createClass。因为helper方法中使用了Object.definePropertyapi,所以这里也会从core-js中引入。我们再测试一下这段代码:asyncfunctionfunc(){}会变成这样:除了注入core-js和helper代码外,还会注入regenerator代码,也就是asyncawait的实现。综上所述,babelruntime包含的代码是core-js、helper、regenerator。@babel/preset-env的处理方式是直接注入helper代码,全局导入regenerator和core-js代码。这样会导致多个模块重复注入相同的代码,从而污染全局环境。要解决此问题,请使用@babel/plugin-transform-runtime插件。我们在配置文件中引入这个插件:{presets:[['@babel/preset-env',{targets:'chrome30',debug:true,useBuiltIns:'usage',corejs:3}]],plugins:[['@babel/plugin-transform-runtime',{corejs:3}]]}注意这个插件也是处理polyfill的,所以也需要指定corejs的版本。然后测试引入后有什么变化:先测试class的情况:以前是这样的:现在变成这样了:变成了从@babel/runtime-corejs3导入的形式,这样多个模块就不会了被重复注入了实现代码,而core-js的API并没有全局导入,而是变成了模块化导入。这样就解决了corejs重复注入和全局polyfill引入的两个问题。再测试一下asyncfunction的情况:之前是这样的:还有全局引入和重复注入的问题。引入transform-runtime插件后,是这样的:那两个问题也是用同样的方法解决的。让我们再次测试一个api,使用这段代码:newWeakMap();仅配置preset-env时:{presets:[['@babel/preset-env',{targets:'chrome30',debug:true,useBuiltIns:'usage',corejs:3}]]}结果为像这样:添加@babel/plugin-transform-runtime后:{presets:[['@babel/preset-env',{targets:'chrome30',debug:true,useBuiltIns:'usage',corejs:3}]],plugins:[['@babel/plugin-transform-runtime',{corejs:3}]]}结果是这样的:这样我们就很清楚@babel/plugin-transform-runtime的作用了,它将注入的代码和core-js全局导入的代码转换成@babel/runtime-corejs3导入的形式。@babel/runtime-corejs3包括helpers、core-js和regenerator。功能我们都知道,那么它们是如何实现的呢?实现原理preset-env的原理前面已经讲过,就是根据targets的配置查询内部的@babe/compat-data数据库,过滤掉target环境不支持的语法和api,引入相应的转换插件。targets使用browserslist解析成具体的浏览器和版本:然后根据@babel/compact-data的数据过滤出这些浏览器支持的语法和api:然后去掉支持的语法和api对应的插件,留下唯一需要用到的就是转换插件:这就是preset-env根据targtes按需转换语法和polyfill的原理。@babel/plugin-transform-runtime怎么样?它是如何实施的?这个插件的原理就是babel插件和preset生效的顺序是这样的(下面是官网文档截图):先是plugin然后是preset,plugin从左到右,预设从右到左。这就导致@babel/plugin-transform-runtime在@babel/preset-env之前被调用了,并且提前做了api转换,那么就没有什么可以传给@babel/preset-env了,polyfill就是实现提取。其源码如下:会根据配置引入corejs和regenerator转换插件,实现polyfill注入的功能。并且还为全局上下文文件设置了一个helperGenerator函数,这样@babel/preset-env就可以在以后使用它来生成辅助代码。那自然是退缩了。这就是@babel/plugin-transform-runtime的原理:因为在preset之前调用plugin,可以提前转换polyfill,注入helpGenerator修改@babel/preset-env生成helper代码的行为。原理我们讲清楚了,但是大家有没有发现问题:对于现有解决方案的问题,我们提前通过@babel/plugin-transform-runtime改造了polyfill,但是这个plugin里面没有targets设置,不是按需转换是的,它会做更多不必要的转换。这个其实是一个已知问题,在babel项目中可以找到这个issue:当然官方也提出了解决方案,但是这个要等babel新版本更新后才能使用,等babel8.总结babel7后,我们只需要使用@babel/preset-env指定目标环境的targets,babel会根据内部兼容数据库查询环境不支持的语法和api,并引入相应的插件,从而实现Required语法转换和polyfill导入。但是@babel/preset-env转换使用的一些辅助代码(helper)是直接注入到模块中的,没有抽取,可能会重复注入多个模块。而且使用的polyfill代码也是全局导入的,可能会污染全局环境。为了解决这两个问题,我们将使用@babel/plugin-transform-runtime插件提取注入的代码,并将全局导入改为从@babel/runtime-corejs3导入。runtime包包含三部分:core-js、regenerator、helper。@babel/plugin-transform-runtime工作原理是因为插件在preset之前被调用,提前转换那些API,并设置preset-env生成helpers的方式。但是这个改造是独立于preset-env的,没有targets的配置,导致无法按需polyfill,还有一些不必要的改造。这是一个已知问题,等待babel版本更新。看到这里,是不是对babel的配置以及这些配置的原理有了更深入的了解呢?