Babel浅谈[上]

一. 介绍

1、Babel 是什么

官方:Babel 是一个 JavaScript 编译器!

我:Babel 是一个源码到目标代码的转换器!

如图可以看到, Babel 的作用就是将「源码」转换为「目标代码」,至于转换中间的过程,下文讨论。

2、Babel 的发展历史

Babel 在 4.0 版本之前叫做 "6to5",由澳大利亚的 Sebastian (早已离开 Babel 团队,目前就职于 Facebook,依然致力于前端工具链开发中)在他的 2014 年十一月(那是他的高中时期)开始发布的,通过 6to5 这个名字我们就能知道,最初的 Babel 就是将 ES6 的代码转换成 ES5 的代码,2015 年与 ESNext 合并并改名成 Babel。Babel 的本意是 "巴别塔",取自神话小说中。由 6to5 重命名为 Babel 前,6to5 已经走过了三个版本,所以改名后的 Babel 从版本 4.0 开始,此时已经能够转译 ES7 与 JSX,2015 年发布 5.0,引入了 stage,创建了 plugin system 来支持自定义转译,同年又发布 6.0 ,拆分了几个核心包,引入了 presets 和 plugin/presets options 的概念,18 年,发布了 7.0,除了性能方面的提升以外,增加了对 typescript 的支持,对安装包使用「@babel」的命名空间。

3、Babel 的作用

主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为 es5 语法,让开发者无视用户浏览器的差异性,并且能够用新的 JS 语法及特性进行开发。除此之外,Babel 能够转换 JSX 语法,并且能够支持 TypeScript 转换为 JavaScript。 总结一下:Babel 的作用如下

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性
  • 源码转换

二. Babel7 的使用

1、配置文件

Babel 支持多种形式的配置文件,根据使用场景不同可以选择不同的配置文件。如果配置中需要书写 js 逻辑,可以选择「babel.config.js」或者 「.babelrc.js」;如果只是需要一个简单的 key-value 配置,那么可以选择「.babelrc」,甚至可以直接在 「package.json」 中配置。

这里给出在各种配置文件中配置 Babel 的书写形式,以 plugins 和 presets 配置为例:

// babel.config.js
module.exports = function(api) {
    api.cache(true);
    const plugins = ["@babel/plugin-transform-arrow-functions"];
    const presets = ["@babel/preset-env"];
    return {
        plugins,
        presets
    };
}
// .babelrc.js
const plugins = ["@babel/plugin-transform-arrow-functions"];
const presets = ["@babel/preset-env"];
module.exports = { plugins, presets };
// .babelrc
{
    plugins: ["@babel/plugin-transform-arrow-functions"],
    presets: ["@babel/preset-env"]
}
// package.json
{
    "name": "my-package",
    "version": "1.0.0",
    // ...省略其他配置
    "babel": {
        "plugins": ["@babel/plugin-transform-arrow-functions"],
        "presets": ["@babel/preset-env"]
    }
}
2、小试个牛刀

所有 Babel 的包都发布在 npm 上,并且名称以 @babel 为前缀(自从版本 7.0 之后),接下来,我们一起看下 @babel/core 和 @babel/cli 这两个 npm 包。

  • @babel/core - 核心库,封装了 Babel 的核心能力
  • @babel/cli - 命令行工具, 提供了「babel」 这个命令

接下来我们试着通过 Babel 将 es6 中的「箭头函数」语法转换为 es5 中「function 函数申明」语法:

// 安装这两个依赖包
npm install --save-dev @babel/core @babel/cli
/*
    package.json 文件中配置 babel 执行命令
    以下命令含义为:将 src 目录中的文件经过 babel 转换,并将转换后的文件输出到 lib 目录中
*/
"script": {
    "compile": "babel src --out-dir lib --watch"
}
//  src/index.js
const fn = () => {
    console.log('xuemingli');
};
//  lib/index.js
const fn = () => {
    console.log('xuemingli');
};

此时,我们执行 npm run compile 后,发现 lib/index.js 中的依然是箭头函数,说明 src/index.js 中的代码并没有没 babel 转换,为什么?请大家记住一句话:Babel 构建在插件之上的。默认情况下,Babel 不做任何处理,需要借助插件来完成语法的解析,转换,输出。

3、插件

接下来我们一起尝试使用 @babel/plugin-transform-arrow-functions 插件来将箭头函数转换成函数声明:

//.babelrc
{
    "plugins": ["@babel/plugin-transform-arrow-functions"]
}
//  lib/index.js
const fn = function () {
    console.log('xuemingli');
};

再次执行 npm run compile 后发现 lib/index.js 中已经是函数声明了,说明 src/index.js 中的代码被 babel 转换了。到此为止,大家可能想迫切的知道插件究竟做了什么事,别急,请大家慢慢往后看。

插件的配置形式常见有两种,分别是字符串格式与数组格式(见下面代码),并且可以传递参数,如果插件名称为 @babel/plugin-XXX,可以使用简写成@babel/XXX,例如 @babel/plugin-transform-arrow-functions 便可以简写成 @babel/transform-arrow-functions。请大家再记住一句话: 插件的执行顺序是从前往后

// .babelrc
/*
* 以下三个插件的执行顺序是:
    @babel/proposal-class-properties ->
    @babel/syntax-dynamic-import ->
    @babel/plugin-transform-arrow-functions
*/
{
    "plugins": [
        // 同 "@babel/plugin-proposal-class-properties"
        "@babel/proposal-class-properties",
        // 同 ["@babel/plugin-syntax-dynamic-import"]
        ["@babel/syntax-dynamic-import"],
        [
            "@babel/plugin-transform-arrow-functions",
            {
                "loose": true
            }
        ]
    ]
}

到此为止,我们了解了插件的使用,对于特定的 ES6+ 语法,我们需要使用特定的插件将其转换为 ES5 中的代码,设想一下,如果我们的项目中使用了大量的 ES6+ 语法,我们是不是需要一个个的配置相应的插件呢?当然不需要,那如何解决呢?有请 「预设」出场。

4、预设

预设是一组插件的集合。与插件类似,预设的配置形式也是字符串和数组两种(见下面代码),预设也可以将 @babel/preset-XXX 简写为 @babel/XXX 。预设的执行顺序是从后往前,并且插件在预设之前执行。

我们常见的预设有以下几种:

  • @babel/preset-env: 可以无视浏览器环境的差异而尽情地使用 ES6+ 新语法和新特性;
    • 注:语法和特性不是一回事,语法上的迭代是让我们书写代码更加简单和方便,如展开运算符、类,结构等,因此这些语法称为语法糖;特性上的迭代是为了扩展语言的能力,如 Map、Promise 等,事实上,Babel 对新语法和新特性的处理也是不一样的,对于新语法,Babel 通过插件直接转换,而对于新特性,Babel 还需要借助 polyfill 来处理和转换。
  • @babe/preset-react: 可以书写 JSX 语法,将 JSX 语法转换为 JS 语法;
  • @babel/preset-typescript:可以使用 TypeScript 编写程序,将 TS 转换为 JS;
    • 注:该预设只是将 TS 转为 JS,不做任何类型检查
  • @babel/preset-flow:可以使用 Flow 来控制类型,将 Flow 转换为 JS;

预设使用示例如下:

// .babelrc
/*
*  以下配置中,插件比预设先执行
*  预设的执行顺序为:
    @babel/preset-react ->
    @babel/preset-typescript ->
    @babel/preset-env
*/
{
    "plugins": [
        // 同 "@babel/plugin-proposal-class-properties"
        "@babel/proposal-class-properties",
        // 同 ["@babel/plugin-syntax-dynamic-import"]
        ["@babel/syntax-dynamic-import"],
        [
            "@babel/plugin-transform-arrow-functions",
            {
                "loose": true
            }
        ]
    ],
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": {
                    "version": 3,
                    "proposals": true // 使用尚在提议阶段特性的 polyfill
                }
            }
        ],
        "@babel/preset-typescript",
        // 同 @babel/preset-react
        "@babel/react"
    ]
}

对于 @babel/preset-env ,我们通常需要设置目标浏览器环境,可以在根目录下的 .browserslistrc 文件中设置,也可以在该预设的参数选项中通过 targets(优先级最高) 或者在 package.json 中通过 browserslist 设置。如果我们不设置的话,该预设默认会将所有的 ES6+ 的新语法(注意:这里我说的只是新语法,不包含新特性)全部做转换,反之,该预设只会对目标浏览器环境不兼容的新语法做转换。我推荐设置目标浏览器环境,这样在中大型项目中可以明显缩小编译后的代码体积,因为有些新语法的转换需要引入一些额外定义的 helper 函数的,比如 class。

目标浏览器配置示例如下:

// .browserslistrc
> 0.25%
not dead
// .babelrc
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "targets": "> 0.25%, not dead"
            }
        ]
    ]
}
// package.json
{
    "name": "my-package",
    "version": "1.0.0",
    // ...省略其他配置
    "browserslist": "> 0.25%, not dead"
}

设置不同目标浏览器环境,编译情况示例如下:

src/index.js 中的是源代码,lib/index.js 是转换后的目标代码:

// src/index.js
// 新语法
const fn = () => {
  console.log('xuemingli');
};
const arr = ["name", "age"];
const [a, b] = arr;
const arr2 = [...arr, "school"];
const arr3 = [["key1", "value1"], ["key2", "value2"]]
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayName() {
    console.log(this.name);
  }
}
const person = new Person('xuemingli', '26');
person.sayName();

// 新特性
const isHas = [1,2,3].includes(2);
const resArr = [1,[2,3]].flat();
const p = new Promise((resolve, reject) => {
    resolve(100);
});
const map = new Map(arr3);
const value1 = map.get('key1');
const arr4 = Object.keys({ name: 'xuemingli', age: 26 })

结论 1:通过如下转换后的目标代码可以验证:如果不设置浏览器环境的话,@babel/preset-env 会将新语法全部转换,并且对于一些特殊的新语法,如 class,还额外定义了一些 helper 函数,而新特性并没有做任何转换。

/*
  lib/index.js
  不设置目标浏览器环境
*/
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
// 新语法
var fn = function fn() {
  console.log('xuemingli');
};
var arr = ["name", "age"];
var a = arr[0],
    b = arr[1];
var arr2 = [].concat(arr, ["school"]);
var arr3 = [["key1", "value1"], ["key2", "value2"]];
var Person = /*#__PURE__*/function () {
  function Person(name, age) {
    _classCallCheck(this, Person);
    this.name = name;
    this.age = age;
  }
  _createClass(Person, [{
    key: "sayName",
    value: function sayName() {
      console.log(this.name);
    }
  }]);
  return Person;
}();
var person = new Person('xuemingli', '26');
person.sayName();

// 新特性
var isHas = [1, 2, 3].includes(2);
var resArr = [1, [2, 3]].flat();
var p = new Promise(function (resolve, reject) {
  resolve(100);
});
var map = new Map(arr3);
var value1 = map.get('key1');
var arr4 = Object.keys({
  name: 'xuemingli',
  age: 26
});

结论 2:通过如下转换后的目标代码可以验证:如果设置了目标浏览器环境,@babel/preset-env 只会对目标浏览器环境不兼容的新语法做转换。如代码所示,当前目标浏览器环境下,该预设只转换了「结构赋值」语法,而其他新语法在已经被浏览器实现了,便不需要转换了。

/*
  lib/index.js
  目标浏览器环境设置为 last 53 Chrome versions
*/
"use strict";
// 新语法
const fn = () => {
  console.log('xuemingli');
};
const arr = ["name", "age"];
const a = arr[0],
      b = arr[1];
const arr2 = [...arr, "school"];
const arr3 = [["key1", "value1"], ["key2", "value2"]];
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayName() {
    console.log(this.name);
  }
}
const person = new Person('xuemingli', '26');
person.sayName();

// 新特性
const isHas = [1, 2, 3].includes(2);
const resArr = [1, [2, 3]].flat();
const p = new Promise((resolve, reject) => {
  resolve(100);
});
const map = new Map(arr3);
const value1 = map.get('key1');
const arr4 = Object.keys({
  name: 'xuemingli',
  age: 26
});

对于新特性,@babel/preset-env 能否转换呢?答案当然是能的。但是需要通过 useBuiltIns 这个参数选项实现,值需要设置为 usage,这样的话,只会转换我们使用到的新语法和新特性(注意:这里既包括新语法也包括新特性),能够有效减小编译后的包体积,并且还要设置 corejs: { version: 3, proposals } 选项,因为转换新特性需要用到 polyfill,而 corejs 就是一个 polyfill 包。如果不显示指定 corejs 的版本的话,默认使用的是 version 2 ,而 version 2 已经停更,诸如一些更新的特性的 polyfill 只会更行与 version 3 里,如 Array.prototype.flat()。

// .babelrc
"presets": [
    [
        "@babel/preset-env",
        {
            "useBuiltIns": "usage",
            "corejs": {
                "version": 3,
                "proposals": true // 使用尚在提议阶段特性的 polyfill
            }
        }
    ]
]
// lib/index.js
// 从 corejs 这个包里引入了 polyfill 并对新特性做了转换
"use strict";
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/esnext.map.delete-all.js");
require("core-js/modules/esnext.map.every.js");
require("core-js/modules/esnext.map.filter.js");
require("core-js/modules/esnext.map.find.js");
require("core-js/modules/esnext.map.find-key.js");
require("core-js/modules/esnext.map.includes.js");
require("core-js/modules/esnext.map.key-of.js");
require("core-js/modules/esnext.map.map-keys.js");
require("core-js/modules/esnext.map.map-values.js");
require("core-js/modules/esnext.map.merge.js");
require("core-js/modules/esnext.map.reduce.js");
require("core-js/modules/esnext.map.some.js");
require("core-js/modules/esnext.map.update.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/es.object.keys.js");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
// 新语法
var fn = function fn() {
  console.log('xuemingli');
};
var arr = ["name", "age"];
var a = arr[0],
    b = arr[1];
var arr2 = [].concat(arr, ["school"]);
var arr3 = [["key1", "value1"], ["key2", "value2"]];
var Person = /*#__PURE__*/function () {
  function Person(name, age) {
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }
  _createClass(Person, [{
    key: "sayName",
    value: function sayName() {
      console.log(this.name);
    }
  }]);
  return Person;
}();
var person = new Person('xuemingli', '26');
person.sayName();

// 新特性
var isHas = [1, 2, 3].includes(2);
var resArr = [1, [2, 3]].flat();
var p = new Promise(function (resolve, reject) {
  resolve(100);
});
var map = new Map(arr3);
var value1 = map.get('key1');
var arr4 = Object.keys({
  name: 'xuemingli',
  age: 26
});

以下示例演示了使用 @babel/preset-react 、@babel/preset-typescript 以及 @babel/preset-env 来转换 src/index.tsx 中的源代码,lib/index.js 中的是转换后的目标代码:

// .babelrc
"presets": [
    [
        "@babel/preset-env",
        {
            "useBuiltIns": "usage",
            "corejs": {
                "version": 3,
                "proposals": true // 使用尚在提议阶段特性的 polyfill
            }
        }
    ],
    "@babel/preset-typescript", "@babel/react"
]

// src/index.tsx
import React, { useState } from 'react';
const App: React.FC = () => {
  const [value, setValue] = useState(0);
  const fn = (): void => {
    setValue(value => value++);
  };
  const arr: string[] = ["name", "age"];
  const [a, b] = arr;
  const arr2: string[] = [...arr, "school"];
  class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
    }
    sayName() {
      console.log(this.name);
    }
  }
  const person = new Person('xuemingli', 26);
  person.sayName();

  // 新特性
  const isHas: boolean = [1,2,3].includes(2);
  const resArr: number[] = [1,[2,3]].flat();
  const p: Promise = new Promise((resolve, reject) => {
      resolve(100);
  });
  const map: Map = new Map();
  map.set('key1', 'value1');
  const arr4: string[] = Object.keys({ name: 'xuemingli', age: 26 })

  return (
    
{value}
) }; export default App;
// lib/index.js
"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.array.slice.js");
require("core-js/modules/es.array.from.js");
require("core-js/modules/es.regexp.exec.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.weak-map.js");
require("core-js/modules/esnext.weak-map.delete-all.js");
require("core-js/modules/es.object.get-own-property-descriptor.js");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/esnext.map.delete-all.js");
require("core-js/modules/esnext.map.every.js");
require("core-js/modules/esnext.map.filter.js");
require("core-js/modules/esnext.map.find.js");
require("core-js/modules/esnext.map.find-key.js");
require("core-js/modules/esnext.map.includes.js");
require("core-js/modules/esnext.map.key-of.js");
require("core-js/modules/esnext.map.map-keys.js");
require("core-js/modules/esnext.map.map-values.js");
require("core-js/modules/esnext.map.merge.js");
require("core-js/modules/esnext.map.reduce.js");
require("core-js/modules/esnext.map.some.js");
require("core-js/modules/esnext.map.update.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/es.object.keys.js");

var _react = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]( "Symbol.iterator") method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
var App = function App() {
  var _useState = (0, _react.useState)(0),
      _useState2 = _slicedToArray(_useState, 2),
      value = _useState2[0],
      setValue = _useState2[1];
  var fn = function fn() {
    setValue(function (value) {
      return value++;
    });
  };
  var arr = ["name", "age"];
  var a = arr[0],
      b = arr[1];
  var arr2 = [].concat(arr, ["school"]);
  var Person = /*#__PURE__*/function () {
    function Person(name, age) {
      _classCallCheck(this, Person);

      this.name = name;
      this.age = age;
    }
    _createClass(Person, [{
      key: "sayName",
      value: function sayName() {
        console.log(this.name);
      }
    }]);
    return Person;
  }();
  var person = new Person('xuemingli', 26);
  person.sayName();

  // 新特性
  var isHas = [1, 2, 3].includes(2);
  var resArr = [1, [2, 3]].flat();
  var p = new Promise(function (resolve, reject) {
    resolve(100);
  });
  var map = new Map();
  map.set('key1', 'value1');
  var arr4 = Object.keys({
    name: 'xuemingli',
    age: 26
  });
  return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("div", null, value), /*#__PURE__*/_react.default.createElement("button", {
    onClick: fn
  }, "+"));
};
var _default = App;
exports.default = _default;

到此为止,我们了解了预设的概念和使用方式。虽然 @babel/env 可以帮我们做新语法和新特性的按需转换,但是依然存在 2 个问题:

  • 从 corejs 引入的 polyfill 是全局范围的,不是模块作用域返回的,可能存在污染全局变量的风险,如上例中的 require("core-js/modules/es.promise.js");
  • 对于某些新语法,如 class,会在编译后的文件中注入很多 helper 函数声明,而不是从某个地方 require 进来的函数引用,从而增大编译后的包体积;

能不能解决如上两个问题呢?当然能,有请 runtime 出场。

5、runtime

runtime 是 babel7 提出来的概念,旨在解决如上提出的性能问题的。接下来我们实践一下 @babel/plugin-transform-runtime 插件配合 @babel/preset-env 使用,示例如下:

npm install --save-dev @babel/plugin-transform-runtime
// @babel/runtime 是要安装到生产依赖的,因为新特性的编译需要从这个包里引用 polyfill
// 不错,它就是一个封装了 corejs 的 polyfill 包
npm install --save @babel/runtime
// .babelrc
{
  "presets": [
    "@babel/env"
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",{
          "corejs": 3
      }
    ]
  ],
}
// src/index.js
// 新语法
const fn = () => {
  console.log('xuemingli');
};
const arr = ["name", "age"];
const [a, b] = arr;
const arr2 = [...arr, "school"];
const arr3 = [["key1", "value1"], ["key2", "value2"]]
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayName() {
    console.log(this.name);
  }
}
const person = new Person('xuemingli', '26');
person.sayName();

// 新特性
const isHas = [1,2,3].includes(2);
const resArr = [1,[2,3]].flat();
const p = new Promise((resolve, reject) => {
    resolve(100);
});
const map = new Map(arr3);
const value1 = map.get('key1');
const arr4 = Object.keys({ name: 'xuemingli', age: 26 })

npm run compile 编译后,可以明显看到,引入的 polyfill 不再是全局范围内的了,而是模块作用域范围内的;并且不再是往编译文件中直接注入 helper 函数了,而是通过引用的方式,既解决了全局变量污染的问题,又减小了编译后包的体积。

// lib/index.js
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/map"));
var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
var _context, _context2, _context3;
// 新语法
var fn = function fn() {
  console.log('xuemingli');
};
var arr = ["name", "age"];
var a = arr[0],
    b = arr[1];
var arr2 = (0, _concat.default)(_context = []).call(_context, arr, ["school"]);
var arr3 = [["key1", "value1"], ["key2", "value2"]];
var Person = /*#__PURE__*/function () {
  function Person(name, age) {
    (0, _classCallCheck2.default)(this, Person);
    this.name = name;
    this.age = age;
  }
  (0, _createClass2.default)(Person, [{
    key: "sayName",
    value: function sayName() {
      console.log(this.name);
    }
  }]);
  return Person;
}();
var person = new Person('xuemingli', '26');
person.sayName();

// 新特性
var isHas = (0, _includes.default)(_context2 = [1, 2, 3]).call(_context2, 2);
var resArr = (0, _flat.default)(_context3 = [1, [2, 3]]).call(_context3);
var p = new _promise.default(function (resolve, reject) {
  resolve(100);
});
var map = new _map.default(arr3);
var value1 = map.get('key1');
var arr4 = (0, _keys.default)({
  name: 'xuemingli',
  age: 26
});
6、结合 webpack

在 webpack 中,通过 babel-loader 的方式来接入 babel 的能力,babel 配置文件与单独使用 babel 时相同。在 webpack 中使用 babel 时建议开启 babel 的缓存能力,即 cacheDirectory,统计显示,在大型项目中,构建速度可以提升 1 倍,webpack 的配置文件示例如下:

// webpack.config.js
const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.[j,t]sx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader?cacheDirectory'
        }
      }
    ]
  },
};

转载 Babel浅谈

你可能感兴趣的:(Babel浅谈[上])