Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Babel 全家桶 #20

Open
brunoyang opened this issue Jun 27, 2016 · 23 comments
Open

Babel 全家桶 #20

brunoyang opened this issue Jun 27, 2016 · 23 comments

Comments

@brunoyang
Copy link
Owner

brunoyang commented Jun 27, 2016

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',
      ],
    }
  }]
}

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

@AyumiKai
Copy link

Up!

@chunghaochow
Copy link

cool

@wonyun
Copy link

wonyun commented Feb 25, 2017

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

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

@brunoyang
Copy link
Owner Author

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

@wonyun
Copy link

wonyun commented Feb 26, 2017

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

@brunoyang
Copy link
Owner Author

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

@wonyun
Copy link

wonyun commented Feb 26, 2017

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

@lcoder
Copy link

lcoder commented Mar 18, 2017

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

@vcxiaohan
Copy link

好文,顶

@lcoder
Copy link

lcoder commented Mar 31, 2017

@wonyun 没明白意思

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

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

@wonyun
Copy link

wonyun commented Mar 31, 2017

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

@xiaofan9
Copy link

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

@RachelRen
Copy link

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

@unclemeric
Copy link

好文章!顶顶

@cunjieliu
Copy link

写得不错

@bigeyefish
Copy link

写的不错,顶

@450611
Copy link

450611 commented Jul 25, 2017

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

@JoeeeeeWu
Copy link

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

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

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

这样的理解对吗?

@wonyun @brunoyang

@JoeeeeeWu
Copy link

JoeeeeeWu commented Jul 27, 2017

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

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

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

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

这样的理解对吗?

@wonyun @brunoyang

@sohucw
Copy link

sohucw commented Aug 11, 2017

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

@rylan0119
Copy link

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

@CommanderXL
Copy link

CommanderXL commented Feb 1, 2018

@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
Copy link

"上面提到的插件可以将语法从 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
Labels
None yet
Projects
None yet
Development

No branches or pull requests