解决JS兼容问题以及初步认识babel(JS工具链的宏观认识)


前言

在前端开发过程中,我们可能会遇到一些js的兼容性问题,例如:语法兼容api兼容。同时我们可能想做一些语言增强,例如使用:jsxts。大多数情况下此类问题上升不到普通开发场景下,因为脚手架可能已经解决了相关问题,本文主要了解解决此类问题原理和宏观思想。


一、解决兼容性问题

我们平时可能会使用新发布的api或语法,有可能在一些老旧环境下不受支持,该如何优雅解决呢?

1.api兼容性问题

  1. 使用nvm降低node版本,模拟老旧环境。

    控制台执行:

    nvm use 9.11.2
    
  2. 使用ES6的apiflatMap处理数组

    创建index.js写入如下代码:

    let arr = [1,2].flatMap(x => [x, x * 2]);
    

    执行结果:TypeError: [1,2].flatMap is not a function

  3. api兼容的原理认知
    我们将使用三方库来兼容当前环境,本质上其实就是在Array.prototype上添加一个名为flatMap的方法。

    添加代码:

    Array.prototype.flatMap = function(callback) {
     //...
    };  
    let arr = [1,2].flatMap(x => [x, x * 2]);
    

    类似于这种做法或思想被称之为polyfill,有道的翻译结果:“一种用于衣物、床具等的聚酯填充材料,使这些物品更加温暖舒适”。我觉得可以简单理解为当前环境下没有这个api,我们通过一些手段将其填充(polyfill)了进去。

  4. 兼容环境配置
    认识了大致原理后,接下来使用core-js实现polyfill

    控制台执行:

    npm install core-js
    

    修改代码:

    require("core-js/modules/es.array.flat-map");
    let arr = [1,2].flatMap(x => [x, x * 2]);
    console.log(arr)//[ 1, 2, 2, 4 ]
    

    结合上下文我们就能认识到其实core-js做了什么事,本质上来说就是将所有的api在其内部实现,我们在编码过程中直接引入对应的api,进而不借助当前运行环境提供的api,从而解决api兼容问题。

2.语法兼容性问题

语法兼容的问题相比api兼容更为复杂一些,其核心思想就是语法转换,详细说来,就是在保证运行结果不变的前提下,将当前运行环境不支持的语法用当前环境支持的语法去替换,下面对async-await语法进行转换。

  1. 安装语法转换库

    控制台执行:

    npm install regenerator
    
  2. 准备待转换代码

    创建index.js写入:

    async function func(){
    	await Promise.resolve();
    }
    
  3. 语法转换

    将刚才的代码以字符串形式转入regenerator.compile方法中进行转换。

    创建compoler.js写入:

    let regenerator = require("regenerator");
    //将待转换的代码以字符串形式转入regenerator.compile方法
    let result = regenerator.compile(`
        async function func(){
            await Promise.resolve();
        }`,
        {
            includeRuntime: true,
        }
     )
    console.log(result.code)//转换后的代码
    
  4. 写一个自动转换工具

    我们已经了解到了语法转换的基本原理,接下来开发一个自动转换语法的小工具吧。

    修改compoler.js:

    let regenerator = require("regenerator");
    let fs = require("fs");
    let path = require("path");
    //读取待转换代码
    let source = fs.readFileSync(path.join(__dirname, "./index.js"), "utf-8");
    //使用regenerator.compile进行转换
    let result = regenerator.compile(source,
        {
            includeRuntime: true,
        }
    )
    //将结果写入target.js
    fs.writeFileSync(path.join(__dirname, "./target.js"), result.code)
    

    执行后就会在当前目录自动创建target.js:在这里插入图片描述

二、初识babel

上面我们解决的两种兼容问题,究其根本实质都是在做代码转换,但在实际开发中,如果每转化一次就进行一次上面的操作,这种做法效率太过低下。有没有能整合上面那些操作的工具(代码集成转换工具)呢?它就是babel

1.安装babel

控制台执行:

npm install -D @babel/core @babel/cli

@babel/core主要进行代码转换@babel/cli主要是使用户可以通过命令行方式去调用@babel/core执行转换

2.准备待转换代码

创建index.js写入:

let family = {
    grandfather: {
        father: {
            son1: {
                name:"a"
            },
            son2: {
                name:"b"
            }
        }
    },
}
console.log(family?.grandfather?.father?.son1?.name)
console.log(family?.grandfather?.father?.son2?.name)
console.log(family?.grandfather?.father?.son3?.name)

3.配置控制台命令并转换

修改当前目录下的package.json:

...
"scripts": {
    "compile":"babel ./index.js -o ./target.js"
},
 ...

控制台执行:

npm run compile

此时再看看当前文件目录,已经多出来target.js,你会发现代码好像只是被复制了一遍,并无变化,不过我们确实进行了转换,原因在于我们在转换过程中没有告诉babel要具体使用什么转换插件。

4.加入转换插件

安装一个专门转换链式调用的插件,控制台执行:

npm install -D @babel/plugin-transform-optional-chaining

创建babel配置文件babel.config.js写入:

module.exports = {
    plugins:[
        "@babel/plugin-transform-optional-chaining"
    ]
}

5.再次转换

控制台执行:

npm run compile

再次看看target.js:

var _family$grandfather, _family$grandfather2, _family$grandfather3;
let family = {
  grandfather: {
    father: {
      son1: {
        name: "a"
      },
      son2: {
        name: "b"
      }
    }
  }
};
console.log(family === null || family === void 0 || (_family$grandfather = family.grandfather) === null || _family$grandfather === void 0 || (_family$grandfather = _family$grandfather.father) === null || _family$grandfather === void 0 || (_family$grandfather = _family$grandfather.son1) === null || _family$grandfather === void 0 ? void 0 : _family$grandfather.name);
console.log(family === null || family === void 0 || (_family$grandfather2 = family.grandfather) === null || _family$grandfather2 === void 0 || (_family$grandfather2 = _family$grandfather2.father) === null || _family$grandfather2 === void 0 || (_family$grandfather2 = _family$grandfather2.son2) === null || _family$grandfather2 === void 0 ? void 0 : _family$grandfather2.name);
console.log(family === null || family === void 0 || (_family$grandfather3 = family.grandfather) === null || _family$grandfather3 === void 0 || (_family$grandfather3 = _family$grandfather3.father) === null || _family$grandfather3 === void 0 || (_family$grandfather3 = _family$grandfather3.son3) === null || _family$grandfather3 === void 0 ? void 0 : _family$grandfather3.name);

6.使用预设

经过上面的配置,我们使用babel转换了链式运算符,如果需要转换更多语法呢,如此一个个操作仍然繁杂,因此我们使用babel的预设,预设可以简单理解为一堆插件的集合,开箱即用。
常见的babel预设:typescript预设、jsx预设、基础预设等。

  1. 安装基础预设

    cmd执行:

    npm install -D @babel/preset-env
    
  2. 修改babel.config.js

    module.exports = {
        presets:[//配置预设
            ["@babel/preset-env",{
                targets:{//需要兼容的浏览器版本
                    edge:"17",
                    chrome:"67"
                },
                useBuiltIns:"usage",//按需导入
                corejs:"3.34.0"//安装的corejs的版本
            }]
        ]
    }
    
  3. 更新代码

    修改index.js:

    async function func(){
        let family = {
            grandfather: {
                father: {
                    son1: {
                        name:"a"
                    },
                    son2: {
                        name:"b"
                    }
                }
            },
        }
        return Promise.resolve(family);
    }
    func().then(res=>{
        console.log(res?.grandfather?.father?.son1?.name)
    })
    

    3.代码转换

    控制台执行:

    npm run compile
    

    再看看target.js:

    "use strict";
    
    require("core-js/modules/es.promise.js");
    async function func() {
      let family = {
        grandfather: {
          father: {
            son1: {
              name: "a"
            },
            son2: {
              name: "b"
            }
          }
        }
      };
      return Promise.resolve(family);
    }
    func().then(res => {
      var _res$grandfather;
      console.log(res === null || res === void 0 || (_res$grandfather = res.grandfather) === null || _res$grandfather === void 0 || (_res$grandfather = _res$grandfather.father) === null || _res$grandfather === void 0 || (_res$grandfather = _res$grandfather.son1) === null || _res$grandfather === void 0 ? void 0 : _res$grandfather.name);
    });
    
    

总结

感谢你读到这里

  • 32
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子禾丶

请作者吃个喔喔奶糖

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值