Si vous ne connaissez pas la configuration de babel, voir ici

1. Qu'est-ce que Babel ?

Jetons un œil à l'explication donnée par le site officiel :

image.pngEn termes simples, il s'agit de convertir la nouvelle syntaxe de es5+ en JavaScript en une syntaxe rétrocompatible, afin que le code puisse être exécuté dans des navigateurs de version inférieure ou d'autres environnements. Mais je ne pense pas qu'il soit exact de dire cela. Personnellement, je pense que babel est en fait une plate-forme qui fournit des capacités de conversion de code. Au bon moment, nous appelons nos plugins enregistrés pour convertir le code cible.

2. Introduction au principe de fonctionnement de Babel

1. Compilateurs et transpileurs

编译Il fait référence à la conversion de langages de haut niveau en langages de bas niveau. Nous utilisons généralement des langages de développement de haut niveau pour le développement, tels que JavaScript, C++, Java, etc. est de fournir des grammaires et des API de niveau supérieur pour que les développeurs puissent développer et comprendre. Améliorer l'efficacité du développement, mais notre ordinateur ne connaît pas ces langages de développement de haut niveau, donc le rôle du compilateur vient, il convertit le langage de développement de haut niveau que nous écrire dans des langages de bas niveau que les machines peuvent comprendre et exécuter, comme le langage d'assemblage, le langage machine, etc.

转译Il fait référence à la conversion d'un langage de haut niveau en un langage de haut niveau, tel que TypeScript en rouille. Généralement, le traducteur est composé parsede trois étapes : , transform, et :generate

image.png

  • parse comprend trois étapes d'analyse lexicale, d'analyse syntaxique et d'analyse sémantique, et génère enfin un arbre de syntaxe abstraite (AST) pour familiariser le code source.

  • La phase de transformation appellera divers plugins pour ajouter, supprimer et modifier l'AST.

  • La phase de génération convertira l'AST en code objet final et générera le sourcemap.

2. Flux de travail Babel

Après avoir parlé de la différence entre un compilateur et un transpileur, il est clair en un coup d'œil à quelle catégorie notre babel appartient. Babel est un transpileur, et le code js qu'il a traduit est finalement remis au moteur js pour analyse. La partie la plus importante pour babel est la phase de transformation, les plugins que nous avons enregistrés sur babel sont exécutés ici, si nous n'enregistrons aucun plugin naturellement babel ne fera aucune transformation sur notre code, ce qui me vérifie Comme mentionné au début, babel est en fait une plate-forme.

3. Configuration de base de Babel

1. Construction du projet

在学习 babel 前我们先手动搭建一个项目。

mkdir babel-test && cd babel-test
npm init
mkdir src && cd src
touch index.js
复制代码

新建好项目之后我们先装几个 babel 必备的依赖:

  1. @babel/core

这个包是 babel 的核心库,我们的 parsetransform 两个核心方法都是在这个库中,具体怎么使用我们不需要知道,我们现安装它。

npm i --save-dev @babel/core

//使用
// var babel = require("@babel/core");
// import { transform, parse } from "@babel/core";
复制代码
  1. @babel/cli

这是一个终端运行工具,这样我们可以直接使用终端命令对指定的文件进行转译,同样先装它。

npm i --save-dev @babel/cli
复制代码

2、plugins

好了依赖装完之后可以来小试牛刀了,我们在 index.js 中写入一个函数:

const fn = () => {
  console.log("babel");
};
复制代码

如果要运行 babel 命令的话我们还需要在 package.json 中添加运行命令,如下所示:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "babel ./src/index.js --out-file ./src/compliled.js --watch"
  },
复制代码

babel 就是我们的启动命令,./src/index.js 表示待转译的文件,--out-file表示指定我们的输出文件,--watch 表示监听文件的改动并重新编译文件。

下面我们在终端运行 npm run build,查看 complied.js 文件:

// complied.js
const fn = () => {
  console.log("babel");
};
复制代码

我们发现 babel 并没有对代码进行转译,这是符合我们的预期的,因为现在我们并没有注册 plugins。如果想对箭头函数进行转换我们需要安装 @babel/pluign-transform-arrow-function 这个插件。

npm install --save-dev @babel/plugin-transform-arrow-function
复制代码

然后在根目录下新建一个 .babelrc 文件去给 babel 添加配置文件:

// .babelrc
{
  "plugins": [
    "@babel/plugin-transform-arrow-functions"
  ]
}
复制代码

我们重新编译一下:

// complied.js
const fn = function () {
  console.log("babel");
};
复制代码

我们看到 babel 成功对代码进行转译,现在我们想想如果我们要将其他的 es5+ 的语法进行转换是不是需要将所有的 plugin 都引入,答案是肯定的,不过不需要我们手动去引入 babel 已经帮我们把这件事做了,这就是下面要介绍的 @babel/preser-env

3、@babel/preset-env

@babel/preset-env 主要作用是对我们所使用的并且目标浏览器中缺失的功能进行代码转换,那这里有个疑问了,babel 是怎么知道目标浏览器到底支不支持该特性呢?这就需要我们在配置项 targets 中去设置浏览器的版本,下面我们举例说明: 先安装 @babel/preset-env 预设:

npm i --save-dev @babel/preset-env
复制代码

我们更改下 index.js 中的内容:

// index.js
const fn = function () {
  console.log("babel");
};
const num = 2 ** 4;
复制代码

@babel/preset-env 加入配置文件:

// .babelrc
{
  "presets": [
    "@babel/env" // 也可以写 @babel/preset-env 两者是等价的
  ]
}
复制代码

接下来我们再重新执行 npm run build

"use strict";

var fn = function fn() {
  console.log("babel");
};

var num = Math.pow(2, 4);
复制代码

babel 顺利对我们的代码进行转译,如果我们想指定浏览器的版本,我们可以这样写:

// .babelrc
{
  "presets": [
    [
        "@babel/env", // 也可以写 @babel/preset-env 两者是等价的
        {
            "targets": {
                "browsers": "Chrome 99"
            }
        }
    ]
  ]
}
复制代码

此时就会告诉 babel 我们的浏览器的版本是 Chrome 99,而对于这个版本的浏览器他是支持箭头函数的,所以 babel 不会对他进行转译。

4、@babel/polyfill

polyfill 的中文意思是垫片,垫片就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用,也就是说对于像 PromiseclassArray.prototype.includes 这些特性 babel 是无法转换的,此时我们就需要用到 polyfill

我们先安装 @babel/polyfill,然后需要在 index.js 文件中引入:

// index.js
import '@babel/polyfill';
const fn = () => {
  console.log("babel");
};
const p = new Promise((resolve, reject) => {
  resolve("babel");
});
const list = [1, 2, 3, 4].map((item) => item * 2);
复制代码

我们看下转译后的代码:

"use strict";

require("@babel/polyfill");

var fn = function fn() {
  console.log("babel");
};

var p = new Promise(function (resolve, reject) {
  resolve("babel");
});
var list = [1, 2, 3, 4].map(function (item) {
  return item * 2;
});
复制代码

乍一看好像没啥特别的变化,只是多了一行 require("@babel/polyfill"),这个意思就是把这个库中的所有 polyfill 都引入进来,里面就包括了 Promise 和 map 的实现,那我们能不能再优化一下呢?让他支持按需加载。

5、useBuiltIns

上面我们通过 import "@bable/polyfill" 的方式来磨平一些新特性,但是从 babel v7.4.0 开始官方就不建议采取这样的方式了,因为引入这个库就等于引入了下面两个库:

require("core-js"); //这里的corejs版本为2
require("regenerator-runtime/runtime");
复制代码

这样意味着会带来两个问题:

  • 我们不能实现按需加载
  • 污染全局环境:因为像 includes、filter 这样的方法是通过向全局对象和内置对象的 prototype 上添加方法来实现的。

@babel/preset-env 提供了一个 useBuiltIns 参数,设置值为 usage 时,就只会包含代码需要的 polyfill,这里需要注意的是如果我们将 useBuiltIns 设置为 usage 时必须要将 corejs 的版本设置为 3,因为在 corejs@2 中已经不会再添加新的特性了,同时我们需要手动去安装 corejs@3。

此时我们再去修改 .babelrc 中的配置:

{
  "presets": [
    [
      "@babel/env",
      {   
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}
复制代码

修改 index.js 中的代码:

const fn = () => {
  console.log("babel");
};
const p = new Promise((resolve, reject) => {
  resolve("babel");
});
const list = [1, 2, 3, 4].map((item) => item * 2);

class Person {
    constructor(name) {
      this.name = name;
    }
    play() {
      console.log(this.name);
    }
}

const asyncFun = async () => {
  await 1;
}
复制代码

我们看一下最终转译后的代码:

"use strict";

require("core-js/modules/es.object.define-property.js");

require("regenerator-runtime/runtime.js");

require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

require("core-js/modules/es.array.map.js");

require("core-js/modules/es.function.name.js");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

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("babel");
};

var p = new Promise(function (resolve, reject) {
  resolve("babel");
});
var list = [1, 2, 3, 4].map(function (item) {
  return item * 2;
});

var Person = /*#__PURE__*/function () {
  function Person(name) {
    _classCallCheck(this, Person);

    this.name = name;
  }

  _createClass(Person, [{
    key: "play",
    value: function play() {
      console.log(this.name);
    }
  }]);

  return Person;
}();

var asyncFun = /*#__PURE__*/function () {
  var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return 1;

          case 2:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));

  return function asyncFun() {
    return _ref.apply(this, arguments);
  };
}();
复制代码

此时我们会发现 babel 并没有把所有的 polyfill 都引入进来而是按需引入,但是从转译后的结果看来,还是存在以下问题:

  • 全局环境污染还是存在

  • 代码的重复引入

    _classCallCheck_defineProperties这样的函数只要用到了 class 就会被引入进来,就会产生重复引入的问题。

  • 仍然需要安装 @babel/polyfill

    因为当我们使用 async/await 时需要引入 regenerator-runtime 这个库,或者我们手动去安装这个库也行。

那有什么方法能够解决这些问题呢?下面就要说到 @babel/plugin-transform-runtime这个插件了。

6、@babel/plugin-transform-runtime

首先我们需要安装两个依赖 @babel/plugin-transform-runtime@babel/runtime,前者是在开发的时候使用,但是最终运行时的代码依赖后者,所以我们需要在生产环境中安装 @babel/runtime

npm install --save-dev @babel/plugin-transform-runtime\
npm install --save @babel/runtime
复制代码

我们先来看看 @babel/runtime-corejs@3这个库中有什么:

  • core-js

    转换一些内置的类和一些静态方法,比如 Promise、Array.from、Array.includes,绝大部分的转换是在这里做的。

  • regenerator

    作为 corejs 的补丁,主要是针对 generator/yield 和 async/await 的支持。

  • helpers

image.png helpers 内置了一些方法的实现,比如使用async/await就需要用到 _asyncToGenerator2

下面就来使用这个插件,首先更改下 .babelrc 的配置,这里我们去掉了 presets 里的 useBuiltInscorejs,不然就会出现重复引入的问题。

{
  "presets": [[
    "@babel/env"
  ]],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ]
  ]
}
复制代码

index.js 文件保持不变,我们重新运行之后看下结果:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

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

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _context, _context2;

var fn = function fn() {
  console.log("babel");
};

var p = new _promise["default"](function (resolve, reject) {
  resolve("babel");
});
var list = (0, _map["default"])(_context = [1, 2, 3, 4]).call(_context, function (item) {
  return item * 2;
});
var list1 = (0, _includes["default"])(_context2 = [1, 2, 3, 4]).call(_context2, 9);

var Person = /*#__PURE__*/function () {
  function Person(name) {
    (0, _classCallCheck2["default"])(this, Person);
    this.name = name;
  }

  (0, _createClass2["default"])(Person, [{
    key: "play",
    value: function play() {
      console.log(this.name);
    }
  }]);
  return Person;
}();

var asyncFun = /*#__PURE__*/function () {
  var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
    return _regenerator["default"].wrap(function _callee$(_context3) {
      while (1) {
        switch (_context3.prev = _context3.next) {
          case 0:
            _context3.next = 2;
            return 1;

          case 2:
          case "end":
            return _context3.stop();
        }
      }
    }, _callee);
  }));

  return function asyncFun() {
    return _ref.apply(this, arguments);
  };
}();
复制代码

下面我们来分析他是怎么解决上面提到的问题的:

  1. 怎么避免重复引入的?

我们所有的 polyfill 实现都被保存在 @babel/runtime-corejs3 这个包里面,我们需要的时候通过 require 导入进来就可以直接使用了。

  1. 怎么避免全局环境污染的呢?

我们挑就拿 Array.includes 来讲,在之前我们是直接在全局对象的 prototype 上添加 includes 方法,而现在我们没有直接修改 prototype,而是使用 _includes 去替代 includes 方法,这样就有效的避免全局环境的污染。

3、@babel/runtime-corejs3 中已经集成了 regenerator 我们就不需要再手动安装。

7、@babel/preset-typescript

@babel/preset-typescript 主要就是帮我们将 ts 代码转换成 js,这里面有两个配置要注意一下:

  • isTSX

这个配置项默认值是 false,表示不开启 jsx 解析,此时意味着我们可以使用尖括号的语法进行断言,如果改为 true,即强制开启 jsx 解析, 此时我们使用尖括号就会出错。我们看下官网是怎么定义断言的。

image.png 这里明确的说明不能在 .tsx 文件里面使用尖括号的形式去断言。

  • allExtensions

Cet élément de configuration est faux par défaut, si nous le définissons sur vrai, cela signifie que chaque fichier doit être analysé comme TS, TSX ou TS sans ambiguïté JSX, il isTSXest utilisé avec .

Utilisons un exemple pour expliquer, nous allons changer index.js en index.ts, et aussi changer le nom du fichier dans le fichier package.json.

// index.ts
const bar = 'bar';
const str = <string>bar;
复制代码

Installer @babel/preset-typescriptet modifier la configuration de .babelrc

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

Lorsque nous l'exécuterons à ce moment, npm run buildnous verrons que babel est converti avec succès. Ensuite, nous modifierons la configuration de .babelrc, ainsi que les deux propriétés mentionnées ci-dessus :

{
  "presets": [
    "@babel/env",
    [
      "@babel/preset-typescript",
      {
        "isTSX": true,
        "allExtensions": true
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ]
  ]
}
复制代码

Après une nouvelle exécution, notre console imprimera un message d'erreur

image.pngCela signifie qu'il existe un contenu JSX non terminé.

Je suppose que tu aimes

Origine juejin.im/post/7082976239421980679
conseillé
Classement