专业名词解释
ECMA
ECMA(European Computer Manufacturers Association,欧洲计算机制造商协会)是一个国际标准化组织,成立于1961年,总部位于瑞士日内瓦。ECMA致力于制定和推广计算机和通信系统的标准,以促进信息与通信技术的发展和互操作性。ECMA发布了许多著名的标准,例如ECMAScript(也就是JavaScript的一种主流实现)和C#等编程语言的标准。
ECMAScript(ES)
为什么JS会与EMCA有关联?这里就要从JavaScript的发展历史说起,Javascript诞生在1995年,由Netscape(网景)的一名程序员Brendan Eich 在短短十天内创建了 JavaScript 编程语言(在开发期间最初称为 Mocha,然后改为 LiveScript)。
在1996年,LiveScript改名为JavaScript(当时Java火热改名有利于推广语言),并且同年微软发布IE3浏览器,并且发布了JScript 兼容 JavaScript(避免引起免许可纠纷,特意区别命名为JScript),Netscape与IE的竞争处于下风,于是寻求ECMA将JavaScript推进标准化,JavaScript正式加入了ECMA大家庭。
次年(1997年)ECMA通过ECMA-262文件,于是诞生了ECMAScript(发音为“ek-ma-script”)这个新脚本语言标准。后来,各家浏览器均以ECMAScript作为自己的JavaScript实现依据(所以ECMAScript 是 JS 的规范,而 JS 是 ECMAScript 的一种实现方法)。
ECMAScript 1就是指ECMA-262,是ECMAScript语言规范的第一版。往后比较有名的就是ECMAScript2015(平时所称的ES6),并且从这个版本开始按年份命名规范版本,如ES2017、ES2020等等。
TC39
是ECMA 国际组织第 39 号技术委员会( Technical Committee ),它是一个推动 JavaScript 发展的委员会。它是 ECMA 的一部分, ECMA 是 “ ECMAScript ” 规范下的 JavaScript 语言标准化的机构。
TC39 由各个主流浏览器厂商的代表构成 。他们的主要工作就是制定 ECMAScript 标准并实现。每两个月召开TC39 会议,成员指定的代表和受邀的专家参加。 这些会议的记录在GitHub 存储库 中公开。
TC39制定新ES标准规范流程
没有正式发布的规范也就是还在定制阶段的特性都被称为 ESNext。
在TC39旧的规范制定流程下,一次新版的发版时间非常长,例如ECMAScript 3(1999 年 12 月增加了正则表达式、字符串处理、控制语句、异常处理等众多核心特性。ECMAScript 4(2008 年 7 月废除) 因为跨度过大,出现了分歧,最终没能推广使用。ECMAScript 6(2015 年 6 月) 才实现了 ECMAScript 4 的许多设想。
总结下来旧的制定规范流程造成了以下两个问题:
- 版本之间时间间隔久,先提案的特性必须要等到规范正式发布才能投入使用
- 并靠后实现的特性需要在规范发版deadline临近才实现,可能发现设计缺陷并且没有时间来补救。
针对这些问题,TC39 制定了新的 TC39 流程(The TC39 Process):
以下是该文档中各个阶段的简要介绍:
- Stage 0 - Strawman(稻草人阶段):这个阶段是一个想法的初步阶段,通常由个人或小组提出。在这个阶段,提案的目标是获得反馈和讨论,以确定是否值得进一步研究和开发。
- Stage 1 - Proposal(提案阶段):在这个阶段,提案需要被提交给 TC39 委员会进行评审。提案需要包含详细的语法和语义规范、示例代码和实现意见等信息。
- Stage 2 - Draft(草案阶段):在这个阶段,提案需要进行更加详细的规范和实现方面的工作,并且需要至少有两个独立的实现。
- Stage 3 - Candidate(候选人阶段):在这个阶段,提案需要被认为是准备好进入 ECMAScript 标准的最终版本,并且需要至少有两个符合规范的实现。
- Stage 4 - Finished(完成阶段):在这个阶段,提案被认为是准备好成为 ECMAScript 标准的一部分,并且需要通过 TC39 委员会的投票批准。一旦被批准,提案就会被包含在下一个 ECMAScript 标准版本中。
有以下优点;
- 更加透明和开放:任何人都可以提交提案和参与标准化流程,更加公正和民主。
- 更加灵活和快速:使得 ECMAScript 规范的更新更加灵活和快速,使得每次发布的版本是较小的增量版本,提案可以更快地进入标准化流程,并且可以更快地被批准或者被放弃,敏捷开发。
- 更加稳定和可靠:可以确保 ECMAScript 规范更加稳定和可靠。每个阶段都有严格的要求和标准,只有符合要求的提案才能进入下一个阶段。
- 更加注重实践和应用:更加注重实践和应用,使得 ECMAScript 规范更加贴近实际应用场景和开发需求。提案需要包含详细的示例代码和实现意见,以确保提案的实用性和可行性。
Babel背景
最近升级项目(由Babel6升级至Babel7),支持es2020的新特性,所以涉及到很多npm依赖关系、项目构建时的编译流程,最主要的还是对于Babel相关依赖的升级,由于时间的推移,在webpack设置、Babel设置选项、依赖npm的版本与包名都有不少改变。
本文主要记录升级项目涉及到的基础依赖如Babel系列、core-js、ployfill等等的作用以及其中技术的发展历史和原因。
Babel作用
Babel 出现的原因是因为 JavaScript 语言的发展速度非常快,新的 ECMAScript 版本中会引入许多新的语法和特性,这些语法和特性不能被旧版浏览器所理解,因此需要一种工具来将它们编译成向后兼容的 JavaScript 代码。它还支持许多其他功能,如 JSX 转换、TypeScript 转换等。
换句话讲,Babel一方面是源码到源码之间的转化编译器,拥有众多模块(可按需使用)可用于不同形式的静态分析,另一方面babel还可以对用户浏览器打“补丁(polyfill)”,在旧的运行时环境上支持新的js的原型方法与Api。
作用总结
是源码到源码之间的转化编译器,将新特性的JS源代码的语法转换成旧版浏览器也能运行的JS代码,拥有众多模块(可按需使用)可用于不同形式的静态分析。
- 例如将箭头函数、for of循环转换成旧版EMCA语法实现
通过 Polyfill 方式在目标环境(用户浏览器的运行环境)中添加缺失的特性:
- 特性一:新的ES内置对象,如
Promise
对象 - 特性二:原有ES内置对象的新的静态方法,如Array.from
- 特性三:原有ES内置对象的新的原型链上的prototype实例方法,如String.prototype.include
polyfill
polyfill的中文名为(垫片/补丁), 出现的背景是ECMAScript的迅速发展以及浏览器的频繁更新换代,每年会出现新的api ,例如Promise、Array.from等等后面加入进来的特性,就引出了polyfill,它的作用就是用一段代码,实现在不兼容某些新特性的浏览器上,可以使用该新特性。所以将各种新特性实现的代码的集合就称为polyfill。
- 特性一:新的ES内置对象,如
Babel发展历史
Babel 的发展历史可以概括为以下几个阶段:
- 6to5阶段:是由sebmck在2014年发布的一个npm包,用于将ES6转换成ES5,npm页面显示已经弃用并且切换到babel上,6to5和Esnext的团队决定一起开发6to5,将其命名为babel。
- Babel 5:Babel 最初的版本,作用是负责将ES6的语法编译成旧浏览器可以执行的写法,类似一个大的全家桶,将所有的工具都集成在一起,安装Babel5的时候会将相关工具都安装进来,导致安装了一些本不需要的工具,导致项目包变得很大。
Babel 6:
- 将Babel全家桶拆分成了许多不同的npm模块包,下文会详细介绍各个Babel模块的作用,了解各个模块的作用是非常必要的,否则在项目初始搭建和项目升级维护时会比较迷糊。
- 引入插件化概念,通过plugin引入一些插件,按需使用,减少冗余代码被构建进项目。
- 引入preset预设概念,该预设除了包含所有稳定的转码插件,还可以根据我们设定的目标环境进行针对性转码。
- 对ES6以及ES6+的语法做了支持,并且优化编译速度。
- 引入transform-runtime用来避免在编译过程中重复引入babel工具函数和polyfill,从而减小编译后的代码大小。
Babel 7:
- 整合了以往的包废弃了一些6的用法,如弃用了state-x
- 采用npm的scope命名空间(@Babel/xxx)
- 引入了TypeScript支持,可以直接使用@babel/preset-typescript预设来编译TypeScript代码。
- 引入了许多新的特性和改进,用于支持ES6+中的类成员属性和私有方法,支持react相关处理等
接下来会详细介绍Babel每个模块的作用,以及Babel6到Babel7时每个模块的变更(包名变更、配置项变更、版本号依赖变更、是否弃用)。
Babel模块系列介绍
上文提到了Babel6时将整个Babel差分成多个模块,主要拆分成@babel/cli、@babel/core、@babel/eslint-parser、@babel/preset-env、@babel/runtime-corejs3以及各种@babel/plugin开头的插件。(PS:这里的包名都是babel7的包名,下文会提到一些在babel7已经弃用但是在babel6还在使用的包)。
babel-loader
babel-loader(没有@符号)是webpack用来加载执行Babel的npm包,核心依赖为@babel/core。
引入babel-loader之后通过在webpack.config.js
中的配置来配置babel,官方文档:https://www.webpackjs.com/loaders/babel-loader/#root
这里要强调在webpack配置babel的时候记得打开cacheDirectory
配置项,cacheDirectory
会指定目录(默认的缓存目录 node_modules/.cache/babel-loader)将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。通过cacheDirectory
可以提升一倍的编译速度。
webpack配置项:
module: {
rules: [
{
test: /\.js$/, // 正则匹配文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'], // 选择babel预设,下文会介绍
cacheDirectory: isDebug, // 启用cacheDirectory
babelrc: true, // 使用babelrc配置文件
},
},
}
],
},
升级至babel7需要保证babel-loader
项目升级至8.x.x,@babel/core
需要升级至7.x.x
@babel/core
在Babel6中名为babel-core。官方文档:https://www.babeljs.cn/docs/babel-core
@babel/core是Babel最基础的组件,负责代码的转换,可以转换传入的代码,可以返回生成的代码、源映射和AST回调。
Demo:
babel.transform("code();", options, function(err, result) {
result.code;
result.map;
result.ast;
});
像@babel/cli、@babel/preset-env、@babel/node等等模块都会依赖@babel/core这个包,不论是通过命令行转码,还是通过webpack进行转码,底层都是通过Node来调用@babel/core来实现代码的编译转换的。
升级至babel7需要将"babel-core": "6.x.x"
替换成"@babel/core": "7.x.x"
core-js与@babel/polyfill(已弃用)
@babel/polyfill
官方文档:https://babeljs.io/docs/babel-polyfill
通过直接在全局对象上增加property和修改内置对象实现polyfill。
babel7.4.0之后被弃用,被core-js/stable取代,使用方法在项目入口文件使用import "core-js/stable";
core-js
core-js是javascript的模块化标准库,实现了ECMAScript的polyfill中的众多特性如: promises, symbols, collections, iterators, typed arrays。换句话讲就是实现了babel中对旧版浏览器中无法支持的Js运行环境中的内置特性。
官方文档:https://www.npmjs.com/package/core-js
什么是全局污染?
JavaScript的全局污染是指在使用全局变量时,可能会发生由于变量命名冲突而导致程序出现意外行为的情况。当多个脚本文件都使用了相同的全局变量名称时,它们将会相互影响,导致程序出现错误。为了避免全局污染,应该尽可能地避免使用全局变量,或者使用命名空间等技术来减少命名冲突的可能性。
使用 Babel 的 core-js 会造成全局污染,因为它会向全局对象添加一些新的内置方法和对象,这些方法和对象与原生 JavaScript 的方法和对象同名,可能会与其他脚本文件中的同名方法和对象产生冲突,导致意外的行为。这些新的方法和对象是通过修改全局对象(如 window 或 global)来实现的。
修改全局对象对于业务项目或者一个命令行工具还好。但是如果你编写的代码是被作为工具库被别的开发者引用或者不能保证代码运行时的环境时,如果别人想针对自己的项目定制某些全局变量的功能并对其重写定制,这时便可能会导致代码异常了。
core-js具体用法在@babel/preset-env中介绍。
core-js是babel-preset实现polyfill的重要依赖,目前core-js是core-js@3版本,core-js@2已经处于不维护状态,所以ES新特性在core-js@2版本中不会支持。
core-js@3针对core-js@2版本进行了较大的改动,首先不支持了babel-polyfill包(已经废弃),其次将本身拆分为core-js(提供给@babel/preset-env使用,会造成全局污染)和core-js-pure(提供给@babel/runtime-corejs3运行时库使用,不会造成全局污染)。
升级至babel7需要将core-js升级至3,同时搭配下文的@babel/preset-env或者@babel/runtime-corejs3使用
babel-preset-stage-[x]/babel-preset-es[x] (插件已弃用)
babel-preset-stage/babel-preset-es
两类插件都是babel6的产物(在babel7中已经被弃用),出现的背景是,ES语法以每年一个版本迭代,所以babel也需要去支持这些新的API以及语法,而新语法提案到纳入ES标准需要经过以下阶段(见上面TC39制定新ES标准规范流程):
- Stage 0:提案,经过 TC39 成员提出即可
- Stage 1:初步尝试
- Stage 2:完成初步规范
- Stage 3:完成规范以及主流浏览器初步实现
- Stage 4:完全完成,将被添加到下一年度进行发布,因此不会存在针对这个阶段的配置项
当一个新的ES特性出来时,babel都需要跟进实现转换每一个特性的响应插件(plugin),但是用户不可能手动接入每一个特性对应的plugin插件,所以这时候引入了preset(预设)的概念,preset将每个版本的ES特性整合起来(plugin的集合),用户通过preset就可以引入这个ES版本中支持的所有特性。
例如支持ES6语法特性就使用babel-preset-es2015、支持ES7语法特性就使用babel-preset-es2016等等。如果想使用尚未加入EMCA正式规范的特性(也就是正在处于TC39标准化规范流程中),此时就使用babel-preset-stage,例如想使用Stage-0阶段的特性,就使用babel-preset-stage-0。
但是这样也造成了很多问题:
- 随着 ECMAScript 标准的不断更新,这些预设插件已经无法涵盖所有新的语言特性,例如es2020、es2021,如果需要支持就得手动添加新的插件,配置的复杂性变大。
- 预设插件命名方式不够清晰明确,导致开发人员无法准确知道插件所支持的特性,例如随着EMCAScript的更新,babel-preset-stage所代表的特性也不相同,例如某一特性从Stage-0到了Stage2,那么开发者无法轻易判断特性当前的阶段。
无法根据目标浏览器或运行环境来自动确定需要转换的 ECMAScript 版本,例如当前babel是将特新转换到ES5语法,但是目标用户的环境已经支持ES6,那么这些转换以及polyfill都是多余的,并且导致转换之后的文件体积更大。
Babel7中废除了这两类的插件,使用
@babel/preset-env
来替代
babel-preset-latest(插件已弃用)
官方文档:https://www.npmjs.com/package/babel-preset-latest
babel-preset-latest包含了es2015, es2016, es2017,相当于是@babel/preset-env的雏形
Babel7中废除此插件,使用@babel/preset-env
来替代
@babel/preset-env(重要)
官网文档:https://babeljs.io/docs/babel-preset-env
重要的组件,@babel/preset-env是babel新的转换语法的预设集合,废除了Babel6中的preset-stage-[x]、babel-preset-es[x]、 babel-preset-latest包,整合成了@babel/preset-env。@babel/preset-env是"指挥"babel对代码进行何种规则的转换。
总的来讲有两大作用方面:
- 将目标代码的语法转换
- 充当polyfill,提供运行环境中缺失的js特性
@babel/preset-env默认会支持所有 ES标准的特性,如果没进入标准但是已经处于T39流程中的特性(低于T39流程的第三阶段),不再封装成 preset,需要手动指定@babel/plugin-proposal-[x]
插件来支持特性。
在babel 7.4.0之后,@babel/preset-env使用的polyfill放弃使用@babel/polyfill,转而使用core-js@3作为babel的polyfill
preset-env基础配置:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3.22"
}
]
]
}
useBuiltIns
useBuiltIns
设置项是Babel7引入的概念,用来指定preset-env,一共有三个选项值:
false
(默认)
只对javascript的语法进行转义,不进行polyfill转义,也就是不支持js运行环境中缺失的特性。
entry
当useBuiltIns
设置成"entry"时,表示在项目入口处引入polyfill,会根据配置的浏览器兼容性列表来导入polyfill,preset-env根据broswerslist
定义的浏览器环境下不支持的特性将对应的polyfill全部导入。具体使用方法见官网:https://babeljs.io/docs/babel-preset-env#usebuiltins
useBuiltIns配置为entry时,需要在项目入口手动引用引入core-js:
import 'core-js/stable';
有些教程还会引入regenerator-runtime,这不是必要的,在@babel/polyfill官网中说明:If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0, you must also load the regenerator runtime package. It is automatically loaded when using @babel/preset-env's useBuiltIns: "usage" option or @babel/plugin-transform-runtime.
总结下来7.18.0之前的版本才需要安装,如果高于这个版本则可以去掉。
entry配置的缺陷:如果我们的代码中仅仅用到了其中一个特性例如Array.from
,那么entry配置下不仅会将Array.from
特性的polyfill引入进来,同时也会引入未使用的特性的polyfill,这种非必要引入,会造成包的体积没有必要的增大。
usage
与entry不同的是:会在模块中智能分析用到了哪些polyfill并自动引入,引入的polyfill是broswerslist
目标环境中不支持的特性中并且代码使用到的情况下使用到的特性。
Promise、Map
特性为例,使用usage的配置下:
工程代码:
var a = new Promise();
var b = new Map();
如果目标环境不支持特性需要使用polyfill打包出来的代码:
import "core-js/modules/es.promise";
var a = new Promise();
import "core-js/modules/es.map";
var b = new Map();
如果目标环境已经支持该特性打包出来的代码:
var a = new Promise();
var b = new Map();
如果说模块有很多,并且模块都用到了某一相同特性,那么usage下会在每个模块下重复import。usage
是在各个模块内import polyfill,不会造成全局污染,而在entry
下配置引入polyfill只会在项目入口引入一次,将polyfill挂载到全局对象上,那么就会造成全局污染。
plugin-proposal插件引用问题
已经被纳入@babel/preset-env的特性可以从包的package.json中的dependencies
中查看,例如7.21.5的版本中:
"dependencies": {
"@babel/compat-data": "^7.21.5",
"@babel/helper-compilation-targets": "^7.21.5",
"@babel/helper-plugin-utils": "^7.21.5",
"@babel/helper-validator-option": "^7.21.0",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7",
"@babel/plugin-proposal-async-generator-functions": "^7.20.7",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-class-static-block": "^7.21.0",
"@babel/plugin-proposal-dynamic-import": "^7.18.6",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@babel/plugin-proposal-json-strings": "^7.18.6",
"@babel/plugin-proposal-logical-assignment-operators": "^7.20.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
...略
"@babel/plugin-transform-reserved-words": "^7.18.6",
"@babel/plugin-transform-shorthand-properties": "^7.18.6",
"@babel/plugin-transform-spread": "^7.20.7",
"@babel/plugin-transform-sticky-regex": "^7.18.6",
"@babel/plugin-transform-template-literals": "^7.18.9",
"@babel/plugin-transform-typeof-symbol": "^7.18.9",
"@babel/plugin-transform-unicode-escapes": "^7.21.5",
"@babel/plugin-transform-unicode-regex": "^7.18.6",
"@babel/preset-modules": "^0.1.5",
"@babel/types": "^7.21.5",
"babel-plugin-polyfill-corejs2": "^0.3.3",
"babel-plugin-polyfill-corejs3": "^0.6.0",
"babel-plugin-polyfill-regenerator": "^0.4.1",
"core-js-compat": "^3.25.1",
"semver": "^6.3.0"
},
但不是所有以@babel/plugin-proposal开头的插件都会被@babel/preset-env包含进来,@babel/preset-env只会包含已经达到ECMAScript规范的特性功能,对于还在提审阶段的功能不会包含,需要单独引入对应的@babel/plugin-proposal包:
假设需要手动安装@babel/plugin-proposal-class-properties插件,首先
npm i --save-dev @babel/plugin-proposal-class-properties
安装完成之后在babel的配置处制定preset-env使用该插件:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-proposal-class-properties"] }
@babel/preset-env总结
总结一下@babel/preset-env的作用是负责将的babel模块组装起来,通过
useBuiltIns
等配置项更加智能的设置babel,从而实现。并且需要根据项目实际情况,选择采用useBuiltIns
合适的配置模式。
这也是第一种解决项目的兼容性方案:@babel/preset-env
+core-js@3
,第二种方案在@babel/plugin-transform-runtime与@babel/runtime中会介绍。
升级至babel7需要将"babel-preset-env"
替换成"@babel/preset-env": "7.x.x"
@babel/plugin-transform-runtime与@babel/runtime
前面介绍了使用@babel/preset-env+core-js@3的解决兼容性方案但是这个方案有全局污染或者重复import代码的问题。
于是babel提供了runtime解决方案:@babel/plugin-tranform-runtime+@babel/runtime(或者@babel/runtime-core-js3)
@babel/runtime、@babel/runtime-corejs2、@babel/runtime-corejs3
@babel/runtime官方文档:https://babeljs.io/docs/babel-runtime
@babel/runtime-corejs2官方文档:https://babeljs.io/docs/babel-runtime-corejs2
@babel/runtime-corejs3官方文档: https://www.npmjs.com/package/@babel/runtime-corejs3
为了解决第一种方案的缺陷,@babel/runtime是一种按需加载的包,需要手动在文件顶部引入对应的polyfill依赖,例如对某个文件需要支持Promise
的特性,则在文件顶部手动使用import promise from 'babel-runtime/core-js/promise'
来引入polyfill支持。
@babel/runtime、@babel/runtime-corejs2、@babel/runtime-corejs3的区别:
- 从package.json中可以看出三者都依赖于
regenerator-runtime
,regenerator-runtime是一个运行时库,作用是将ES6的生成器和async函数转换为ES5的兼容代码。(生成器与async是ES6和ES8分别引入的,可以实现以同步的方式写异步的代码,从而一定程度的解决回调地狱问题) - @babel/runtime只依赖于regenerator-runtime,只能实现语法转换,此时不需要使用code-js对API做转换,使用时core-js设置为false
- @babel/runtime-corejs2依赖于regenerator-runtime、core-js@2,此时会使用core-js@2对API做转换,使用时core-js设置为2。
- @babel/runtime-corejs3依赖于regenerator-runtime、core-js-pure(也就是core-js@3),此时会使用core-js@3对API做转换,使用时core-js设置为3。
- 一般来说,如果你的目标环境支持大部分 ES6+ 语法,只需要异步函数支持(async/await),那么你可以选择使用 @babel/runtime。如果你的目标环境对 ES6+ 语法支持不全,那么你应该选择使用@babel/runtime-corejs3(@babel/runtime-corejs2有点旧了,直接上3),三者只需要任选其一即可。
单独使用runtime会有以下两个问题:
- 在runtime中babel会使用像_extend这样小的辅助函数,默认时会在每个require的文件中重复添加(不是引用)这种函数,尤其是当项目最终编译被分成多个文件时,这种小函数显然重复被添加多次是冗余。
单独使用@babel/runtime的引入方式完全依靠手动引入,而不会智能分析并引入每个文件中缺少的特性的polyfill,如果想实现根据每个文件缺失特性智能引入runtime,这时就轮到@babel/plugin-transform-runtime发挥作用了。
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime官方文档:https://babeljs.io/docs/babel-plugin-transform-runtime/
@babel/runtime官方文档:https://babeljs.io/docs/babel-runtime
@babel/plugin-transform-runtime主要解决了单独使用@babel/runtime的两个问题:
- @babel/plugin-transform-runtime可以通过AST分析自动分析缺失特性,然后智能引入runtime,不用手动require。所有的helpers函数都会引用@babel/runtime中的,这样避免了重复编译,所以runtime会被编译进最终的项目中去。
- 另一个使用@babel/plugin-transform-runtime的目的是为了给项目代码创建一个沙盒环境。什么是沙盒环境,它有什么用?我们先假设使用第一种bebel方案(core-js 和 @babel/polyfill )来提供引入Promise,Set,map这种特性,这会直接污染全局空间。而使用@babel/plugin-transform-runtime会将这些内置插件别名为core-js这样可以无需require polyfill便能无缝使用,沙盒环境便能避免出现全局变量的问题。
transform-plugin通常只在开发环境中使用,但是rutime会依赖于开发者的代码。并且需要注意的是,当@babel/plugin-transform-runtime被打开时,不要配置@babel/preset-env中的useBuiltIns,否则会造成插件不能在环境中完全沙盒化。
@babel/cli
官方网址:https://babeljs.io/docs/en/babel-cli
Babel 提供的命令行接口,可以通过命令行使用babel转换文件。
@babel/eslint-parser
官方网址:https://github.com/babel/babel/tree/main/eslint/babel-eslint-parser
这个组件允许 ESLint 在 Babel 转换之后源代码上执行,不支持 Babel 提供的实验性(如新特性)和非标准(如 Flow 或 TypeScript 类型)语法,这样 ESLint就可以支持 Babel 识别的最新的 JavaScript 特性。
@babel/plugin-proposal-class-properties
官方网址:https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
功能:这个 Babel 插件用于支持类属性的语法,已经被列为ES2022中的一个特性,目前已经被包含在@babel/preset-env中。
@babel/plugin-proposal-nullish-coalescing-operator
官方网址:https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator
功能:这个 Babel 插件用于支持空值合并运算符 (??),该语法用于在左操作数为 null 或 undefined 时,返回右操作数。
ES2020中的一个特性,已经被包含在@babel/preset-env中。
@babel/plugin-proposal-optional-chaining
官方网址:https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
功能:这个 Babel 插件用于支持可选链语法 (?.),这允许在对象上安全地访问嵌套属性,即使中间的某个属性不存在。
ES2020中的一个特性,已经被包含在@babel/preset-env中。
babel-plugin-transform-object-rest-spread
官方网址:https://babeljs.io/docs/en/babel-plugin-transform-object-rest-spread
功能:这个 Babel 插件用于支持对象剩余和展开操作符 (...),这允许更容易地复制和修改对象。
从 Babel 7.0 开始,此功能已经被内置,不再需要此插件。
Babel方案总结
总结下Babel的polyfill方案总共分为两种:
第一种@babel/preset-env、core-js@3(主流用3版本)
- 这种方案直接依赖core-js@3直接在全局对象中支持目标浏览器环境中缺失的特性,这种方案有个问题就是会造成全局污染,如果是业务代码没有什么问题,如果是作为工具库给其他人使用,不建议使用该方案。优点可以通过broswerslist定制指定需要babel转换的范围。
第二种@babel/preset-env、@babel/runtime(或者@babel/runtime-corejs3)、@babel/plugin-transform-runtime
- 这种方案通过core-js-pure实现隔离沙箱环境来避免全局污染,但是不能通过broswerslist指定范围,也就是说只要被识别出是core-js-pure中存在的api都会被转换,这样就缺失了方案一的优点,造成多余的转换。
参考资料
Babel官方资料
babel-plugin-transform-object-rest-spread
@babel/plugin-transform-runtime · Babel
使用指南 · Babel 中文文档 | Babel中文网
babel-loader | webpack 中文文档 cacheDirectory
babel/packages/babel-preset-env at main · babel/babel
@babel/runtime-corejs3
babel-plugin-transform-object-rest-spread
GitHub - zloirock/core-js: Standard Library
@babel/core · Babel资料介绍
(有误)
一文彻底读懂Babel
一口(很长的)气了解 babel
TC39、ECMA-262、ECMAScript 的一些事儿
Babel教程10:@babel/preset-env
babel版本变化 - 简单理解 - 渴望成为大牛的男人 - 博客园
不容错过的 Babel7 知识 - 掘金
回顾 babel 6和7,来预测下 babel 8
回顾babel历程,展望babel8_Dream-Long的博客-CSDN博客
babel 依赖 - 不想飞的小鸟 - 博客园
webpack基础篇_webpack query_接着奏乐接着舞。的博客-CSDN博客
peerdependency的作用
babel插件支持Object rest spread
webpack - 谁有关于@babel/runtime-corejs3的说明文档 - SegmentFault 思否
Babel 踩坑总结(三) —— 7.X 版本升级_babel-preset-stage-0_zwkkkk1的博客-CSDN博客
ES2020 空值合并操作符??和可选链操作符?._登楼痕的博客-CSDN博客
vue项目安装babel巧用es2020新语法
babel-preset-env使用指南 - 程序猿小卡 - 博客园
vue项目安装babel巧用es2020新语法
Babel 之 @babel/preset-env_谷易的博客-CSDN博客
@babel/plugin-transform-runtime(三) - 姜瑞涛的官方网站问题报错/解决方案
TypeError: token.type.endsWith is not a function vue eslint 报错问题修复
启动项目报错this.setDynamic is not a function - 码农教程
Using / for division is deprecated and will be removed in Dart Sass 2.0.0._Mr丶YangZCH的博客-CSDN博客
Parsing error: Cannot find module ‘babel-eslint‘解决方法_DX_release的博客-CSDN博客
解决WebPack使用babel处理es6问题_@babel/preset-env: ‘usebuiltlns’ is not a valid to_玲曦的博客-CSDN博客
[npm WARN deprecated core-js@2.6.11: core-js@<3 is no longer maintained and not recommended for usage due to the number of issues](https://stackoverflow.com/questions/59420896/npm-warn-deprecated-core-js2-6-11-core-js3-is-no-longer-maintained-and-not-r)
[Webpack中的query: { presets: [‘es2015’,‘react’] }_webpack presets_藏红的博客-CSDN博客](https://blog.csdn.net/ch717828/article/details/83755868?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EESLANDING%7Edefault-6-83755868-blog-125055826.pc_relevant_landingrelevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EESLANDING%7Edefault-6-83755868-blog-125055826.pc_relevant_landingrelevant&utm_relevant_index=13)
总结
一门编程语言或者一项技术其实就是一个产品,其背后是公司或者机构之间的对抗,其中的人情世故与勾心斗角,然后演变出了各种生态形式:开源、闭源、社区组织、公司等等,但是不管是什么形式的玩法,本质上都是完善产品,推广使用。一个产品想要做好,一门技术想要推广,并且活下去,殊途同归,一是赢得信任,二是满足需求,三形成壁垒。
首先是如何赢得信任?一种便捷的方法就是利用口碑与影响力作为背板(简单来讲就是有人帮你打包票),从而赢得信任。比如说可以依靠行业知名有影响力的企业做背书,发布产品,例如Golang、Android背后就是科技巨头谷歌在背书,产品一经问世便有大量的人愿意深入去尝试使用。还有一种方法就是不断打磨完善自身产品,用“品质”说话,老话说的好:酒香不怕巷子深,如果产品形成了好的口碑,那么会带动更多的人愿意尝试使用。但其实方法一的本质还是方法二,影响力与口碑不是凭空而来,还是通过做出一个个好的产品积累起来的,方法一更像是带杠杆的方法二,倘若推出的产品好用,那么就是锦上天花,反过来一个有影响力的企业生产的产品质量差,反而会被巨大的流量遭到反噬。
讲完了赢得信任,再聊聊满足需求,用技术视野来看,热门的语言像Javascript、Golang、java、python、c++往往在某一领域十分擅长,有别的语言无可比拟的优势,并且不容易被替代。比如js在浏览器中如鱼得水(当然后面也推出了node,这里不多提了),Golang构建出了云计算的很多生态,并且以简洁的语法,较为高效的运行速度深得开发者们的青睐,C++的高性能所以被使用在底层架构中...总之,热门的语言总是在某一个方面满足开发者的需求。除了满足个人的需求,从宏观历史角度来讲,也是抓住了时代的契机,顺应了某一时代的发展的需求。就像javascript给浏览器带来全新的交互形式,成就了Web2.0时代。
最后一点要形成自己的壁垒门槛,但这并,门槛有很多通过价格战、技术壁垒、人才垄断、信息差等等。展开讲讲技术门槛,回顾Javascript的历史发展,1995年出现了静态页面,1995年网景公司开发了Javascript,96年微软发布IE3浏览器且发布支持Jscript(可以理解成那会的Javascript的竞争对手)。网景为了web不被微软所垄断,寻求EMCA(欧洲计算机制造商协会)标准化组织的帮助来将javascript标准化,这样就使得大伙一起来参与形成标准。
5 条评论
博主太厉害了!
真好呢
怎么收藏这篇文章?
不错不错,我喜欢看
博主真是太厉害了!!!