babel详解(一)

babel已经推出7.x版本,与babel紧密关联的core-js也推出了3.x版本,与babel7.x和core-js3.x对应的是babel6.x和core-js2.x。babel7.x和core-js3.x有很多新变化,值得学习,并且考虑在现有项目中对babel6.x进行升级处理。

从本篇开始,我将尝试从细节方面来总结最新版本babel(7.x)的作用及用法。经过这几天了解babel和core-js的最新文档,我认为熟练地掌握babel7.x有以下一些要点:

  1. babel是什么
  2. babel的plugins有什么作用
  3. babel的presets有什么作用
  4. babel/polyfill做什么用,现在有什么变化
  5. 什么是transform-runtime
  6. core-js是什么,它是怎么与babel集成使用的,core-js@3与core-js@2有什么变化
  7. 什么是babel/register
  8. 如何选择babel的配置方式?各个配置方式的应用场景是怎样的
  9. babel的配置options中各个选项的含义和作用
  10. 常见的babel使用方式都有哪些
  11. 如何升级babel7.x,目前其它与babel结合使用的工具,如webpack,对babel7.x的支持情况咋样?
  12. babel的api能做什么

以上要点涉及内容非常多,所以会分为n篇文章来处理,而且因为个人能力有限,每个内容在学习过程中,一定会遇到自己不熟悉的问题或场景,需要耗费时间去琢磨,所以这一系列内容能够更新完的时间也没法保证。

本篇围绕babel等一些基本概念做一些记录和总结。

babel是什么

简单来说,babel就是一个js代码的转换器。它主要的使用场景有:

  1. 把ES6的代码转换为ES5代码,这样即使代码最终的运行环境(常见:浏览器)不支持ES6,在开发期间也能使用最新语法提升开发效率和质量;
  2. 有些ES6最新api,目标运行环境(常见:浏览器)还没有普遍提供实现,babel借助core-js对可以自动给js代码添加polyfill,以便最终在浏览器运行的代码能够正常使用那些api;babel始终能提供对最新es提案的支持;
  3. 因为babel可以对代码进行处理,有些大厂团队,利用babel在做源码方面的处理(codemods),因为大厂人多,每个人开发习惯都有差异,为了规范代码风格,提高代码质量,借助babel,将每个人的源码做一定的标准化转换处理,能够提升团队整体的开发质量;
  4. vue框架推荐使用单文件.vue格式的方式来编写组件,它也是借助babel,将.vue文件,转换为标准的ES代码;
  5. react框架普遍使用JSX语法来编写模板,当然vue框架也可以使用jsx,借助babel,jsx也能被正确地转换为ES代码;
  6. js本身是弱类型语言,但是在大厂里面,人多特别容易因为语言本身一些小错误导致出现严重bug,所以如果在代码上线之前,借助强类型的语言静态分析能力,对js代码做一些检查的话,就能规避不少问题,flow和typescript目前是两种主流的强类型js的编程方式,babel能够将flow和typescript的代码,转换为标准的ES代码;
  7. babel因为会对代码进行转换,所以可选地能够生成代码的sourcemap,便于排查特殊问题;
  8. babel尽可能地在遵守ES的规格,当在学习babel的presets和plugins的时候,可能会看到有spec这个option,只要这个option启用了,相应presets和plugins就会按照更加符合规格的方式,对ES代码进行转换,只不过这个option一般是false,表示不启用,这样babel的转换速度会快一些;
  9. node运行环境,目前对es6的modules支持不是很好,babel提供了另外解决方案,可以把es6编写的modules在被require的时候,自动进行代码转换,虽然没法评定这个方案的优劣,但是也能感受到babel为了让开发人员能够使用最新ES语法这方面确实很努力;

开箱即用的babel什么也不做。这句话的含义就是如果不对babel不做任何配置,一段ES6的代码经过babel处理,还是原来那段代码。babel的功能都是通过plugin来完成的。每个plugin都有特定的代码处理的功能,只有将它配置到babel里面运行,才能对代码进行转换。babel们目前有很多的plugin,既有官方的,也有第三方的。

因为插件太多,每次配置起来都特别麻烦,每个项目需要的plugin,通常都是相似的一组plugin,所以babel推出了presets,每一个presets都包含若干个plugin,只要配置了presets,意味着这个preset包含的所有插件,都可能会参与到代码的转换当中。常见的preset有:preset-env,preset-state-0,preset-stage-2等等。

plugins和presets都是要经过配置才能生效的,除了这两个,babel还有很多其它的options可以配置,只有在挨个了解了每个option的作用之后,才能灵活得在项目中使用它们,所以babel的options也是学习babel的目标之一。

babel的options有很多种配置方式,可以在package.json中配置,也可以在babel.config.js中配置,可以在json格式的.babelrc文件中配置,也可以在js格式的.babelrc.js文件中配置。因为babel有命令行工具,所以options也可以直接在babel的命令行上使用时配置;因为babel有提供api,让开发者可以手工调用babel对代码进行转换,所以options也可以在api调用时配置。

调用babel有很多种方式,最底层的应该是直接利用api调用babel;后面要说的其它方式,都脱离不了这层形式。babel提供了命令行工具babel-cli,可以直接通过命令行对文件或文件夹进行转换,这个方式对于测试babel的一些特性,还比较方便。babel可以跟webpack、gulp等构建工具结合起来使用,当跟他们结合时,babel将由构建工具自动调用;这也是目前前端开发中,babel最常见的使用入口。babel还可以跟lint工具如eslint这些结合使用。在node环境中,babel提供了babel-register,实现ES6的文件被node加载时自动转换为ES5的代码。

ES6从语法上转换到ES5,babel是可以做到的,但是一些新的api,本身不是语法上不支持,而是ES5的运行环境没有对应的实现;所以babel的一项重要职责就是代码的polyfill。在babel7之前,babel专门提供了一个库叫babel/polyfill来做这件事情,在babel7之后,这个库被废弃了,因为polyfill有了新的使用方式。这也是babel7.x学习的重要内容之一。

因为babel在转换过程中,会利用很多babel自己的工具函数:helpers。在不经过优化的时候,每个文件都会单独包含这些helpers代码,如果文件很多,就会导致大量的重复代码,所以babel专门推出了transform-runtime来对这些helpers进行自动提取和其它优化。

babel对代码的polyfill,是利用另外两个库来做的:core-js和regenerator-runtime。core-js目前升级到了3.x版本,跟2.x区别也很多;regenerator-runtime没有什么变化。core-js@3.x的版本,也值得学习,将来很有可能会直接使用这个库里面的东西,所以需要掌握它是如何组织ES的各个模块实现的。

以上就是跟babel使用密切相关一些概念和要点,更细节的学习和记录,会在接下来的文章中一一总结。babel始终在和最新的ES规范打交道,所以下面的部分补充一些对ESMAScript相关概念的说明。

ECMA

ECMA是“European Computer Manufacturers Association”的缩写,中文称欧洲计算机制造联合会。是1961年成立的旨在建立统一的电脑操作格式标准–包括程序语言和输入输出的组织。这是个计算机权威的机构,负责多个计算标准制定和推进,不仅仅是ES。

ECMA负责的全部标准:https://www.ecma-international.org/publications/standards/Standard.htm

ECMAScript

ECMAScript是一种可以在宿主环境中执行计算并能操作可计算对象的基于对象的程序设计语言。ECMAScript是由网景的布兰登·艾克开发的一种脚本语言;最初命名为Mocha,后来改名为LiveScript,最后重命名为JavaScript。1995年12月,升阳与网景联合发表了JavaScript。1996年11月,网景公司将JavaScript提交给欧洲计算机制造商协会(ECMA)进行标准化。

ECMA-262

ECMA-262是Javascript提交给ECMA进行标准化后,制定出的标准名称。当时提交给ECMA的语言名称是Javascript,按理说ECMA-262这份标准对应的语言名称应该是Javascript才对,但是早期参与ECMA-262标准制定的组织,对于起名为Javascript有争议,所以最后妥协到将ECMA-262制定的语言命名为ECMAScript。从当前的环境来讲,当我们在谈论ECMA-262的时候,谈论的其实就是ECMAScript。

微软也把C#语言提交了ECMA进行标准化,最后经过ECMA的标准化工作,c#语言也有了一个ECMA的标准规范,编号为:ECMA-334。不过ECMA-334这份标准描述的语言最终命名还是c#,没有曾经ECMA-262给语言起名时的争议。

曾经sun公司也把java提交了ECMA进行标准化,但是害怕后面失去对java语言的控制权,于是又撤回了提交的决定,所以java语言最终并没有纳入ECMA的标准。

所以,262、334都是ECMA标准的名称,ECMAScript、C#分别是ECMA-262、ECMA-334这两份标准规范制定的编程语言名称。

ECMA-262标准内容可以从官方网站上下载查看,有pdf和html版本,500多页:https://www.ecma-international.org/publications/standards/Ecma-262.htm。

ECMAScript和Javascript的关系

这个标题里面的Javascript,是指当前在浏览器、node等环境中运行的javascript,不是早期提交给ECMA标准化的Javascript。前面说过,ECMAScript,是ECMA-262标准描述的编程语言名称。它其实是一个抽象语言名称,ECMA-262的内容,描述了这个语言该如何去实现,但是并没有给出特定运行环境的实现。

现在我们写的javascript,是ECMAScript的一种实现。ECMAScript还有另外两种常见的编程语言实现:JScript和ActionScript。JScript运行与IE浏览器之中,ActionScript可以运行于flash和flex平台。Javascript是目前最流行的ECMAScript的实现。

ECMAScript允许实现它的语言进行扩展,所以Javascript会包含超出ECMAScript的功能。一份完整的Javascript,实际上包含三个主要部分:

  1. ECMAScript
  2. DOM
  3. BOM

TC39

TC39:Ecma International, Technical Committee 39 - ECMAScript。TC39是ECMA组织里面专门负责ECMA-262也就是ECMAScript标准规范管理的一个分支结构。tc39目前管理ECMA-262的流程:https://tc39.es/process-document/。

任何人都可以向TC39提案,要求修改语言标准。一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由TC39委员会批准。

  • Stage 0 - Strawman(展示阶段)
  • Stage 1 - Proposal(征求意见阶段)
  • Stage 2 - Draft(草案阶段)
  • Stage 3 - Candidate(候选人阶段)
  • Stage 4 - Finished(定案阶段)

tc39当前的所有提案都可以在github上查看:github.com/tc39/ecma262
tc39每年处理提案的大致安排如下:

  • February 1: Candidate Draft is produced.
  • February - March: 60 day royalty-free opt-out period.
  • March TC39 Meeting: stage 4 proposals are incorporated, final semantics are approved, and the new spec version is branched from master. Only editorial changes are accepted from this point forward.
  • April-June: ECMA CC and ECMA GA review period.
  • July: Approval of new standard by the ECMA General Assembly
    也就是说只有stage 4阶段的提案,才可能在每年7月份的时候,加入到当前的新标准发布中。

ECMAScript的版本历史

ECMAScript的发展历史:
http://es6.ruanyifeng.com/#docs/intro#ECMAScript-%E7%9A%84%E5%8E%86%E5%8F%B2。

ESMAScript从ES6推出以来的版本发布情况:

版本号 发布日期及标准文件连接 版本名称
6 June 2015 ECMAScript 2015 (ES2015),也成为ES6
7 June 2016 ECMAScript 2016 (ES2016),也成为ES7
8 June 2017 ECMAScript 2017 (ES2017),也成为ES8
9 June 2018 ECMAScript 2018 (ES2018),也成为ES9
10 June 2019 ECMAScript 2019 (ES2019),也成为ES10

我们现在通常所说的ES6,其实是泛指ES2015发布以后,所有的ES版本,包括ES6、8、9、10。当别人说ES8、9、10的时候,说的其实就是ES2017、ES2018、ES2019。了解以上这些之后,对于一个前端开发者来说,关注TC39每年都有哪些新的提案,每年新的ESMA-262发布,都添加了哪些特性进入标准,应该是一个保持关注技术新特性的方式。

TEST262

TEST262: https://github.com/tc39/test262
这是官方提供的ECMAScript标准规范的全套测试用例。babel在它的roadmap中表示,他们想要对babel转换后的代码运行test262的用例,只要能通过test262的用例,说明babel转码后的代码,对ECMA-262标准规范的支持程度就很高。这个目前是babel的规划目标,敬请期待官方的发布这方面的信息吧。

本篇补充一个内容:

最新babel相关的npm包前面的@符号是什么含义

从babel7.0开始,babel一系列的包都以@babel开头,这个跟babel没关系,是npm包的一种形式。详细得介绍可参考下面的引用地址。

https://docs.npmjs.com/misc/scope

简而言之,@符号开始的包,如@babel/preset-env,代表的是一类有scope限定的npm包。scope通常的含义代表的就是一个公司或者一个机构、甚至个人,它的作用就是将同一个主体的相关包都集中在一个范围内来组织和管理,这个范围就是scope。这类有scope的包,最大的好处就是只有scope的主体公司、机构和个人,才能往这个scope里面添加新的包,别人都不行;也就是说以@开头的npm包,一定是官方自己推出或者官方认可推出的包,比较有权威性质。

这类包的安装,与普通的包安装没有太大区别,就是前面要加上scope限定而已@myorg:

1
npm install @myorg/mypackage

或者是:

1
2
3
4
5
{
"dependencies": {
"@myorg/mypackage": "^1.3.0"
}
}

普通的包,安装在node_modules/packagename这个文件夹下,而scope包,则安装在node_modules/@myorg/mypackage这个文件夹下,相比之下,scope包多了一层@myorg的文件夹。所以引入scope包的时候,必须带上这个scope文件夹:

1
require('@myorg/mypackage');

nodejs并没有对scope进行特殊处理,之所以要写成require('@myorg/mypackage'),也仅仅是因为这个包放在node_modules/@myorg文件夹下而已。