最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Babel 的理解

    正文概述 掘金(掘金小朋友)   2020-12-04   654

    目录

    • 前言
      • babel 是什么
      • babel 能做什么
    • 工作流程
      • 解析 Parse
      • 转换 Transform
      • 生成 Generator
    • 使用方式
      • babel-standalone
        • 介绍
        • 版本
        • 示例
        • 问答
        • 补充
      • cli
    • 模块介绍
    • 版本升级
    • 问答
    • 总结

    前言

    babel 是什么

    Babel 是一个 JavaScript 编译器

    这是babel 官网对 babel 一句短而精的定义, 该如何理解这句话的含义呢?首先定义它为一个编译器,其次只对 JavaScript 语言负责.

    关于编译器概念可参考维基百科bk.tw.lvfukeji.com/wiki/%E7%BC…

    babel 能做什么

    这里我们只需要知道 babel 作为 JavaScript 编译器的工作流程:解析->转换->生成.

    通俗的可以理解为 babel 负责把 JavaScript 高级语法、新增 API 转换为低规范以保障能够在低版本的环境下正常执行,接下来我们看 babel 是如何工作的.

    工作流程

    Babel 的理解

    解析 Parse

    万事开头难,第一步的解析工作该由谁完成?

    babylon

    Babylon 是一款 JavaScript 解析器.

    babel 本身是不负责解析工作的,而是调用了 babylon.parse 方法对源码进行词法解析生成 AST 树.

    Babel 的理解

    转换 Transform

    babel-traverse

    babel-traverse 负责遍历 AST 树进行增删改的操作.

    从第一步获取到 AST 树之后,调用 babel-traverse 库提供的 traverse 方法对树进行更新.

    babel-types

    一个基于 AST 树的工具库(可对节点增删改查校验).babel-traverse 对 AST 树进行操作的时候便使用了此库.

    Babel 的理解

    生成 Generator

    最后一步将更新之后的 AST 树进行生成代码.

    babel-generator

    对外提供 generator 方法接收 ast 参数返回值为改变之后的源码.

    Babel 的理解

    Babel 的理解

    以上则是对 babel 编译器整个流程大概的描述信息.所以 babel 是由一系列动作配合完成的.

    使用方式

    babel-standalone

    介绍

    由于 babel 是基于 node 环境下运行,对于非 node 环境(如浏览器),babel-standalone这个开源项目提供了 babel.min.js 可通过<script>方式引入使用.

    题外话:babel-standalone已加入了babel大家族(上岸成为有编制一员),以后下载的 7.x 版本 babel 包内可以看到它的身影.

    版本
    名称版本体积备注在线地址
    babel.js6.26.01.78MB未压缩unpkg.com/babel-stand…babel.min.js6.26.0773KB已压缩unpkg.com/babel-stand…babel.js7.12.93.1MB未压缩unpkg.com/@babel/stan…babel.min.js7.12.91.6MB已压缩unpkg.com/@babel/stan…
    示例
    • 示例一 es6 转码
      • 使用<script>引入在线地址或者下载之后本地引用.
      • 将编写的 es6 代码放入<script type="text/babel">内,需要注意一点type 类型为 text/babel.
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone es6 转码</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script type="text/babel">
          const getMsg = () => {
            const name = 'Babel';
            document.getElementById(
              'output'
            ).innerHTML = `Hello ${name} version:${Babel.version}`;
          };
          getMsg();
        </script>
      </body>
    </html>
    

    Babel 的理解

    • 示例二 模拟在线实时转码用户输入的脚本

      • 这种方式适用于一些在线转码的场景:

        • babel 官网首页

          Babel 的理解

        • babel-repl 在线转码

        • JSFiddle

        • JSBin

    以上这些均引入babel.min.js(可能引入的名称或版本不一样)通过调用Babel对象提供的各种 API(如transformdisableScriptTagstransformScriptTags...)实现在线实时转码.

    Babel 的理解

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone 模拟在线实时转码用户输入的脚本</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        输入:
        <textarea id="input" style="width: 100%" rows="15">
        class UserInfo{
          constructor(name='张三') {
            this.name = name;
          }
          getUserName(){
          return `${this.name}`;
          }
        }
        const user=new UserInfo('张三');
        console.log(user.getUserName());
        </textarea>
        实时转码:
        <pre id="output"></pre>
    
        <script>
          var inputEl = document.getElementById('input');
          var outputEl = document.getElementById('output');
    
          function transform() {
            try {
              outputEl.innerHTML = Babel.transform(inputEl.value, {
                presets: [
                  'es2015',
                  [
                    'stage-2',
                    {
                      decoratorsBeforeExport: false,
                    },
                  ],
                ],
              }).code;
            } catch (e) {
              outputEl.innerHTML = 'ERROR: ' + e.message;
            }
          }
    
          inputEl.addEventListener('keyup', transform, false);
          transform();
        </script>
      </body>
    </html>
    

    Babel 的理解

    • 示例三 import、export 的使用

    以上示例都是通过内嵌的方式在页面直接写 es6 代码,但实际开发中有可能需要以外链的方式引入脚本,所以我们看会遇到哪些问题.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script type="text/babel" src="./index.js"></script>
      </body>
    </html>
    
    // index.js
    const getMsg = () => {
      const name = 'Babel';
      document.getElementById(
        'output'
      ).innerHTML = `Hello ${name} version:${Babel.version}`;
    };
    getMsg();
    

    Babel 的理解

    把示例一从内嵌方式修改为外链引入脚本证明没问题.但我们不止编写一个 index.js 脚本,对于使用 import、export 这些 es6 语法是否也支持?

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script type="text/babel">
          export default {
            name: '小朋友',
            age: 18,
          };
        </script>
      </body>
    </html>
    

    首先我们以内嵌方式运行之后抛错Uncaught ReferenceError: exports is not defined,这里抛出exports未定义而不是 es6 的export未定义,由此说明内部是要把 es6 代码转换 CommonJS 模块规范(印证了前面介绍中提到过的由于 babel 是基于 node 环境下运行),而我们是需要在浏览器执行的,所以babel-standalone项目提供的babel.min.js包含了 babel 用到的所有插件(各种特性、规范),可以直接配置为 umd 模式即可.

    6.x 版本配置data-plugins="transform-es2015-modules-umd"

    7.x 版本配置data-plugins="transform-modules-umd"

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script type="text/babel" data-plugins="transform-modules-umd">
          export default {
            name: '小朋友',
            age: 18,
          };
        </script>
      </body>
    </html>
    

    既然可以定义export导出那该如何import导入呢?这里babel-standalone又给我们提供了data-module定义导出的模块名称,然后导入即可.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          data-module="userInfo"
        >
          export default {
            name: '小朋友',
            age: 18,
          };
        </script>
        <script type="text/babel" data-plugins="transform-modules-umd">
          import userInfo from 'userInfo';
          document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
        </script>
      </body>
    </html>
    

    Babel 的理解

    这一些似乎都没发现问题,难道真的没有什么问题?那直接修改为外链的方式呢.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./userInfo.js"
        ></script>
    
        <script type="text/babel" data-plugins="transform-modules-umd">
          import userInfo from 'userInfo';
          document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
        </script>
      </body>
    </html>
    
    // userInfo.js
    export default {
      name: '小朋友',
      age: 18,
    };
    

    如果仔细看上面这段代码的话,有没有发现script标签内缺少了属性data-module定义模块名称,依然可以正常执行呢?

    划重点,babel.min.js内部获取script标签之后对属性做了判断,如果有src属性则使用属性值作为模块名称(如src="./userInfo.js"最终以userInfo作为模块名称),如果没有src属性则获取data-module属性值作为模块名称,所以如果使用外链方式的话是不需要data-module属性的(即使配置了也无效).

    上面示例演示了 export 可以使用外链的方式,那import又如何使用外链方式呢?

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./userInfo.js"
        ></script>
    
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./index.js"
        ></script>
      </body>
    </html>
    
    // userInfo.js
    export default {
      name: '小朋友',
      age: 18,
    };
    
    // index.js
    import userInfo from 'userInfo';
    document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
    

    import 的使用方式同export的方式是一样的,这样就完成了以外链的方式引入脚本.

    问答

    说这个问题之前,大家是否考虑过如果没有外链引入<script src="./userInfo.js">,只有<script src="./index.js">会怎样?在index.js内部已经通过import userInfo from 'userInfo';导入,是否可以不需要外链的方式引入,如果import导入多个 js 又会怎样?

    稳住.所以拎到问答部分单独聊...

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./index.js"
        ></script>
      </body>
    </html>
    
    // index.js
    import userInfo from 'userInfo';
    document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
    

    如上面的示例的,直接外链引入index.js,抛出Uncaught TypeError: Cannot read property 'name' of undefined错误,原因是userInfoundefined所以userInfo.name也无法取到值.

    那我们就从转码开始入手.

    // index.js 转码之后
    (function (global, factory) {
      if (typeof define === 'function' && define.amd) {
        define(['userInfo'], factory);
      } else if (typeof exports !== 'undefined') {
        factory(require('userInfo'));
      } else {
        var mod = {
          exports: {},
        };
        factory(global.userInfo);
        global.index = mod.exports;
      }
    })(
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
        ? self
        : this,
      function (_userInfo) {
        'use strict';
    
        _userInfo = _interopRequireDefault(_userInfo);
    
        function _interopRequireDefault(obj) {
          return obj && obj.__esModule ? obj : { default: obj };
        }
    
        document.getElementById('output').innerHTML = 'Hello '.concat(
          _userInfo['default'].name
        );
      }
    );
    

    我们对比一下index.js转码前后:

    转码前转码后
    import userInfo from 'userInfo'; _userInfo = _interopRequireDefault(_userInfo);
    function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}

    我们看到转码之后的代码通过_interopRequireDefault(_userInfo)函数对_userInfo重新赋值的操作.(_interopRequireDefault函数的作用判断对象是否需要添加default属性).

    划重点:这里插一段为什么会有__esModuledefault属性,首先在本节开头提到过由于 babel 是基于 node 环境下运行,所以 babel 是要把 es6 的模块转换为 CommonJS 的形式,那么就需要了解两者的区别.

    导出导入
    es6export
    export default
    import {}
    import
    CommonJSmodule.exportsrequire

    通过对比发现 es6 的导出、导入可以是多种形式,而 CommonJS 则是单一的对象导出、导入.所以 babel 要把 es6 转换 CommonJS 的形式就需要一些辅助改动.

    关于模块对象添加__esModule属性,是为了标记此模块是否被转码,如果有此属性则直接调用模块(exports)的default属性导出对象(babel 会把 es6 的export default默认导出对象转码为 exports.default的形式,同时这种写法又符合 CommonJS 的规范module.exports = exports.default),主要是做到 es6 转码后与 CommonJS 规范的一致性.

    关于default属性,上面介绍了是有__esModule属性的情况下,如果没有__esModule属性说明没有对该模块进行转换(有可能是一个第三方模块)对于这种情况直接调用模块(exports)的default属性会为undefined,所以这种情况就直接返回一个对象并且该对象添加一个default属性,把属性值指向自己(如上面这句转码之后的代码return obj && obj.__esModule ? obj : { default: obj })

    快醒醒,这两个属性不是这里的重点,还记得问题是什么吗?userInfo为什么是undefined,看完转码之后的代码,我们只需要知道一点import只是导入而已,至于导入的对象是否存在,是不属于转码的职责所在(转码不会检测导入的对象否存在...),还是继续查找userInfo在哪里定义的吧...

    继续看转码之后的代码发现在开头有if...else if...else对各种环境(AMD、CommonJS、UMD)做判断,由于我们是浏览器内执行(UMD 模式),所以进入else里在global.userInfo全局对象上有个userInfo,至此得出一个结论,在外链方式引入的脚本内直接使用import导入对象引用,而外部无任何声明export,此对象是undefined(其实在 AMD 的define(['userInfo'], factory)、CommonJS 的require('userInfo')也是同理).

    简单一句话:无论通过哪种形式引用,必须要声明,所以外链的方式还是需要script声明.

    简单一句话:无论通过哪种形式引用,必须要声明,所以外链的方式还是需要script声明.

    简单一句话:无论通过哪种形式引用,必须要声明,所以外链的方式还是需要script声明.

    此处使用的声明可能用词不太准确,但相信看到这里应该可以意会到.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone import、export 的使用</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./userInfo.js"
        ></script>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./index.js"
        ></script>
      </body>
    </html>
    
    // userInfo.js
    export default {
      name: '小朋友',
      age: 18,
    };
    
    // index.js
    import userInfo from 'userInfo';
    document.getElementById('output').innerHTML = `Hello ${userInfo.name}`;
    
    // userInfo.js 转码后
    (function (global, factory) {
      if (typeof define === 'function' && define.amd) {
        define(['exports'], factory);
      } else if (typeof exports !== 'undefined') {
        factory(exports);
      } else {
        var mod = {
          exports: {},
        };
        factory(mod.exports);
        global.userInfo = mod.exports;
      }
    })(
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
        ? self
        : this,
      function (_exports) {
        'use strict';
    
        Object.defineProperty(_exports, '__esModule', {
          value: true,
        });
        _exports['default'] = void 0;
        var _default = {
          name: '小朋友',
          age: 18,
        };
        _exports['default'] = _default;
      }
    );
    
    // index.js 转码之后
    (function (global, factory) {
      if (typeof define === 'function' && define.amd) {
        define(['userInfo'], factory);
      } else if (typeof exports !== 'undefined') {
        factory(require('userInfo'));
      } else {
        var mod = {
          exports: {},
        };
        factory(global.userInfo);
        global.index = mod.exports;
      }
    })(
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
        ? self
        : this,
      function (_userInfo) {
        'use strict';
    
        _userInfo = _interopRequireDefault(_userInfo);
    
        function _interopRequireDefault(obj) {
          return obj && obj.__esModule ? obj : { default: obj };
        }
    
        document.getElementById('output').innerHTML = 'Hello '.concat(
          _userInfo['default'].name
        );
      }
    );
    

    对于这个问题,请品一下,上面对__esModuledefault属性的介绍,就是那段这两个属性不是这里的重点,现在是这个问题的答案.

    其实和上面的示例是一样的,只需要外链多个script即可.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>babel-standalone 如何使用多个import、export</title>
        <script src="https://unpkg.com/@babel/standalone@7.12.9/babel.min.js"></script>
      </head>
      <body>
        <div id="output"></div>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./other.js"
        ></script>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./userInfo.js"
        ></script>
        <script
          type="text/babel"
          data-plugins="transform-modules-umd"
          src="./index.js"
        ></script>
      </body>
    </html>
    
    // other.js
    export function random() {
      return Math.floor(Math.random() * 10);
    }
    
    export const randomStr = '幸运数字:';
    
    // userInfo.js
    export default {
      name: '小朋友',
      age: 18,
    };
    
    // index.js
    import { randomStr, random } from 'other';
    import userInfo from 'userInfo';
    function init() {
      document.getElementById('output').innerHTML = `Hello ${
        userInfo.name
      } ${randomStr} ${random()}`;
    }
    
    init();
    
    补充

    对于开发者来说这种直接编写 es6 代码实时转码比较方便,同时也耗时影响性能,所以开发环境可以使用,对于生产环境还是推荐加载转码之后的脚本.

    You are using the in-browser Babel transformer. Be sure to precompile your scripts for production——官网建议.

    未完待续.


    起源地下载网 » Babel 的理解

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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