babel实践:真实gulp项目支持ES6转译ES5的跳坑指北

Posted 前端小二

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了babel实践:真实gulp项目支持ES6转译ES5的跳坑指北相关的知识,希望对你有一定的参考价值。

这两天,在对现有项目进行框架优化,由于项目使用gulp+jQuery构建的,不支持ES6规范,不能很好满足越来越复杂的需求场景,尤其是需要多异步任务的情况下,js又要异步又要操作各种DOM状态,状态与状态间也是各种紧耦合,单纯使用es5和jQuery,已经开始影响开发效率了。

虽然通过相关设计模式的使用,一定程度上减轻了js逻辑处理的复杂度,但看着有更佳实践的ES6语法不能用而只能白流口水,实在是不能忍,尤其是口水已久的ES6中的Promise对象,简直异步最爱,也是我这次优化最想拿下的目标。趁有点空闲,果断优化编译流程,打算添加支持编译ES6功能,主要是要支持Promise

为不对现有项目造成影响,我本地新建个小demo,打算最后能编译通过再移植到现有项目中。

新建项目es

其中src目录是js源代码目录,本次测试js放在src/js/test1.js文件中,测试涉及ES6语法:letPromiseObject.assgin()、字符串扩展。

 1// Promise 对象
2function test(x){
3    return new Promise((resolve, reject)=>{
4        if(x > 10){
5            resolve(x)
6        }else {
7            reject('x小于10');
8        }
9    })
10}
11let result = test(13);
12result
13    .then((x)=>{
14        console.log(x);
15    })
16    .catch((err)=>{
17        console.log(err);
18    })
19
20//Object.assign() 方法
21const object1 = {
22    a1,
23    b2,
24    c3
25};
26const object2 = Object.assign({c4d5}, object1);  
27console.log(object2.c, object2.d);
28
29// ES6 字符串语法
30let str1 = '12345';
31let str = `口令aa${str1}`;
32console.log(str);

在es项目根目录创建package.json并安装依赖:

  • npm install gulp babel-core gulp-babel babel-preset-env babel-plugin-transform-runtime --save-dev

  • npm install babel-polyfill --save
    由于项目打包后还需要对babel-polyfill进行依赖,所以安装在dependencies中。


gulp不用说,是构建工具。

下面是重点:

js作为宿主语言,非常依赖执行环境(浏览器、node等)。不同环境对js语法的支持也不同,甚至不同浏览器可能也会对js语法的支持存在差异。目前对于ES5语法的支持基本都没有问题,但是对于ES6乃至ES7甚至更高版本的JS语法,支持还远没有完善。

在WEB开发中,如果想使用高版本的JS语法用到那些更好的语法实践,就需要先将高版本的JS语法编译成低版本的ES5语法,来尽量兼容各浏览器。babel就是用来做这个编译工作。

babel5时,babel是全家桶形的,装个babel其他就不需要管了,因为所以相关工具插件全装好,但babel升级到版本6后,移除全家桶,将各工具拆分成单独模块,比如babel-corebabel-clibabel-nodebabel-polyfill等;并且新增了.babelrc配置文件,所有babel转译都会先读其中的配置再进行后续操作;新增 plugin 配置,所有的东西都插件化,什么代码要转译都能在插件中自由配置;新增 preset 配置,babel5会默认转译ES6和jsx语法,babel6转译的语法都要在perset中配置,preset简单说就是一系列plugin包的使用

其中babel-core是核心模块,babel的核心api都在这个模块里。比如transformbabel.transform用于字符串转码得到AST…

gulp-babel是专供gulp用的。

babel-polyfill注意,这家伙是有大作用的。 因为babel默认只转译新的javascript句法,而不会转译新的API,比如IteratorGeneratorSetMapsProxyReflectSymbolPromise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码,所以babel-polyfill必加,不然如果项目的js文件中有Promise等全局对象,那么就算用  babel-preset-env 转化过后,代码中还是存在 Promise对象,对于兼容性并没有什么用。(这里我踩了三个小时的坑才爬出来,明明编译通过却没有转译Promise,并且还没有任何报错,为找原因差点把头发都拔光,无知害死人啊啊啊…)

上面是babel的模块,前面说了,还有个配置文件.babelrc需要配置。babel所有的操作基本都会来读取这个配置文件,除了一些在回调函数中设置options参数的,如果没有这个配置文件,会从package.json文件的babel属性中读取配置。配置的对象属性为presets(预设)、plugins插件。

目前官方推荐使用babel-preset-env来进行presets配置,详情配置如下:

 1// npm install babel-preset-env --save-dev
2{
3    "presets": [
4        ["env", {
5            "targets": { //指定要转译到哪个环境
6                //浏览器环境
7                "browsers": ["last 2 versions""safari >= 7"],
8                //node环境
9                "node""6.10"//"current"  使用当前版本的node
10
11            },
12             //是否将ES6的模块化语法转译成其他类型
13             //参数:"amd" | "umd" | "systemjs" | "commonjs" | false,默认为'commonjs'
14            "modules"'commonjs',
15            //是否进行debug操作,会在控制台打印出所有插件中的log,已经插件的版本
16            "debug"false,
17            //强制开启某些模块,默认为[]
18            "include": ["transform-es2015-arrow-functions"],
19            //禁用某些模块,默认为[]
20            "exclude": ["transform-es2015-for-of"],
21            //是否自动引入polyfill,开启此选项必须保证已经安装了babel-polyfill
22            //参数:Boolean,默认为false.
23            "useBuiltIns"false
24        }]
25    ]
26}

注意上例中的mudules属性,其作用是将es6模块化语法转译成其他类型,这里请根据你生产代码的实现部署场景选择相应的模块规范,选false则会转译成ES模块规范,这里也被坑过,比如我开始没选,转译默认选择的commonjs的模块规范,结果浏览器打印报require not defined错误,也是坑了好久才找到这么个犄角旮旯的知识点,这里要吐槽babel的文档不是很全呐。

另外当转译成ES6模块规范后,还有个需要注意的,在html页面script引用编译后js时,由于已经是使用模块化了,所以在script属性中要加上type="module",这块可以看下ES6的 Module 的加载实现部分。

关于最后一个参数useBuiltIns,有两点必须要注意:

  • 如果useBuiltInstrue,项目中必须引入babel-polyfill

  • babel-polyfill只能被引入一次,如果多次引入会造成全局作用域的冲突。

下面给出我的.babelrc配置

1{
2    "presets": [ [ "env", { 
3                    "modules"false } 
4                ] ], 
5    "plugins": ["transform-runtime"]   // babel-plugin-transform-runtime 在这里使用,可以编译Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象等新的API
6}

再来看看plugins配置。babel中的插件,通过配置不同的插件才能告诉babel,我们的代码中有哪些是需要转译的,比如转译箭头函数、class语法、for-of等等,可以对单一转译需求进行个性化定制,从而减少最后打包时文件体积。当然,我是不喜欢这样做的,一般WEB开发也不会需要用到这么极端,推荐babel+babel-polyfill一口气把所有能转译的ES6全支持。一般的建议是开发一些框架或者库的时候使用不会污染全局作用域的babel-runtime,而开发web应用的时候可以全局引入babel-polyfill避免一些不必要的错误,而且大型web应用中全局引入babel-polyfill可能还会减少你打包后的文件体积(相比起各个模块引入重复的polyfill来说)。

唔,写到这里,看下最后的转译JS代码:

 1//es6模块规范
2import _Object$assign from 'babel-runtime/core-js/object/assign';
3import _Promise from 'babel-runtime/core-js/promise';
4// Promise 对象
5function test(x{
6    return new _Promise(function (resolve, reject{
7        if (x > 10) {
8            resolve(x);
9        } else {
10            reject('x小于10');
11        }
12    });
13}
14var result = test(13);
15result.then(function (x{
16    console.log(x);
17}).catch(function (err{
18    console.log(err);
19});
20
21//Object.assign() 方法
22var object1 = {
23    a1,
24    b2,
25    c3
26};
27var object2 = _Object$assign({ c4d5 }, object1);
28console.log(object2.c, object2.d);
29
30// ES6 字符串语法
31var str1 = '12345';
32var str = '\u53E3\u4EE4aa' + str1;
33console.log(str);

开启服务器模式后,打开浏览器页面,结果报这个错:

这个问题暂时没有解决,因为考虑到其实在打包后需要把相关模块也打包到dist文件里去,再考虑到报错中的路径引用问题,使用gulp暂时无法解决,和webpack相比,确实gulp属于上一代的打包工具明显功能欠缺。或者有更好的解决方案,但时间关系就不去找了。

不过在项目中使用上Promise对象的初衷还是要实现的,就换使用流行的Promise库吧,也就是q.js,毕竟先有的这个库,再有的ES6中的Promise语法,而且两者的代码实践居然一模一样,让我有点怀疑两者之间的关系。但不管怎么样,Promise对象可用的目标是实现了。

下面给出q.js实现的promise方案:

 1var imgsrc = "http://jspang.com/static/upload/20181111/G-wj-ZQuocWlYOHM6MT2Hbh5.jpg";
2function loadImg(src){
3    return new Q.promise(function(resolve, reject){
4        var img = document.createElement('img');
5        img.onload = function(){
6            resolve(img)
7        }
8        img.onerror = function(){
9            reject("图片加载失败")
10        }
11        img.src = src;
12    })
13}
14var promise = loadImg(imgsrc);
15promise
16    .then(function(img){
17        document.body.appendChild(img);
18    })
19    .then(function(){
20        console.log("图片加载成功");
21    })
22    .catch(function(err){
23        console.log(err);
24    })

套路和ES6的Promise是一样一样的,并且这个库兼容到IE9及以上,也是很不错的。就酱,我把它加入到现有项目JS模块体系中去。

这次对于babel进行系统化的研究,基本上里外也都摸了个遍,倒是对我在webpack构建中的babel理解帮助很大,算没白忙活。

以上是关于babel实践:真实gulp项目支持ES6转译ES5的跳坑指北的主要内容,如果未能解决你的问题,请参考以下文章

ES6转ES5:Gulp+Babel

第879期使用 Babel 和 Gulp 搭建 ES6 开发环境

javascript 使用Babel,Browserify和Gulp的新ES6项目

es6转es5:gulp+babel

ES6转换器之Babel

从 Babel 转译过程浅谈 ES6 实现继承的原理