Skip to content

Babel 全家桶 #20

Open
Open
@brunoyang

Description

@brunoyang

15 年 11 月,Babel 发布了 6.0 版本。相较于前一代 Babel 5,新一代 Babel 更加模块化, 将所有的转码功能以插件的形式分离出去,默认只提供 babel-core。原本只需要装一个 babel ,现在必须按照自己的需求配置,灵活性提高的同时也提高了使用者的学习成本。下面就来讲讲 Babel 全家桶中的各个部分。

npm i babel

已经弃用,你能下载到的仅仅是一段 console.warn,告诉你 babel 6 不再以大杂烩的形式提供转码功能了。

npm i babel-core

babel-core 的作用是把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。

以下代码转自阮一峰老师博客

const babel = require('babel-core');

// 字符串转码
babel.transform('console.log', options);
// => { code, map, ast }

// 文件转码(异步)
babel.transformFile('filename.js', options, (err, result) => {
  result; // => { code, map, ast }
});

// 文件转码(同步)
babel.transformFileSync('filename.js', options);
// => { code, map, ast }

// Babel AST转码
babel.transformFromAst(ast, code, options);
// => { code, map, ast }

npm i babel-register

该模块给 require 加了个钩子,.jsjsx.eses6 后缀的模块都会先转码。此外有几点需要注意:

  1. 当前文件不会被转码;
  2. 需首先加载 babel-register
  3. 由于是实时转码,只适合开发环境。
require('babel-register')
const index = require('./index.jsx')

npm i babel-cli -g

babel 命令行工具,可以转码文件或目录并输出至指定文件,直接看用法:

#1. 直接在终端输出
$ babel script.js
# output...

#2. 输出到文件
$ babel script.js --out-file script-compiled.js

#3. 支持 source maps
$ babel script.js --out-file script-compiled.js --source-maps

#4. watch 变动并输出到文件
$ babel script.js --watch --out-file script-compiled.js

#5. 可以编译文件夹
$ babel src --out-dir lib

#6. 也可以编译成一个文件
$ babel src --out-file script-compiled.js

npm i babel-plugin-*

babel-plugin-* 代表了一系列的转码插件,如babel-plugin-transform-es2015-arrow-functions 用于转码 es6 中的箭头函数,babel-plugin-transform-async-to-generator 用于将 es7 中的 async 转成 generator。

.babelrc中的配置:

{
  plugins: [
    'transform-es2015-arrow-functions',
    'transform-async-to-generator',
  ]
}

npm i babel-preset-*

我们现在有了 babel-plugin 系列,可以按需配置自己想要的特性。但若是想搭个 es6 环境,一个个地配置各个插件,我猜你会疯掉。babel-preset 系列就可以满足我们的需求,babel-preset 系列打包了一组插件,类似于餐厅的套餐。如 babel-preset-es2015 打包了 es6 的特性,babel-preset-stage-0 打包处于 strawman 阶段的语法(关于 ECMAScript 制定流程可以看这里),

.babelrc中可以这样配置:

{
  presets: [
    'es2015',
    'stage-3',
    'react'
  ]
}

npm i babel-runtime / babel-polyfill

上面提到的插件可以将语法从 es6 转成 es5,但没有提供 api 的转码功能,如 Promise、Set、Map 等新增对象,Object.assign、Object.entries 等全局对象上的新增方法都不会转码。而 babel-runtimebabel-polyfill 就是为此而生。

这两个模块功能几乎相同,就是转码新增 api,模拟 es6 环境,但实现方法完全不同。babel-polyfill 的做法是将全局对象通通污染一遍,比如想在 node 0.10 上用 Promise,调用 babel-polyfill 就会往 global 对象挂上 Promise 对象。对于普通的业务代码没有关系,但如果用在模块上就有问题了,会把模块使用者的环境污染掉。

babel-runtime 的做法是自己手动引入 helper 函数,还是上面的例子,const Promise = require('babel-runtime/core-js/promise') 就可以引入 Promise。

但 babel-runtime 也有问题,第一,很不方便,第二,在代码中中直接引入 helper 函数,意味着不能共享,造成最终打包出来的文件里有很多重复的 helper 代码。所以,babel 又开发了 babel-plugin-transform-runtime,这个模块会将我们的代码重写,如将 Promise 重写成 _Promise(只是打比方),然后引入_Promise helper 函数。这样就避免了重复打包代码和手动引入模块的痛苦。此外,babel-runtime 不能转码实例方法,比如这样的代码:

'!!!'.repeat(3);
'hello'.includes('h');

这只能通过 babel-polyfill 来转码,因为 babel-polyfill 是直接在原型链上增加方法。

babel-polyfill vs babel-runtime

那什么时候用 babel-polyfill 什么时候用 babel-runtime 呢?如果你不介意污染全局变量(如上面提到的业务代码),放心大胆地用 babel-polyfill ;而如果你在写模块,为了避免污染使用者的环境,没的选,只能用 babel-runtime + babel-plugin-transform-runtime

和 webpack 配合

很少有大型项目仅仅需要 babel,一般都是 babel 配合着 webpack 或 glup 等编译工具一起上的。下面就来介绍下 babel 和 webpack 如何 1 + 1 > 2。

为了显出 babel 的能耐,我们分别配个用 babel-polyfillbabel-runtime 、支持 react 的webpack.config.js

先来配使用 babel-runtime 的:

module: {
  loaders: [{
    loader: 'babel',
    test: /\.jsx?$/,
    include: path.join(__dirname, 'src'),
    query: {
      plugins: ['transform-runtime'],
      presets: [
        'es2015', 
        'stage-0',
        'stage-1',
        'stage-2',
        'stage-3',
        'react',
      ],
    }
  }]
}

需要注意的是,babel-runtime 虽然没有出现在配置里,但仍然需要安装,因为 transform-runtime 依赖它。

再来个 babel-polyfill 的:

entry: [
  'babel-polyfill',
  'src/index.jsx',
],

module: {
  loaders: [{
    loader: 'babel',
    test: /\.jsx?$/,
    include: path.join(__dirname, 'src'),
    query: {
      presets: [
        'es2015', 
        'stage-0',
        'stage-1',
        'stage-2',
        'stage-3',
        'react',
      ],
    }
  }]
}

行文仓促,多有疏漏,理解上可能有偏差,欢迎各位搞个大新闻。

Activity

AyumiKai

AyumiKai commented on Jul 22, 2016

@AyumiKai

Up!

chunghaochow

chunghaochow commented on Feb 6, 2017

@chunghaochow

cool

wonyun

wonyun commented on Feb 25, 2017

@wonyun

请问一下作者,babel-runtime不污染全局,用到什么需要polyfill的api需要手动引入,比如promise;而babel-plugin-transform-runtime引入后,还是会用到什么api需要单独手动引入么???

若是这样,这样开发时岂不是很繁琐?

brunoyang

brunoyang commented on Feb 25, 2017

@brunoyang
OwnerAuthor

@wonyun 首先感谢回复。行文上可能有疏漏引起误解。因为 babel-runtime 都是自动帮你引入所需 polyfill,但每个文件引入的都是全量的 polyfill,很容易引起体积暴涨。所以我们可以加上 babel-plugin-transform-runtime,这样 runtime 所用的 polyfill 就会从 transform-runtime 里引入,就避免了体积暴涨的问题。

wonyun

wonyun commented on Feb 26, 2017

@wonyun

@brunoyang ,不太明白这句因为 babel-runtime 都是自动帮你引入所需 polyfill,比方说我在代码中用到了Promise,在IE8下,babel-runtime难道会自动的帮我require这个promise的polyfill,还是需要开发者在代码开头手动引入这个promise polyfill文件?

brunoyang

brunoyang commented on Feb 26, 2017

@brunoyang
OwnerAuthor

@wonyun 应该这么说,Babel 中 runtime 只是个 helper 函数库,runtime transform 根据 ast 结果帮你引入所需 helper 函数。所以是只用 runtime 是需要手动,但不要这样用,而是用 runtime transform。

wonyun

wonyun commented on Feb 26, 2017

@wonyun

@brunoyang ,非常感谢,理解其中情况,babel-runtime是会自动引入polyfill的

lcoder

lcoder commented on Mar 18, 2017

@lcoder

博主,问下transform-runtimetransform-decorators-legacytransform-class-properties这些plugins引入的数组顺序会有关系吗

vcxiaohan

vcxiaohan commented on Mar 31, 2017

@vcxiaohan

好文,顶

lcoder

lcoder commented on Mar 31, 2017

@lcoder

@wonyun 没明白意思

非常感谢,理解其中情况,babel-runtime是会自动引入polyfill的

这句话中babel-plugin-runtime-transform 是自动的吧?所有的helper,一个bundle里面就一个;而babel-runtime,一个模块(文件)里一个,打包成bundle就有好多冗余的代码了。

wonyun

wonyun commented on Mar 31, 2017

@wonyun

@lcoder 对,一个bundle里面一个但不用太在意,这些helper的体积很小,用这些helper换来的是自动引入polyfill的开发体验

xiaofan9

xiaofan9 commented on Apr 11, 2017

@xiaofan9

那问问作者,babel-plugin-runtime-transform,引入的模块,需要手动安装并在.babelrc添加plugins或presets么?

RachelRen

RachelRen commented on May 11, 2017

@RachelRen

现在引入了runtime-transform,但是发现array中的fill不起作用,然后又去引用了polyfill,是在webpack中引入的,但是发现都不起作用

unclemeric

unclemeric commented on Jun 5, 2017

@unclemeric

好文章!顶顶

cunjieliu

cunjieliu commented on Jun 20, 2017

@cunjieliu

写得不错

bigeyefish

bigeyefish commented on Jun 22, 2017

@bigeyefish

写的不错,顶

450611

450611 commented on Jul 25, 2017

@450611

请问作者, es2015 跟 stage-* 是不是得同时存在, 如果自己写的代码存在es6、7语法的话

JoeeeeeWu

JoeeeeeWu commented on Jul 26, 2017

@JoeeeeeWu

是不是说transform-runtime有两个作用:

  • 如果只用babel-runtime,那么如果多个模块都引用了helper,那么这些模块中都会有这些helper。如果用了transform-runtime,会将重复的helper提取出来让所有的模块重用

  • 如果只用babel-runtime,需要通过类似const Promise = require('babel-runtime/core-js/promise')这样的方式手动引入。而如果用了transform-runtime就不用管了,需要用到的地方会自动引入

这样的理解对吗?

@wonyun @brunoyang

JoeeeeeWu

JoeeeeeWu commented on Jul 27, 2017

@JoeeeeeWu

还有一个疑问就是,babel-polyfill和babel-runtime + transform-runtime这两种方案是不是有以下区别:

  • babel-polyfill的方案会把所有的都打包进来

  • babel-runtime + transform-runtime 的方案只会把那些用到的helper提取打包进来,而那些没用到的则不会。

那么这样的话babel-runtime + transform-runtime会比babel-polyfill的方案更省空间。

这样的理解对吗?

@wonyun @brunoyang

sohucw

sohucw commented on Aug 11, 2017

@sohucw

@xiaozhouwu babel-runtime 是会自动引入 polyfill 每一个模块 都有
ransform-runtime 的方案只会把那些用到的重复helper过滤掉 打包。
babel-polyfill 应该是全局的

rylan0119

rylan0119 commented on Oct 29, 2017

@rylan0119

有点疑惑想请教,transform-runtime中的"helpers"和"moduleName"到底设置了什么,我是用webpack打包的,感觉没什么影响,官网上的解释也不是很理解

CommanderXL

CommanderXL commented on Feb 1, 2018

@CommanderXL

@xiaozhouwu

babel-polyfill 内部实现包含了所有的polyfill,而且部分polyfill是通过改变全局对象的方式。使用的方式也是直接在你的入口文件中去引入babel-polyfill
babel-runtime及其插件babel-plugin-transform-runtime,会在babel对你的代码编译的过程中,分析你的代码,如果用到了一些新的API,需要引入polyfill的代码,会自动帮你引入在core-js或者regenerator.js中的shim代码。babel-plugin-transform-runtime这个插件主要是做了一层映射,映射到babel-runtime内引用的core-jsregenerator.js中具体对应的helper:

如果你使用了一些新的实例上的方法,例如:

const str = [1, 2, 3]
str.includes(2)

像这种实例上添加的新的方法,仅仅用core-js中的shim是没法解决的。这个时候必须要引入babel-polyfill

babel-runtime及其插件babel-plugin-transform-runtime事实上就是按需去引入你需要的helper,而babel-polyfill是全部引入进去,从某种程度上说,确实比全部引入babel-polyfill最终打出的包更小。

zhangenming

zhangenming commented on Apr 10, 2018

@zhangenming

"上面提到的插件可以将语法从 es6 转成 es5,但没有提供 api 的转码功能,如 Promise、..."
不太对吧,
比如babel-plugin-transform-object-assign属于 babel-plugin-*可以转换Object.assign

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @RachelRen@wonyun@brunoyang@lcoder@chunghaochow

        Issue actions

          Babel 全家桶 · Issue #20 · brunoyang/blog