乐趣区

Babel的使用笔记

写在前面

先理解下 babel 存在的意义,babel 的出现是为了解决不同的运行环境对于 es 语言的差异性,主要包括 2 类,语法的转译和特性的支持。
本文主要介绍 babel 7,此后的模块包都是以 @bable 开头的。

主要思路

举个栗子:你是个开饭店的,有一个客人来你店里点了一份牛排外卖,然后你做出来送到他的住所。那么完成这件事情需要几个步骤:

  1. 客人下单
  2. 你将生牛肉摆上
  3. 准备点缀物品
  4. 准备调料
  5. 操刀并根据客人要求加入点缀物品和调料
  6. 然后一顿操作
  7. 最后将 10 分熟的牛肉送到客人面前

大致是这样,先记住这个栗子,我们继续往下看。

主要模块包

  1. @babel/cli
  2. @babel/preset-env
  3. @babel/polyfill
  4. @babel/core
  5. @babel/plugin-transform-xxx(其他插件)

    • @babel/runtime
    • @babel/plugin-transform-runtime
    • @babel/plugin-transform-arrow-functions
    • 其他

@babel/cli

@babel/cli 是 babel 提供的内建的命令行工具,主要是提供 babel 这个命令来对 js 文件进行编译。
这里要注意它与另一个命令行工具 @babel/node 的区别,首先要知道他们二者都是命令行工具,但是官方文档明确对他们定义了他们各自的使用范围:
@babel/cli 是一个适合安装在本地项目里,提供 babel 命令,@babel/node 是一个全局安装提供 babel-node 命令。
将此包安装至开发依赖中可以在在当前项目中使用 babel 命令,并可以添加参数然后将转译后的代码输出到目标文件中

执行

./node_modules/.bin/babel src/testItems/babel --out-dir src/testItems/dist

或者

npx babel src/testItems/babel --out-dir src/testItems/dist

会发现 testItems 下会生成一个文件夹 dist 及其下面的 index.js 文件,内容如下

console.log([1, 2, 3].findIndex(x => x == 4));

const alertMe = msg => {console.log(msg);
};

呵呵,和我的源码一毛一样,那有个毛用!!!
不要慌,这是因为我们还没有对源码做相应的处理,就想做牛肉一样,总不能拿盘生牛肉给客户吧,是时候准备材料开始拿刀了!

@babel/preset-env

先看一个简单例子, 这里的 @babel/core 后面会讲到

const babel = require("@babel/core");

babel.transform(
  ` 
    console.log([1,2,3].findIndex(x => x == 4))
    const alertMe = (msg) => {console.log(msg)
    }
    
   `,
  {plugins: ["@babel/plugin-transform-arrow-functions"]
  },
  function(err, result) {console.log(result);
  }
);

执行

node src/testItems/babel/index.js

我们运行看下输出

{metadata: {},
  options:
   { babelrc: false,
     configFile: false,
     passPerPreset: false,
     envName: 'development',
     cwd: 'd:\\ 我的项目 \\bq-test',
     root: 'd:\\ 我的项目 \\bq-test',
     plugins: [[Plugin] ],
     presets: [],
     parserOpts:
      {sourceType: 'module', sourceFileName: undefined, plugins: [] },
     generatorOpts:
      { filename: undefined,
        auxiliaryCommentBefore: undefined,
        auxiliaryCommentAfter: undefined,
        retainLines: undefined,
        comments: true,
        shouldPrintComment: undefined,
        compact: 'auto',
        minified: undefined,
        sourceMaps: false,
        sourceRoot: undefined,
        sourceFileName: 'unknown' } },
  ast: null,
  code:
   'console.log([1, 2, 3].findIndex(function (x) {\n  return x == 4;\n}));\n\nconst alertMe = function (msg) {\n  console.log(msg);\n};',
  map: null,
  sourceType: 'module' }

可以看出这里的箭头函数已经被转译了,这就是 @babel/plugin-transform-arrow-functions 的作用。

但是在我们实际的开发中会用到更多的新语法,我们不可能一个个的添加插件吧,到这里我们可以看下 @babel/preset-env 这个插件,简单理解字面意思就是 ” 预设 ”,那到底是啥意思呢,大致意思就是官方帮我们准备的好的插件集合(包含 @bable/preset-es2015、@bable/preset-es2016、@bable/preset-es2017),修改代码如下会得到相同输出

const babel = require("@babel/core");

babel.transform(
  ` 
    console.log([1,2,3].findIndex(x => x == 4))
    const alertMe = (msg) => {console.log(msg)
    }
    
   `,
  {
    presets: [
      [
        "@babel/preset-env",
        {
          targets: {node: "4"}
        }
      ]
    ]
    // plugins: ["@babel/preset-env"]
  },
  function(err, result) {console.log(result);
  }
);

@babel/polyfill

我们再仔细看看这里

code:
   'console.log([1, 2, 3].findIndex(function (x) {\n  return x == 4;\n}));\n\nconst alertMe = function (msg) {\n  console.log(msg);\n};',
  map: null,

会发现除了箭头函数被转译了之外,findIndex 方法并没有任何的变化,那是为什么呢,这里理解下语法和特性的区别,语法指的是类似箭头函数等编码方式,特性指的是标准 api。后者需要 @babel/polyfill 来解决。

注意,使用 –save 参数而不是 –save-dev,因为这是一个需要在你的源码之前运行的 polyfill。

  • 和 @babel/preset-env 一起用的时候

    • 如果在.babelrc 中指定 useBuiltIns: ‘usage’ 的话,那么就不要在 webpack.config.js 的 entry array 和 source 中包含 @babel/polyfill 了。注意,@babel/polyfill 依然需要安装
    • 如果在.babelrc 中指定 useBuiltIns: ‘entry’ 的话,那么就要和上面讨论的一样,在你应用的入口文件顶部通过 require 或者 import 引入 @babel/polyfill.
    • 如果在.babelrc 中没有指定 useBuiltIns 的值或者 设置 useBuiltIns: false. 可以直接在 webpack.config.js 的 entry array 中添加 @babel/polyfill
    module.exports = {entry: ['@babel/polyfill', './app']
      }
  • 如果没有使用 @babel/preset-env. 那么就可以像我们上面讨论的一样把 @babel/polyfill 添加到 webpack 的 entry array 中。你也可以直接通过 import 或 require 把它添加到应用的入口文件顶部。但是我们并不推荐这么做

@babel/core

@babel/core 是 babel 的核心包,起到转译的作用,也就是我们前面提到的刀,主要方法有 transform 等
简单试用下:

下面添加配置文件来使用 babel 命令进行编译
主要有一下几种方式创建配置文件

  1. babel.config.js

在项目的根目录(package.json 文件所在目录)下创建一个名为 babel.config.js 的文件,并输入如下内容。

module.exports = function (api) {api.cache(true);

  const presets = [...];
  const plugins = [...];

  return {
    presets,
    plugins
  };
}
  1. .babelrc

在你的项目中创建名为 .babelrc 的文件,并输入以下内容。

{"presets": [...],
  "plugins": [...]
}
  1. package.json

或者,还可以选择将 .babelrc 中的配置信息作为 babel 键(key)的值添加到 package.json 文件中,如下所示:

{
  "name": "my-package",
  "version": "1.0.0",
  "babel": {"presets": [ ...],
    "plugins": [...],
  }
}
  1. .babelrc.js

与 .babelrc 的配置相同,但你可以使用 JavaScript 编写。

const presets = [...];
const plugins = [...];

module.exports = {presets, plugins};

这里使用第一种配置方法

module.exports = function(api) {api.cache(true);

  const presets = [
    [
      "@babel/preset-env",
      {
        "targets": {"node": "2",},
        "corejs": "3", 
        "useBuiltIns": "usage"
      }
    ]
  ];

  return {presets};
};

说明:

  1. targets 指定需要兼容的运行环境,也就是这里编译的代码能在 node 版本是 2 的 node 环境运行
  2. 使用 ”useBuiltIns”: “usage” 表示会使用 @bable/polyfill

其他插件

@babel/plugin-transform-runtime

"presets": [
    [
      "@babel/preset-env",
      {
        "targets": {"node": "4"}
      }
    ]
  ],
  "plugins": ["@babel/plugin-transform-runtime"]
}

编译后的代码

"use strict";

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

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

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

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

var testClass =
/*#__PURE__*/
function () {function testClass(msg) {(0, _classCallCheck2.default)(this, testClass);
    this.message = msg;
  }

  (0, _createClass2.default)(testClass, [{
    key: "say",
    value: function say() {alertMe(this.message);
    }
  }]);
  return testClass;
}();

console.log([1, 2, 3].findIndex(function (x) {return x == 4;}));

var alertMe = function alertMe(msg) {console.log(msg);
};

会发现 createClass 等比较通用的方法被提出来了,便于减少重复代码
当然还有其他的插件,这里就不一一列举了

总结

最后回顾一下开头的栗子做一下总结:

  1. @babel/cli 就是客户发出的订单要求也就是代码当前工程的运行指令
  2. 源代码就是生牛肉作为输入
  3. @bable/preset-env(还有其他的一些预设包集合)就是点缀物品改变原代码的表达形式
  4. @bable/polyfill 就是调料提供源代码的性特性补充
  5. @bable/core 就是操刀了或者其他的一些操作工具
  6. 然后结合一些其他插件如 @babel/plugin-transform-runtime 等插件一顿操作
  7. 最后生成 10 分兼容的编译代码

栗子不是很恰当,旨在帮助自己理解和记忆。

退出移动版