背景
说起ES6,webpack,打包,模块化总是离不开babel,babel作为一个js的编译器已经被广泛使用。在babel的官网是这样介绍它的:
Babel is a JavaScript compiler.
Use next generation JavaScript, today.
大家都知道js作为宿主语言,很依赖执行的环境(浏览器、node等),不同环境对js语法的支持不尽相同,特别是ES6之后,ECMAScrip对版本的更新已经到了一年一次的节奏,虽然每年更新的幅度不大,但是每年的提案可不少。babel的出现就是为了解决这个问题,把那些使用新标准编写的代码转译为当前环境可运行的代码,简单点说就是把ES6代码转译(转码+编译)到ES5。
经常有人在使用babel的时候并没有弄懂babel是干嘛的,只知道要写ES6就要在webpack中引入一个babel-loader,然后胡乱在网上copy一个.babelrc到项目目录就开始了(ps: 其实我说的是我自己)。理解babel的配置很重要,可以避免一些不必要的坑,比如:代码中使用Object.assign在一些低版本浏览器会报错,以为是webpack打包时出现了什么问题,其实是babel的配置问题。
ES6
正文之前先谈谈ES6,ES即ECMAScript,6表示第六个版本(也被称为是ES2015,因为是2015年发布的),它是javascript的实现标准。
被纳入到ES标准的语法必须要经过如下五个阶段:
- Stage 0: strawman
- Stage 1: proposal
- Stage 2: draft - 必须包含2个实验性的具体实现,其中一个可以是用转译器实现的,例如Babel。
- Stage 3: candidate - 至少要有2个符合规范的具体实现。
- Stage 4: finished
可以看到提案在进入stage3阶段时就已经在一些环境被实现,在stage2阶段有babel的实现。所以被纳入到ES标准的语法其实在大部分环境都已经是有了实现的,那么为什么还要用babel来进行转译,因为不能确保每个运行代码的环境都是最新版本并已经实现了规范。
更多关于ES6的内容可以参考hax的live:Hax:如何学习和实践ES201X?
Babel的版本变更
写这篇文章时babel版本已经到了v7.0.0-beta.3,也就是说7.0的正式版就要发布了,可喜可贺。但是今天不谈7.0,只谈babel6,在我知道并开始使用的babel的时候babel已经到了版本6,没有经历过5的时代。
在babel5的时代,babel属于全家桶型,只要安装babel就会安装babel相关的所有工具,
即装即用。
但是到了babel6,具体有以下几点变更:
- 移除babel全家桶安装,拆分为单独模块,例如:babel-core、babel-cli、babel-node、babel-polyfill等;
可以在babel的github仓库看到babel现在有哪些模块。
- 新增 .babelrc 配置文件,基本上所有的babel转译都会来读取这个配置;
- 新增 plugin 配置,所有的东西都插件化,什么代码要转译都能在插件中自由配置;
- 新增 preset 配置,babel5会默认转译ES6和jsx语法,babel6转译的语法都要在perset中配置,preset简单说就是一系列plugin包的使用。
babel各个模块介绍
babel6将babel全家桶拆分成了许多不同的模块,只有知道这些模块怎么用才能更好的理解babel。
下面的一些示例代码已经上传到了github,欢迎访问,欢迎star。
安装方式:
#通过npm安装
npm install babel-core babel-cli babel-node
#通过yarn安装
yarn add babel-core babel-cli babel-node
1、babel-core
看名字就知道,babel-core是作为babel的核心存在,babel的核心api都在这个模块里面,比如:transform。
下面介绍几个babel-core中的api
- babel.transform:用于字符串转码得到AST
-
/*
-
* @param {string} code 要转译的代码字符串
-
* @param {object} options 可选,配置项
-
* @return {object}
-
*/
-
babel.transform(code: string, options?: Object)
-
-
//返回一个对象(主要包括三个部分):
-
{
-
generated code, //生成码
-
sources map, //源映射
-
AST //即abstract syntax tree,抽象语法树
-
}
-
更多关于AST知识点请看这里。
一些使用babel插件的打包或构建工具都有使用到这个方法,下面是一些引入babel插件中的源码:
-
//gulp-babel
-
const babel = require('babel-core');
-
/*
-
some codes...
-
*/
-
module.exports = function(opts){
-
opts = opts || {};
-
return through.obj(function(file, enc, cb){
-
try {
-
const fileOpts = Object.assign({}, opts, {
-
filename: file.path,
-
filenameRelative: file.relative,
-
sourceMap: Boolean(file.sourceMap),
-
sourceFileName: file.relative,
-
sourceMapTarget: file.relative
-
});
-
const res = babel.transform(file.contents.toString(), fileOpts);
-
if (res !== null) {
-
//some codes
-
}
-
} catch (err) {
-
//some codes
-
}
-
}
-
}
-
-
//babel-loader
-
var babel = require("babel-core");
-
/*
-
some codes...
-
*/
-
var transpile = functiontranspile(source, options){
-
//some code
-
try {
-
result = babel.transform(source, options);
-
} catch (error) {
-
//some codes
-
}
-
//some codes
-
}
-
-
//rollup-pugin-babel
-
import { buildExternalHelpers, transform } from 'babel-core';
-
/*
-
some codes...
-
*/
-
export default function babel ( options ){
-
//some codes
-
return {
-
// some methods
-
transform ( code, id ) {
-
const transformed = transform( code, localOpts );
-
//some codes
-
return {
-
code: transformed.code,
-
map: transformed.map
-
};
-
}
-
}
-
}
-
上面是一些打包工具引入babel插件时的一些源码,可以看到基本都是先通过调用transform方法进行代码转码。
- babel.transformFile
-
//异步的文件转码方式,回调函数中的result与transform返回的对象一至。
-
babel.transformFile("filename.js", options, function(err, result){
-
result; // => { code, map, ast }
-
});
-
- babel.transformFileSync
-
//同步的文件转码方式,返回结果与transform返回的对象一至。
-
babel.transformFileSync(filename, options) // => { code, map, ast }
-
- babel.transformFromAst
-
//将ast进行转译
-
const { code, map, ast } = babel.transformFromAst(ast, code, options);
-
2、babel-cli
babel-cli是一个通过命令行对js文件进行换码的工具。
使用方法:
- 直接在命令行输出转译后的代码
babel script.js
- 指定输出文件
babel script.js --out-file build.js 或者是 babel script.js -o build.js
让我们来编写了一个具有箭头函数的代码:
-
//script.js
-
const array = [1,2,3].map((item, index)=> item * 2);
-
然后在命令行执行 babel script.js,发现输出的代码好像没有转译。
因为我们没有告诉babel要转译哪些类型,现在看看怎么指定转译代码中的箭头函数。
babel --plugins transform-es2015-arrow-functions script.js
或者在目录里添加一个.babelrc文件,内容如下:
{
"plugins": [
"transform-es2015-arrow-functions"
]
}
.babelrc是babel的全局配置文件,所有的babel操作(包括babel-core、babel-node)基本都会来读取这个配置,后面会详细介绍。
3、babel-node
babel-node是随babel-cli一起安装的,只要安装了babel-cli就会自带babel-node。
在命令行输入babel-node会启动一个REPL(Read-Eval-Print-Loop),这是一个支持ES6的js执行环境。
其实不用babel-node,直接在node下,只要node版本大于6大部分ES6语法已经支持,况且现在node的版本已经到了8.7.0。
babel-node还能直接用来执行js脚本,与直接使用node命令类似,只是会在执行过程中进行babel的转译,并且babel官方不建议在生产环境直接这样使用,因为babel实时编译产生的代码会缓存在内存中,导致内存占用过高,所以我们了解了解就好。
babel-node script.js
4、babel-register
babel-register字面意思能看出来,这是babel的一个注册器,它在底层改写了node的require方法,引入babel-register之后所有require并以.es6, .es, .jsx 和 .js为后缀的模块都会经过babel的转译。
同样通过箭头函数做个实验:
-
//test.js
-
const name = 'shenfq';
-
module.exports = ()=> {
-
const json = {name};
-
return json;
-
};
-
//main.js
-
require('babel-register');
-
var test = require('./test.js'); //test.js中的es6语法将被转译成es5
-
-
console.log(test.toString()); //通过toString方法,看看控制台输出的函数是否被转译
-
默认babel-register会忽略对node_modules目录下模块的转译,如果要开启可以进行如下配置。
-
require("babel-register")({
-
ignore: false
-
});
-
babel-register与babel-core会同时安装,在babel-core中会有一个register.js文件,所以引入babel-register有两种方法:
-
require('babel-core/register');
-
require('babel-register');
-
但是官方不推荐第一种方法,因为babel-register已经独立成了一个模块,在babel-core的register.js文件中有如下注释。
TODO: eventually deprecate this console.trace(“use the
babel-register
package instead ofbabel-core/register
“);
5、babel-polyfill
polyfill这个单词翻译成中文是垫片
的意思,详细点解释就是桌子的桌脚有一边矮一点,拿一个东西把桌子垫平。polyfill在代码中的作用主要是用已经存在的语法和api实现一些浏览器还没有实现的api,对浏览器的一些缺陷做一些修补。例如Array新增了includes方法,我想使用,但是低版本的浏览器上没有,我就得做兼容处理:
-
if (!Array.prototype.includes) {
-
Object.defineProperty(Array.prototype, 'includes', {
-
value: function(searchElement, fromIndex){
-
if (this == null) {
-
throw new TypeError('"this" is null or not defined');
-
}
-
var o = Object(this);
-
var len = o.length >>> 0;
-
if (len === 0) {
-
return false;
-
}
-
var n = fromIndex | 0;
-
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
-
while (k < len) {
-
if (o[k] === searchElement) {
-
return true;
-
}
-
k++;
-
}
-
return false;
-
}
-
});
-
}
-
上面简单的提供了一个includes方法的polyfill,代码来自MDN。
理解polyfill的意思之后,再来说说babel为什么存在polyfill。因为babel的转译只是语法层次的转译,例如箭头函数、解构赋值、class,对一些新增api以及全局函数(例如:Promise)无法进行转译,这个时候就需要在代码中引入babel-polyfill,让代码完美支持ES6+环境。前面介绍的babel-node就会自动在代码中引入babel-polyfill包。
引入方法:
-
//在代码的最顶部进行require或者import
-
-
require("babel-polyfill");
-
-
import "babel-polyfill";
-
-
//如果使用webpack,也可以在文件入口数组引入
-
module.exports = {
-
entry: ["babel-polyfill", "./app/js"]
-
};
-
但很多时候我们并不会使用所有ES6+语法,全局添加所有垫片肯定会让我们的代码量上升,之后会介绍其他添加垫片的方式。
.babelrc
前面已经介绍了babel常用的一些模块,接下来看看babel的配置文件 .babelrc
。
后面的后缀rc来自linux中,使用过linux就知道linux中很多rc结尾的文件,比如.bashrc
,rc是run command
的缩写,翻译成中文就是运行时的命令,表示程序执行时就会来调用这个文件。
babel所有的操作基本都会来读取这个配置文件,除了一些在回调函数中设置options参数的,如果没有这个配置文件,会从package.json
文件的babel属性中读取配置。
plugins
先简单介绍下 plugins ,babel中的插件,通过配置不同的插件才能告诉babel,我们的代码中有哪些是需要转译的。
这里有一个babel官网的插件列表,里面有目前babel支持的全部插件。
举个例子:
-
{
-
"plugins": [
-
"transform-es2015-arrow-functions", //转译箭头函数
-
"transform-es2015-classes", //转译class语法
-
"transform-es2015-spread", //转译数组解构
-
"transform-es2015-for-of" //转译for-of
-
]
-
}
-
//如果要为某个插件添加配置项,按如下写法:
-
{
-
"plugins":[
-
//改为数组,第二个元素为配置项
-
["transform-es2015-arrow-functions", { "spec": true }]
-
]
-
}
-
上面这些都只是语法层次的转译,前面说过有些api层次的东西需要引入polyfill,同样babel也有一系列插件来支持这些。
-
{
-
"plugins":[
-
//如果我们在代码中使用Object.assign方法,就用如下插件
-
"transform-object-assign"
-
]
-
}
-
-
//写了一个使用Object.assign的代码如下:
-
const people = Object.assign({}, {
-
name: 'shenfq'
-
});
-
//经过babel转译后如下:
-
var _extends = Object.assign || function(target){ for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-
const people = _extends({}, {
-
name: 'shenfq'
-
});
-
这种通过transform添加的polyfill只会引入到当前模块中,试想实际开发中存在多个模块使用同一个api,每个模块都引入相同的polyfill,大量重复的代码出现在项目中,这肯定是一种灾难。另外一个个的引入需要polyfill的transform挺麻烦的,而且不能保证手动引入的transform一定正确,等会会提供一个解决方案:transform-runtime
。
除了添加polyfill,babel还有一个工具包helpers,如果你有安装babel-cli,你可以直接通过下面的命令把这个工具包输出:
./node_modules/.bin/babel-external-helpers > helpers.js
这个工具包类似于babel的utils模块,就像我们项目中的utils一样,很多地方都会用到,例如babel实现Object.assign就是使用的helpers中的_extend方法。为了避免同一个文件多次引用babel的助手函数,通过external-helpers
插件,能够把这些助手函数抽出放到文件顶部,避免多次引用。
-
//安装: cnpm install --save-dev babel-plugin-external-helpers
-
-
//配置
-
{
-
"plugins": ["external-helpers"]
-
}
-
虽然这个插件能避免一个文件多次引用助手函数,但是并不能直接避免多个文件内重复引用,这与前面说到的通过transform添加polyfill是一样的问题,这些引用都只是module级别的,在打包工具盛行的今天,需要考虑如何减少多个模块重复引用相同代码造成代码冗余。
当然也可以在每个需要使用helpers的js文件顶部直接引入之前生成的helpers文件既可,通过打包工具将这个公共模块进行抽离。
-
require('helpers');
-
在说完babel的helpers之后就到了插件系统的最后的一个插件:transform-runtime
。前面在transform-polyfill的时候也有提到这个插件,之所以把它放到helpers后面是因为这个插件能自动为项目引入polyfill和helpers。
cnpm install -D babel-plugin-transform-runtime babel-runtime
transform-runtime这个插件依赖于babel-runtime,所以安装transform-runtime的同时最好也安装babel-runtime,为了防止一些不必要的错误。babel-runtime由三个部分组成:
-
core-js
core-js极其强悍,通过ES3实现了大部分的ES5、6、7的垫片,作者zloirock是来自战斗名族的程序员,一个人维护着core-js,听说他最近还在找工作,上面是core-js的github地址,感兴趣可以去看看。
-
regenerator
regenerator来自facebook的一个库,用于实现 generator functions。
- helpers
babel的一些工具函数,没错,这个helpers和前面使用babel-external-helpers生成的helpers是同一个东西
从babel-runtime的package.json文件中也能看出,runtime依赖了哪些东西。
安装有babel-runtime之后要引入helpers可以使用如下方式:
-
require('babel-runtime/helpers');
-
使用runtime的时候还有一些配置项:
-
{
-
"plugins": [
-
["transform-runtime", {
-
"helpers": false, //自动引入helpers
-
"polyfill": false, //自动引入polyfill(core-js提供的polyfill)
-
"regenerator": true, //自动引入regenerator
-
}]
-
]
-
}
-
比较transform-runtime与babel-polyfill引入垫片的差异:
- 使用runtime是按需引入,需要用到哪些polyfill,runtime就自动帮你引入哪些,不需要再手动一个个的去配置plugins,只是引入的polyfill不是全局性的,有些局限性。而且runtime引入的polyfill不会改写一些实例方法,比如Object和Array原型链上的方法,像前面提到的
Array.protype.includes
。 - babel-polyfill就能解决runtime的那些问题,它的垫片是全局的,而且全能,基本上ES6中要用到的polyfill在babel-polyfill中都有,它提供了一个完整的ES6+的环境。babel官方建议只要不在意babel-polyfill的体积,最好进行全局引入,因为这是最稳妥的方式。
- 一般的建议是开发一些框架或者库的时候使用不会污染全局作用域的babel-runtime,而开发web应用的时候可以全局引入babel-polyfill避免一些不必要的错误,而且大型web应用中全局引入babel-polyfill可能还会减少你打包后的文件体积(相比起各个模块引入重复的polyfill来说)。
presets
显然这样一个一个配置插件会非常的麻烦,为了方便,babel为我们提供了一个配置项叫做persets(预设)。
预设就是一系列插件的集合,就好像修图一样,把上次修图的一些参数保存为一个预设,下次就能直接使用。
如果要转译ES6语法,只要按如下方式配置即可:
-
//先安装ES6相关preset: cnpm install -D babel-preset-es2015
-
{
-
"presets": ["es2015"]
-
}
-
-
//如果要转译的语法不止ES6,还有各个提案阶段的语法也想体验,可以按如下方式。
-
//安装需要的preset: cnpm install -D babel-preset-stage-0 babel-preset-stage-1 babel-preset-stage-2 babel-preset-stage-3
-
{
-
"presets": [
-
"es2015",
-
"stage-0",
-
"stage-1",
-
"stage-2",
-
"stage-3",
-
]
-
}
-
-
//同样babel也能直接转译jsx语法,通过引入react的预设
-
//cnpm install -D babel-preset-react
-
{
-
"presets": [
-
"es2015",
-
"react"
-
]
-
}
-
不过上面这些preset官方现在都已经不推荐了,官方唯一推荐preset:babel-preset-env
。
这款preset能灵活决定加载哪些插件和polyfill,不过还是得开发者手动进行一些配置。
-
// cnpm install -D babel-preset -env
-
{
-
"presets": [
-
["env", {
-
"targets": { //指定要转译到哪个环境
-
//浏览器环境
-
"browsers": ["last 2 versions", "safari >= 7"],
-
//node环境
-
"node": "6.10", //"current" 使用当前版本的node
-
-
},
-
//是否将ES6的模块化语法转译成其他类型
-
//参数:"amd" | "umd" | "systemjs" | "commonjs" | false,默认为'commonjs'
-
"modules": 'commonjs',
-
//是否进行debug操作,会在控制台打印出所有插件中的log,已经插件的版本
-
"debug": false,
-
//强制开启某些模块,默认为[]
-
"include": ["transform-es2015-arrow-functions"],
-
//禁用某些模块,默认为[]
-
"exclude": ["transform-es2015-for-of"],
-
//是否自动引入polyfill,开启此选项必须保证已经安装了babel-polyfill
-
//参数:Boolean,默认为false.
-
"useBuiltIns": false
-
}]
-
]
-
}
-
关于最后一个参数useBuiltIns
,有两点必须要注意:
- 如果useBuiltIns为true,项目中必须引入babel-polyfill。
- babel-polyfill只能被引入一次,如果多次引入会造成全局作用域的冲突。
做了个实验,同样的代码,只是.babelrc
配置中一个开启了useBuiltIns
,一个没有,两个js文件体积相差70K,戳我看看。
useBuiltIns.js | 189kb |
notUseBuiltIns.js | 259kb |
最后啰嗦一句
关于polyfill还有个叫做polyfill.io的神器,只要在浏览器引入
服务器会更具浏览器的UserAgent返回对应的polyfill文件,很神奇,可以说这是目前最优雅的解决polyfill过大的方案。
前前后后写完这个差不多写了一个星期,查了很多资料(babel的官网和github都看了好几遍),总算憋出来了。
相关推荐
3. 确保你的Babel配置正确处理源文件,并输出转换后的代码。 现在,当Babel编译你的项目时,所有使用`require`和`module.exports`的地方都会被转换为`import`和`export`。这使得你的代码能够在支持ESM的环境中正常...
标题中的"babel+babel-polyfill兼容IE,ES6转ES5"指的就是利用Babel转换ES6代码,并通过使用`babel-polyfill`库来实现对旧版浏览器的兼容。 **Babel** 是一个广泛使用的JavaScript转换器,它可以将最新的JavaScript...
该工具包旨在促进化学数据的共享与交流,并为化学家、生物学家以及其他科研人员提供一个灵活强大的化学数据管理平台。 #### 1.2 常见问题解答 文档中包含了针对OpenBabel的一些常见问题及其解答,这些问题可能包括...
- **生成器(Generator)**: 分析 @babel/generator,了解如何将转换后的 AST 转回可执行的 JavaScript。 5. **开发实践** - **配置 Babel**: 学习如何通过 .babelrc 或 package.json 文件设置 Babel 配置,包括...
2. **配置.babelrc**:创建一个`.babelrc`或`babel.config.js`文件来设置Babel的配置,包括指定预设集和插件。 3. **使用Babel CLI**:通过命令行工具将源代码转换为目标代码,例如: ``` npx babel src --out-...
标题中的"babelmacros启用零配置可导入的babel插件"指的是`babel-macros`,这是一个非常有用的工具,它允许开发者在使用Babel转换JavaScript代码时,实现自定义的预处理逻辑,而且无需进行繁琐的配置。`babel-macros...
该压缩包中可能包含了`.babelrc`配置文件,这是Babel的默认配置文件,用于定义项目的转译规则。通过修改`.babelrc`,开发者可以定制Babel的行为,包括指定预设和插件,以及设置各种选项。 5. **CLI工具和集成**: ...
1. **优化配置**: `babel-preset-metalab` 提供了优化过的Babel配置,这意味着它可能包含了针对特定性能优化或代码风格的设定,不同于默认的Babel配置。 2. **兼容性预设**: 这个预设可能包含对旧版浏览器的支持,...
在Webpack配置中,`babel-loader`是一个关键的加载器(loader),用于处理JavaScript源码,确保新语法在旧版本浏览器中也能正常运行。 在"webpack入门和实战(二)"中,我们将深入理解Webpack的loader和plugin系统,...
这个版本号 `6a2c940` 指向的是 Babel 的某个 Git 提交哈希值,意味着你可以查看该版本的源代码,了解其内部实现和工作原理,甚至参与贡献代码。 总的来说,Babel 是现代前端开发中的关键工具,它使得开发者可以...
Babel提供预设(presets)来简化配置,`@babel/preset-env`是最常用的一个。它可以根据目标环境(如浏览器版本或Node.js版本)自动选择需要转换的ES特性,这样可以避免不必要的编译开销。 ### Babel插件(Plugins)...
这个文件可能包含配置文件、源代码、测试用例等,用于实现或演示如何使用Babel codemod来转换JavaScript代码。 **知识点详解:** 1. **Babel**:Babel是一个广泛使用的JavaScript编译器,能够将ES6+(包括最新的...
babel-transform-config 如果您想以编程方式更新Nuxt或Next配置文件。自述文件:wip 了解其作用的最简单方法: git clone https://github.com/hypervillain/babel-transform-config ;cd babel-transform-config && ...
2. **配置.babelrc**:创建`.babelrc`文件,定义Babel的配置,比如预设和插件。在这个例子中,我们使用`@babel/preset-env`预设。 ```json { "presets": ["@babel/preset-env"] } ``` 3. **执行转译**:通过Babel...
Webpack-babel-env-deps的主要功能是分析项目的Webpack配置,找出所有需要通过Babel进行转译的依赖。这个插件能够帮助开发者确保所有的ES6+特性都被适当地转换,防止因为某些未被转换的代码导致的运行时错误。以下是...
4. **Babel配置**:虽然这里直接引入了Babel,但通常在实际项目中,我们还需要配置.babelrc文件,来指定需要转换的语法插件和presets,比如`@babel/preset-env`可以自动选择目标环境支持的语法。 5. **类型声明`...
接下来,在Babel的配置文件(如`.babelrc`或`babel.config.js`)中,将`babel-plugin-polished`添加到plugins数组中: ```json { "plugins": ["babel-plugin-polished"] } ``` 现在,你可以在JavaScript代码中...
例如,你可以在Webpack的配置文件中使用`babel-load-config`来加载Babel配置,这样每次打包时都会按照预期处理源代码。 下面是一个简单的示例,展示如何在Webpack配置中使用`babel-load-config`: ```javascript ...
6. **配置Babel**:开发者需要在项目中设置 `.babelrc` 或 `babel.config.js` 文件来指定编译规则和插件,包括目标浏览器列表。 7. **使用Babel**:可以通过命令行工具或者集成到构建流程(如 Webpack 或 Gulp)中...
3. .babelrc配置:通过.babelrc文件,开发者可以配置Babel的转换规则和插件。 4. @babel/preset-react:这是一个Babel预设,包含了将JSX转换为React.createElement调用的规则。 5. 开发环境集成:Babel通常与构建...