zoukankan      html  css  js  c++  java
  • Babel 7 初探

      最近在做项目的依赖的清理,看到babel 已经升级7.9了,而我对babel的认知还停留在6时代,觉得需要重新学习一下了。

      我们都知道, Babel是转译工具,它能把最新的javaScript的语法,转化成旧的js 的语法。转译的核心则在于插件,提供给babel 什么插件,babel 就会转译什么。如果我们提供了转化箭头函数的插件,babel 遇到箭头函数就会把它转化成普通函数。新建一个项目babel-learning,  在其中建一个src目录,在src 下建index.js, (mkdir babel-learning && cd babel-learning && mkdir src && cd src && touch index.js),  最好再建一个package.json 文件(cd ..  && npm init -y),管理项目依赖。使用babel 进行转译,要安装哪些依赖呢?

      @babel/cli  babel 命令集合,在命令行中直接调用babel 命令,对文件进行编译,如果使用webpack,那就不用它,要用babel-loader. 

      @babel/core, babel的核心,它负责转译js 语法,但单独使用它,却起不到转译的作用,需要你给它提供插件。也就是说,如果只安装@babel/core, 调用babel 命令进行转译,文件会原模原样地进行输出,转译后的文件和原文件一模一样。

      npm i @babel/core @babel/cli  --save-dev,这里要注意,babel 7 把babel依赖包的名称给重新写了,以前是 babel-,现在是@babel/,安装完成后,在index.js 文件中随便写一个箭头函数,

    const sum = (a, b ) => a + b;

      然后在命令行中调用babel 命令 npx babel src  --out-dir dist,npx可以直接调用node_modules 中的命令,--out-dir 表示转译后的文件输出到什么地方。也可以在package.json 的script 中写入"babel": "babel src --out-dir dist" ,再npm run babel 调用babel 命令。看一下转译后dist目录中的index.js ,没有任何变化。

      提供转译箭头函数的插件, npm i  @babel/plugin-transform-arrow-functions --save-dev, 调用babel 命令的时候,怎么让babel 使用这个插件呢?有两种方式,一种是在命令行后面加--plugins, 一种是配置文件。最好使用配置文件吧,因为添加和修改插件都比较方便。配置文件的命名也改了,原来是.babelrc, 现在官方建议是babel.config.json. touch babel.config.json

    {
        "plugins": ["@babel/plugin-transform-arrow-functions"]
    }
      npm run babel,箭头函数已经转译了,这时你再想,要不要把const 再转译一下,那就需要提供另外一下babel插件了,随着转译的语法越来越多,你要在babel 的config 文件中添加的插件也就越来越多,如果一个一个手动添加进去,那就有点麻烦了。有一个插件集合就好了,正好 babel 提供了一个@babel/preset-env。 @babel/preset-env,它是一个插件集合,所以它的作用也是转译新的js 语法到旧的js语法。那么它能转译哪些语法呢?ECMAScript 官方发布的正式版本,如ES2015 ~ES2020 中的新语法和ECMAScript proposals 中stage 4  中的新语法,这也就是下一年要发布的ECMAScript 版本中的新语法。只要官方定稿的语法,@babel/preset-env 都可以进行转译,那这就方便多了,只要安装它这一个,你就可以写一堆的新语法。插件集合称为预设(presets)。npm i @babel/preset-env  -- save-dev,相应的babel.config.json 文件就要改成
    {
        "presets": [
            "@babel/preset-env"
        ]
    }
      npm run babel, const 和箭头函数都转译了。当然有的时候,确实想用最新的语法,比如 decorator,  官方没有定稿,还在stage-2 阶段,那就单独为这个decorator安装一个插件。npm install --save-dev @babel/plugin-proposal-decorators,  然后在babel.config.json 中配置plugins,在stage <=3 阶段的语法,转译插件名称变成了proposal.
    {
        "presets": [
            "@babel/preset-env"
        ],
        "plugins": [
            "@babel/plugin-proposal-decorators"
        ]
    }
      如果安装的presets 和 plugins 越来越多,最好注意一下babel 的执行顺序。先执行plugins, 再执行presets, 如果plugins有多个,按照书写顺序从上到下(或从左到右) 依次执行每一个plugin.  但presets 确相反,如果有多个pesets, 按照书写顺序从下到上或从右到左依次执行,一般来说,顺序不会引起问题。
      说回@babel/preset-env, 除了它是一个插件集合,能够转译最新语法外,它还集成了browserlist, 能够根据指定的浏览器决定要不要编译最新的语法到旧的语法。默认情况下,如果没有指定browserlist, 它会全部进行转译,就像现在的配置一样。指定一下browserlist,  有三种方式,一个是 .browserslistrc 文件,一个是package.json,一个是@babel/preset-env options,如果使用options, presets中的每一项就要变成数组,数组的第一项是使用的preset, 第二项是该preset的参数配置
    "presets": [
            [
                "@babel/preset-env",
                {
                    "targets": {
                        "chrome": "58"
                    }
                }
            ]
        ]
      npm run babel 可以发现,任何语法都没有进行转译,因为chrome 58 已经支持const 和箭头函数了。再把IE 11 加上
    "targets": {
        "chrome": "58",
        "ie": "11"
    }
      npm run  babel, 所有的语法都重新编译到旧的语法。
      JavaScript的世界不止新的语法,还有新的对象和方法,如Promise 对象,Map 对象,这些对象和方法是无法进行编译的,把index.js 改成如下代码
    const promise = new Promise()
      npm run babel,只有const 转化成了var, promise对象没有处理,这需要用到polyfill,  就是把新的对象和方法用已经实现了的对象和方法实现。 原来实现polyfill 的方式有两种,一种是@babel/polyfill,  一种是@babel/plugin-transform-runtime 和@babel/runtime,为什么有两种呢?不同的使用场景而已,@babel/polyfill会造成全局变量的污染,所以适用于应用开发,@babel/runtime 不会造成全局变量的污染,适用于库的开发。现在的实现方式有了很大的区别。@babel/polyfill 在7.4 以后被废弃了。@babel/runtime 现在仅仅剩下helper 函数了,polyfill 的功能被移除了。
      @babel/polyfill  被废弃的原因是babel直接支持core-js, 更准确的说是支持core-js@3 三版本。 core-js@3 不仅包含了core-js@2,core-js@3 中的stable 就是对应的core-js@2,  且比它更加强大, babel 通过直接支持core-js还可以实现按需polyfill.  而babel/polyfill 正好使用的是core-js@2, 并且无法平滑地升级到core-js@3,而且使用@babel/polyfill 无法实现按需polyfill, 所以直接废弃了。babel 直接支持core-js@3 是通过@babel/preset-env 中的useBuiltIns 和 corejs 实现的。useBuiltIns有三个取值
      1, false, 默认值,表示 @babel/preset-env 不会进行polyfill.
      2,   entry:   我们在项目的最开始部分引入 core-js; 或 regenerator-runtime/runtime, 和@babel/polyfill 用法一致,babel 就会把整个的引入变成一个一个小polyfill的引入,引入哪些小polyfill 是根据browserlist 定义的浏览器目标决定的,这就是按需加载polyfill
      3,usage:  它是按文件进行polyfill,  如果一个文件中 用到了一个对象如promise 对象,而browserlist 中定义的浏览器又不支持这个对象,它就会在这个文件的顶部引入polyfill.  也是按需加载polyfill
      corejs 有2, 3等, 就是指定core-js 的版本。npm i core-js@3 --save
    "presets": [
            [
                "@babel/preset-env",
                {
                    "targets": {
                        "chrome": "58",
                        "ie": 11
                    },
                    "useBuiltIns": "entry",
                    "corejs": 3
                }
            ]
        ]

      使用entry,那就在项目的入口文件index.js 中 import 'core-js'

    import 'core-js';
    
    const promise = new Promise();

      npm run babel, 编译后的index.js引入了450个小的polyfill . 这时把ie: 11 去掉, 重新npm run babel,  index.js只引入了100多个polyfill, 确实是按需要加载。

      把entry 改成 usage, 并且把index.js 中的import 'core-js' 去掉

                {
                    "targets": {
                        "chrome": "58",
                        "ie": 11
                    },
                    "useBuiltIns": "usage",
                    "corejs": 3
                }    

      npm run build, 在编译后的index.js 中开头部分只引入了2个关于promise 的polyfill。当你再把ie:11 去掉,那就只引入了一个polyfill

    "use strict";
    
    require("core-js/modules/es.promise");
    
    const promise = new Promise();

      @babel/preset-env 中配置core-js和使用@babel/polyfill一样,会造成全局变量的污染,core-js 下面定义的都是global polyfill.  require("core-js/modules/es.promise"); 最终的结果是一个globle.Promise对象的存在,在浏览器中就是window.Promise. 如果不想全局变量的污染,@babel/plugin-transform-runtime. npm install --save-dev @babel/plugin-transform-runtime 和npm install --save @babel/runtime, babel配置如下

    {
        "presets": [
            [
                "@babel/preset-env",
                {
                    "targets": {
                        "chrome": "60",
                "ie": 11
    } } ] ], "plugins": ["@babel/plugin-transform-runtime"] }

      npm run babel, 发现并没有polyfill ,因为@babe/runtime 现在只剩下helpers, 没有polyfill 了。什么是helper ,就是一些辅助函数,为了避免重复的代码,主要是在编译过程中出现的重复代码。简单看一个例子,再在src 下建立 main.js, index.js 和main.js 都写一个class 类, 把babel.config.json 中的plugins暂时删除一下。npm run babel,看一下编译后的代码

    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    var fn = function fn() {
      _classCallCheck(this, fn);
    };

      都有一个一模一样的函数__classCallCheck, 如果代码中使用大量的类,就会存在大量重复的代码,最终会影响文件的体积。其实这个函数完全可以抽成一个共用的函数,这些函数是helper. babel 配置文件中的plugins 加回去, npm run babel

    "use strict";
    
    var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
    
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
    
    var fn = function fn() {
      (0, _classCallCheck2.default)(this, fn);
    };

       require("@babel/runtime/helpers/classCallCheck"), 这个函数就是__classCallCheck了, babel 把编译过程中需要的函数都抽成公用的,这些公用的函数都放到了@babel/runtime/ helpers 中.所以称之为helper 函数。  @babel/runtime 不提供polyfill 之后,babel 重新提供了两个包@babel/runtime-core2, @babel/runtime-corejs3, 它们分别对应 core-js@ 和core-js@3, 直接使用3 就可以了 npm install --save @babel/runtime-corejs3, 同时babel 的配置改一下

    "plugins": [["@babel/plugin-transform-runtime", {"corejs": 3}]]

       把index.js 改回promise, 可以看到成功编译了

    "use strict";
    
    var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
    
    var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
    
    var promise = new _promise.default();

      @babel/runtime-corejs3/core-js-stable/promise,在vscode,安装ctrl,点击它,找到这个文件,module.exports = require("core-js-pure/stable/promise"); 它引用的core-js-pure 下的文件。core-js-pure 是core-js 不污染全局变量的版本,我们只是在index.js引用了这个promise. 如果使用打包工具webpack的话,这个promise 的实现最终会打包到 最终的bundle文件中。简单看一下这个polyfill 的过程,@babel/runtime-corejs3, 这个包其实不包含任何代码,它只是把core-js 和regenator-runtime 列为了依赖项(githup仓库可以看到)。真正起作用的是 @babel/plugin-transform-runtime, 它把@babel/runtime-core3 node_modules中的 polyfill 插入到要polyfill的文件。这也就是index.js 中为什么会有 @babel/runtime-corejs3/helpers or @babel/runtime-corejs3/core-js-stable。 @babel/plugin-transform-runtime 同样也有一个问题,那就是它不能按需polyfill, 可能会使你的polyfiil 后的文件代码增大。

       作为一个应用开发者,我们并不关心全局变量污染的问题,可以使用@babel/preset-env 加上 core-js@3, 同是使用@babel/runtime 中的helper 函数。npm i @babel/preset-env @babel/plugin-transform-runtime  -- save-dev,  npm i core-js@3 @babel/runtime --save, 

    {
        "presets": [
            [
                "@babel/preset-env",
                {
                    "targets": {
                        "chrome": "60",
                        "ie": 11
                    },
                    "useBuiltIns": "entry",
                    "corejs": 3
                }
            ]
        ],
        "plugins": ["@babel/plugin-transform-runtime"]
    }

      useBuiltIns: "entry",  要在项目的入口文件 import 'core-js'  和 import "regenerator-runtime/runtime";(如果项目中使用async/await).

      useBuiltIns 也可以设置成"usage", 

    {
        "presets": [
            [
                "@babel/preset-env",
                {
                    "targets": {
                        "chrome": "60",
                        "ie": 11
                    },
                    "useBuiltIns": "usage",
                    "corejs": 3
                }
            ]
        ],
        "plugins": ["@babel/plugin-transform-runtime"]
    }

      但这里有一种case 需要考虑,把index.js 改成

    async function f() {}

      npm run babel, 转译后的文件如下

    "use strict";
    
    var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
    
    var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
    
    require("regenerator-runtime/runtime");
    
    var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
    
    function f() {
      return _f.apply(this, arguments);
    }
    
    function _f() {
      _f = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
        return _regenerator.default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
              case "end":
                return _context.stop();
            }
          }
        }, _callee);
      }));
      return _f.apply(this, arguments);
    }

      可以看到有

    var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

      在node_modules 中找到这个文件,可以发现它依赖Promise,  如果像我们现在这样,Promise 并没有polyfill,  所以对于不支持promise 的浏览器来说,项目运行就会报错。useBuiltIns 设置为"usage",有的时候,不太好配置,但是绝大部分情况下没有问题。

      如果你是一个库的作者,最好不要污染使用者的全局变量,那就用@babel/plugin-transform-runtime 和 @babel/runtime-core@3 进行polyfill ,使用@babel/preset-env 进行语法转译。 npm i @babel/preset-env @babel/plugin-transform-runtime  -- save-dev,  npm i @babel/runtime-corejs3 --save, 

    {
        "presets": ["@babel/preset-env"],
        "plugins": [["@babel/plugin-transform-runtime", {"corejs": 3}]]
    }

      如果你的库指定支持的浏览器,也可以对@babel/preset-env  进行设置 

     
  • 相关阅读:
    使用tcmalloc编译启动时宕机
    使用tcmalloc编译出现undefined reference to `sem_init'
    使用AddressSanitizer做内存分析(一)——入门篇
    VIM-美化你的标签栏
    Entity Framework Code First (六)存储过程
    Entity Framework Code First (五)Fluent API
    Entity Framework Code First (四)Fluent API
    Entity Framework Code First (三)Data Annotations
    Entity Framework Code First (二)Custom Conventions
    Entity Framework Code First (一)Conventions
  • 原文地址:https://www.cnblogs.com/SamWeb/p/12992110.html
Copyright © 2011-2022 走看看