最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【JS】简简单单的一次babel大扫盲!

    正文概述 掘金(永不秃头)   2021-04-05   749

    祖传开头

    这篇文章算是一次关于babel的一次扫盲。整理出一份babel的基础概念扫盲,写给自己,也写需要这种基础梳理的小伙伴吧。希望大家都不要因为非常基础的问题,比如babel版本之类的小问题绊住了手脚哈哈哈!另外babel也是一个非常好的学习工具,可以把es6的代码转化成es5,抛开各种语法糖,探究js的本质。总而言之,希望这篇文章能对各位有所助益吧~

    一、Babel基本概念

    特点:

    • 静态分析,在不需要执行代码的情况下,对代码分析处理。
    • 通过组合各种模块,生一个能实现特定功能的集合。
    • @babel/core 包含了所有babel的 核心功能,使用babel前必须先引入。

    ?一个简单的例子


    第一步:在 .babelrc 文件中进行babel的配置。

    {
        "presets": [
            [
                "@babel/preset-env"
            ]
        ]
    }
    

    **第二步:安装 @babel/core 、 @babel/cli 和 ****@babel/preset-env** 
    @babel/core是babel的和核心库。@babel/cli是babel能在终端环境中可以运行的工具库。

    第三步:创建源代码文件和babel运行命令。

    src/index.js

    const newArys = [1, 2, 3, 4, 5].map((n) => n * n);
    


    package.json

    "scripts": {
        "compiler": "babel src --out-dir lib",
        "watch": "npm run compiler -- --watch"
     }
    

    命令 compiler 的作用就是将 src 目录下所有的JavaScript文件都进行解析,最后输出到 lib 目录下。
    命令 watch 的作用就是监听 src 目录下是否有进行改动,若有就重新进行编译。

    **
    更多babel-cli命令
    以下列举了几个比较常见的配置命令。

    说明使用
    --out-file/-o指定输出文件babel src --out-file index.js--out-dir/-o指定输出目录babel src --out-dir lib--watch/-w监听文件改动并且进行重新编译babel src --out-dir lib --watch--source-maps/-s生成映射代码babel src --out-file script-compiled.js --source-maps--ignore指定不需要转换的文件babel src --out-dir lib ---ignore "src/**/*.spec.js",

    www.babeljs.cn/docs/babel-…

    二、Babel原理

    三个步骤

    babel的处理步骤主要分为以下步骤:

    第一步:解析(parse)

    将原代码转换成AST树,也就是抽象语法树。这个步骤分为两个阶段:

    • 词法分析
    • 语法分析


    词法分析 简单的来理解,就是把原代码(字符串形式)进行分割,生成一个个最小单位——令牌(token),方便后续的组装。
    一个简单的表达式:

    a * b
    

    经过词法分析后,以上表达式分割成3个部分。变成一组 令牌流(tokens) 。形式如下:

    [
      { type: { ... }, value: "a", start: 0, end: 1, loc: { ... } },
      { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
      { type: { ... }, value: "b", start: 4, end: 5, loc: { ... } },
      ...
    ]
    

    词法分析 ,就是把每个token的关系描述清楚,把有关系的token组合在一起。比如上述三个token组合起来就是个获取乘积的表达式。经过词法分析后,就会生成AST树。

    ?先来看一个具体的AST树:(通过AST Explorer模拟AST树的生成)
    源码:

    function add(a, b) {
      return a + b
    }
    
    {
      "type": "Program",
      "start": 0,
      "end": 37,
      "body": [
        {
          "type": "FunctionDeclaration",
          "start": 0,
          "end": 37,
          "id": {
            "type": "Identifier",
            "start": 9,
            "end": 12,
            "name": "add"
          },
          "expression": false,
          "generator": false,
          "async": false,
          "params": [
            {
              "type": "Identifier",
              "start": 13,
              "end": 14,
              "name": "a"
            },
            {
              "type": "Identifier",
              "start": 16,
              "end": 17,
              "name": "b"
            }
          ],
          "body": {
            "type": "BlockStatement",
            "start": 19,
            "end": 37,
            "body": [
              {
                "type": "ReturnStatement",
                "start": 23,
                "end": 35,
                "argument": {
                  "type": "BinaryExpression",
                  "start": 30,
                  "end": 35,
                  "left": {
                    "type": "Identifier",
                    "start": 30,
                    "end": 31,
                    "name": "a"
                  },
                  "operator": "+",
                  "right": {
                    "type": "Identifier",
                    "start": 34,
                    "end": 35,
                    "name": "b"
                  }
                }
              }
            ]
          }
        }
      ],
      "sourceType": "module"
    }
    

    AST树的每一层结构都比较相似,每一层树都会由一个个节点(Node)构成。每个节点的type都会对这个节点的类型进行说明:

    • Program :一个程序的根节点类型
    • FunctionDeclaration :函数声明
    • Identifier :变量声明
    • BinaryExpression :表达式
    • ...
    {
       type:"",
       id:"",
       params:{},
       body:{}
    }
    

    第二步:转换(transform)

    这个步骤就是接收到第一步生成的AST树后进行遍历,对AST的节点进行更新、添加、删除等操作。也是整个编译过程中最复杂的部分,也是babel插件介入工作并完成一些特定功能的过程。

    babel插件原理:

    • 创建一个访问者,这个访问者对象会提供具体的获取树节点信息的方法
    • 实际访问的是一个路径

    **
    ?看一个简单的babel插件例子

    // babel-plugins-mm
    module.exports = function () {
        return {
            visitor:{
                Identifier: {
                    enter(path) {
                        console.log("Entered!"+ path.node.name);
                    },
                    exit(path) {
                        console.log("Exited!"+ path.node.name);
                    }
                }
            }
        };
    }
    

    使用插件:(只写插件名称,默认会从node-modules里查找)
    mm 是 babel-plugins-mm 的简写。

    {
        "presets": [
            ["@babel/preset-env"]
        ],
        "plugins": [
            ["mm"]
        ]
    }
    

    第三步:生成(generate)

    最后一步就是就是将处理过后的AST再次转化后字符串形式的代码。(深度优先的遍历AST树,并生成字符串形式的代码。)

    三、Babel配置

    {
      // 预设
      "presets":[...],
      // 插件
      "plugins":[...],
      // 环境配置
      "env": {
         "development": {
           "plugins": [...]
         },
         "production": {
           "plugins": [...]
         }
        }
    }
    

    4、Babel预设


    目前官方的推荐的一些预设:

    @babel/preset-env
    @babel/preset-flow
    @babel/preset-react
    @babel/preset-typescript
    ...
    


    注意:babel @7.0.0之后对标准阶段提案的一些预设都会废弃,比如以下预设不推荐使用了。

    @babel/preset-stage-0
    @babel/preset-stage-1
    @babel/preset-stage-2
    @babel/preset-stage-3
    

    @babel/preset-env

    对ES2015和ES2016进行转换,并且处理polyfill。

    preset-env有以下配置,具体可以访问官网查看。

    targets
    spec
    loose
    modules
    debug
    include
    exclude
    useBuiltIns
    corejs
    forceAllTransforms
    configPath
    ignoreBrowserslistConfig
    shippedProposals
    

    tagets

    指定适配游览器范围,如果设置了该参数,优先级会比.browserslist 或者 package.json/browserslist设置的高。如果不设置,则默认会获取 .browserslist 或者 package.json/browserslist指定的游览器适配范围。
    但是在项目中,还是建议使用 .browserslistrc  或者 package.json/browserslist指定目标。

    useBuiltIns

    "usage" | "entry" | false, defaults to false.
    这个配置参数主要的作用是明确如何处理polyfill。

    **polyfill
    先简单的了解一下 polyfill ,中文成为垫片,形象一点描述用一个个垫片铺平低端游览器的坑,使代码运行的和在高端游览器中一样。对ES2015+的一些语法进行转化适配(除了一些实验性的Js特性),比如一些新的内置对象和实例方法等(Promise、Array.from、Object.assign...)

    @babel/polyfill 其实就是由 core-js/stable  和 regenerator-runtime/runtime  组合而成。

    • core-js:@babel/polyfill的底层依赖库,为ES2015+的JS特性提供编译转化功能。
    • **regenerator-runtime : **专门为 Generator 函数提供编译转化功能,比如在项目中使用了async/await就需要引入这个插件。


    以下两种写法其实是一样,而且只能两种方式取一种,否则会报重复引入的报错。

    import "@babel/polyfill";
    // ====等同于====
    import "core-js/stable";
    import "regenerator-runtime/runtime";
    

    但是由于polyfill的体积比较大,通常情况下我们不需要引入完成的polyfill,而且全部引入也会导致打包后体积变大。所以此处useBuiltIns就开始发挥作用。

    useBuiltIns:usage
    不需要在文件入口处引入polyfill,env会提供一个插件,根据每个文件的需求进行特殊引入垫片。

    此时需要你在babel配置中指定corejs的版本的版本。

    {
        "presets": [
            [
                "@babel/preset-env",
                {
                    "useBuiltIns": "usage",
                    "corejs": 3
                }
            ]
        ]
    }
    

    比如promise-a.js文件只使用到promise,就只会在该文件入口引入promise相关的垫片。
    src/promise-a.js

    // src/promise-a.js
    new Promise((resolve)=>{
        console.log("promise A");
        resolve()
    })
    

    lib/promise-a.js

    "use strict";
    
    require("core-js/modules/es.object.to-string");
    
    require("core-js/modules/es.promise");
    
    new Promise(function (resolve) {
      console.log("promise A");
      resolve();
    });
    

    useBuiltIns: 'entry'
    需要在文件入口处手动引入polyfill,然后根据当前的环境尽可能的引入需要的功能。
    比如引入一个array相关的垫片。
    src/entry.js

    import "core-js/es/array";
    

    lib/entry.js

    "use strict";
    require("core-js/modules/es.array.concat");
    require("core-js/modules/es.array.copy-within");
    require("core-js/modules/es.array.every");
    require("core-js/modules/es.array.fill");
    require("core-js/modules/es.array.filter");
    require("core-js/modules/es.array.find");
    require("core-js/modules/es.array.find-index");
    require("core-js/modules/es.array.flat");
    require("core-js/modules/es.array.flat-map");
    ...
    


    useBuiltIns: false
    不为文件自动添加垫片,也不会对于自行引入的core-js或者@babel/polyfill有任何转化。

    corejs

    23 or { version: 2 | 3, proposals: boolean }, defaults to 2.

    这个选项需要和useBuiltIns结合起来使用。当useBuiltIns: usage或者useBuiltIns: entry时才会起效。
    core-js目前存在两个版本,分别是coreJs2和coreJs3。coreJs2目前已经不再更新,所以对于一些特别新的特性只会在coreJs3去更新。

    如果使用coreJs3时只想注入稳定的ECMAScript功能,有以下两种方式:
    1.对于useBuiltIns: usage 
    corejs: { version: 3, proposals: true }<br />
    2.对于useBuiltIns: entry
    corejs配置为 3 ,另外在项目引入import "core-js/proposals/string-replace-all"

    ✍其他小知识点:


    1.插件名称简写

    @babel/plugin-XXX 等同于 @babel/XXX
    

    2.babel命名
    @7.0.0以上的包都是以@开头命名,用于区分6.x版本

    3.执行顺序

    • 插件在 Presets 前运行。
    • 插件顺序从前往后排列。
    • Preset 顺序是颠倒的(从后往前)。


    参考文档
    Babel用户手册


    起源地下载网 » 【JS】简简单单的一次babel大扫盲!

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元