Babel 学习手记

发布于 2023-07-31 12:43:50 字数 17420 浏览 43 评论 0

一、Babel 相关的概念

  • @babel/core: babel 的核心,核心的 api 都在包含在这里。
  • @babel/cli: 命令行工具,通过命令对 js 文件进行转换的工具。
  • @babel/perset-env: 指定转换的工作环境。
  • @babel/polyfill: 相当于一个填充,因为 babel 本身只支持转换箭头函数、结构赋值这些语法糖类的语法,而一些新的 API 或者 Promise 函数等是无法转换的。@babel/polyfill 就是解决这个问题的。
  • babel-loader: webpack 的加载器,用于调用 @babel/core 的核心 API 来完成编译。

1、@babel/core 与 babel-core 区别

@babel/core 是 babel 7 过后的版本标识,babel-core 是以前版本的标识。


2、.babelrc 和 babel.config.js

.babelrc 和 babel.config.js 均是 babel 的配置文件,babel.config.js 是 bebel 7 引入的新的方式。


3、@babel/polyfill 和@babel/plugin-transform-runtime 和@babel/runtime 和@babel/runtime-corejs2(都是用来转换新 Api 的)

(1)@babel/polyfill:babel-polyfill 则是通过改写全局 prototype 的方式实现,比较适合单独运行的项目。开启 babel-polyfill 的方式,可以直接在代码中 require,或者在 webpack 的 entry 中添加,也可以在 babel 的 env 中设置 useBuildins 为 true 来开启,但是 babel-polyfill 会有近 100K,打包后代码冗余量比较大,对于现代的浏览器,有些不需要 polyfill,造成流量浪费污染了全局对象。

//使用方法一在 entry 中添加
module.exports = {
    entry: {
        main: ["@babel/polyfill", path.resolve(__dirname, "./src/index.js")]
    }
}
//使用方式二 babel.config.js 中设置
module.exports = {
    presets: [
        "@babel/preset-env", { "useBuiltIns": true }
    ]
}

(2)@babel/runtime 和@babel/plugin-transform-runtime:babel-runtime 和 babel-plugin-transform-runtime 的区别是,相当一前者是手动挡而后者是自动挡,每当要转译一个 api 时都要手动加上 require('babel-runtime'),而 babel-plugin-transform-runtime 会由工具自动添加,主要的功能是为 api 提供沙箱的垫片方案,不会污染全局的 api,因此适合用在第三方的开发产品中。@babel/runtime 和@babel/plugin-transform-runtime 都要结合到使用


(3)@babel/runtime-corejs2:plugin-transform-runtime 可以设置成 false 或者 2,在 babel.config.js 的配置文件下有以下代码:

module.exports = {
    preset: ["@babel/preset-env"],
    plugins: [
        "@babel/plugin-transform-runtime", { corejs: 2 }
    ]
}

1、corejs 是一个给低版本的浏览器提供接口的库,如 Promise, map, set 等。在 babel 中你设置成 false 或者不设置,就是引入的是 corejs 中的库,而且在全局中引入,也就是说侵入了全局的变量。
2、如果你的全局有一个引入,不要让引入的库影响全局,那你就需要引把 corejs 设置成 2。
所以一旦你使用了 2 这个参数就必须引入@babel/runtime-corejs2
3、@babel/plugin-transform-runtime 是必须装的,如果 corejs 设置为 2 的话安装@babel/runtime-corejs2 来代替@babel/runtime,反正设置为 false 的话就需要@babel/runtime,根据你的设置来安装即可。
4、所以@babel/runtime 和@babel/runtime-corejs2 区别就是后面那个多了个 corejs2 的包。与@babel/runtime 区别,官网是这样描述的 Difference from @babel/runtime:This can be used instead of a polyfill for any non-instance methods. It will replace things like Promise or Symbol with the library functions in core-js


在 babel 7 下:

  • babel.config.js 是对整个项目(父子 package) 都生效的配置,但要注意 babel 的执行工作目录。
  • .babelrc 是对 待编译文件 生效的配置,子 package 若想加载.babelrc 是需要 babel 配置 babelrcRoots 才可以(父 package 自身的 babelrc 是默认可用的)。
  • 任何 package 中的 babelrc 寻找策略是: 只会向上寻找到本包的 package.json 那一级。
  • node_modules 下面的模块一般都是编译好的,请剔除掉对他们的编译。如有需要,可以把个例加到 babelrcRoots 中。

默认情况下.babelrc 不作用于子包,那么在 babel.config.js 下加入一下 babelrcRoots 来指定即可。

    module.exports = {
      babelrcRoots: ['.', './frontend', './backend'] // 允许这两个子 package 加载 babelrc 相对配置
    }

一文读懂 babel7 的配置文件加载逻辑
对 babel-transform-runtime,babel-polyfill 的一些理解
babel7 中 corejs 和 corejs2 的区别
babel preset env 配置
babel 学习笔记

二、Babel 配置入门指南

首先需要本机安装 node.js,使用 npm 包管理工具来初始化目录,本次操作学习都是在 Babel 7 上进行的,相较于 Babel 6 还是有一定区别,单独使用 Babel 7 需要 CLI 来完成,所以先安装脚手架和核心@babel/core。

npm install @babel/cli @babel/core

安装完成后,我们就可以使用 cli 提供的命令来转换我们的 JS 代码了,比如建立一个 test.js 的 JS 文件,里面包含 ES6 的内容:

let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

然后来一波命令:

npx babel test.js --watch --out-file test-transform.js
  • 注意:npx 是 npm 在 5.2.0 后附赠的东西,使用这个我们可以避免全局安装这些命令工具,具体大家可以百度。
  • 我的 test.js 是直接建在跟 package.json 同级目录下的。
    之后我们可以发现输出的 test-transform.js 没改变,没有转化,这是因为 babel 是基于插件来实现的,没配置插件肯定什么也不会干,所以需要配置,这里就不介绍单独手动引入插件的方式了。
// 转换前
let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

// 转换后
let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

不想每次需要什么插件都去手动引入插件,所以我们就需要祭出@babel/preset-env,先安装这个工具。

npm intasll @babel/preset-env

然后在根目录建一个 babel.config.js 来配置 babel,在文件中加入以下内容:

module.exports = {
	presets: [
		["@babel/preset-env"]
	]
}

之后我们再运行一次上面的转换命令,可以得到以下代码:

"use strict";

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);

这样 ES6 新的语法糖就转换成 ES5 了。

当然,babel.config.js 里面还可以指定目标,当满足什么样的条件才去转换语法,不指定 targets 的情况下,默认是把所有的 ES6+都转换成 ES5,比如下面的示例:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8"
    }]
  ]
}

这种只有当在大于 ie 8 以上的浏览器不支持的语法才会转换。

@babel/polyfill

babel 只能转换一般的语法糖,不能转换新的 API,所以就只能祭出 polyfill 来弥补。首先安装下 polyfill,然后引入就可以了。

npm install @babel/polyfill

然后在 JS 文件里面引入

import "@babel/polyfill";

对于使用 webpack 的同学,可以直接在 main 入口直接引入,打包过后直接可以使用,如下:

entry: {
  main: ["@babel/polyfill", path.resolve(__dirname, "../main.js")]
}

注意:这个地方我只是举个栗子,实际上你这样单独操作出来的 JS 再直接引入某个 HTML 文件里面其实是没有用的,要结合到打包工具一起使用才行,比如 Webpack。

@babel/preset-env 加强

上面的引入方法是完全引入,导致包非常大,我们可以按需引入,这里又要配置@babel/preset-env,修改 babel.config.js 代码如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBulitIns": "entry"
    }]
  ]
}

然后运行一下会看到这些代码:

"use strict";

require("core-js/modules/es6.array.copy-within");

require("core-js/modules/es6.array.every");

require("core-js/modules/es6.array.fill");
..........
require("core-js/modules/web.timers");

require("core-js/modules/web.immediate");

require("core-js/modules/web.dom.iterable");

require("regenerator-runtime/runtime");

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

它会一股脑的把所有的包全部引进来,这样嘿不科学,所以 usage 参数可以做到按需引入,它只会引入相关的包,没使用的 ES6+API 不会引入相关的包,修改 babel.config.js 代码:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBuiltIns": "usage"
    }]
  ]
}

转换过后的代码如下:

"use strict";

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

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

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

这样,我们可以看到,我的代码使用了 promise,它就只引入 promise 相关的代码。

  • 注意:这儿使用 usage 参数后,JS 文件里面就不要单独写 import "@babel/polyfill" 这句话了,多此一举。

到这儿,问题来了,在使用 usage 参数后使用 babel 的 cli 命令工具(也就是这个:npx babel test.js --watch --out-file test-done.js)会给一段提示,告诉我们要指定 core-js@2 或者 core-js@3,从 babel 7.4 过后官方就建议这么做了,就是让我们放弃@babel/polyfill,我先卸载掉@babel/polyfill 试一试,发现还是能转换,不科学,然后查资料和看 preset-env 文件夹下才发现,人家自带了 polyfill,所以这儿如果有了@babel/preset-env 不需要单独安装@babel/polyfill 了,前提应该是使用了@babel/preset-env 配置了 babel 才行(像前面的完全引入 polyfill 还是需要单独安装@babel/polyfill),(注意:这儿我试了使用 webpack 结合 babel-loader 在不单独安装@babel/polyfill 或者 core-js 这些打包会出问题,"useBuiltIns": "usage"时提示找不到包,使用 entry 参数打包不报错,直接不会导入包,所以环境不一样不能一概而论)。 前面有个要我们指定 corejs 的提示,也只需要在 babel.config.js 里面指定一下就可以了,这儿我的@babel/perset-env 版本是 7.6.3,不知道其它版本有没有集成 polyfill,babel.config.js 代码如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBuiltIns": "usage",
      "corejs": 2
    }]
  ]
}

这儿我测试了下指定 corejs 为 2 和 3 时引入的代码不一样,暂时没了解到原因-_-。

@babel/runtime、 @babel/plugin-transform-runtime

接下来走一波不污染全局的配置方式,前面的配置都是要污染全局,还是不怎么科学,关于这种方式的优点和缺点大家上网查一下就可以了,接下来先安装好包:

npm install @babel/plugin-transform-runtime -D

上面这个安装到 dev 依赖就可以了,不用于生产环境

npm install @babel/runtime

接下来我们配置一下 babel.config.js,如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
    }]
  ],
  plugins: [
    ["@babel/plugin-transform-runtime"]
  ]
}

然后输出一下

"use strict";

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

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

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

var MM = function MM() {  // 这个是我转换前用 class 定义的类 class MM {constructor(){}} 这种
  (0, _classCallCheck2["default"])(this, MM);
};

发现输出的代码里面没得 polyfill 了。如果需要 polyfill 的话就要单独安装以下内容:

npm install @babel/runtime-corejs2

这里安装这个包之前把我@babel/runtime 给卸载了,安装完成后再重新配置一下 babel.config.js,如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
    }]
  ],
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "corejs": 2
    }]
  ]
}

然后运行一下,看结果。相比前面的@babel/runtime,这里的 polyfill 回来了,多了一句这个

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

参考资料

babel-runtime VS babel-polyfill
结合 Babel 7.4.0 谈一下 Babel-runtime 和 Babel-polyfill

三、结合到 webpack 来使用

结合 webpack 的话,还需要以下几个东西:

npm install webpack -D
npm install webpack-cli -D
npm install babel-loader -D

babel-loader 是加载器,要结合 webpack 来使用的话,这个必须要,同时,在项目根目录建立一个 webpack.config.js 配置文件(这里 webpack 的知识就不介绍了,详情参考 官网 )。

还是借助@babel/preset-env

首先我还是先测试了参数为 entry 的情况,配置文件内容如下(注意:这儿我还安装了@babel/polyfill):

// index.js,是我要转换的文件
import '@babel/polyfill';

let a = `hello, can you hear me!`;

let func = () => {
    console.log('这是箭头函数');
}

let b = [1, 2, 3];
let c = [...b];

let array = [1];
 
 
console.log(array);

let promise_t = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(233);
    }, 2000)
});

promise_t.then((res) => {
    console.log(res);
})

class Test {
    constructor(name) {
        this.name = name;
    }

    print() {
        console.log(this.name);
    }
}

let t_t = new Test('xiaohong');
t_t.print();

然后是 webpack.config.js 文件

const path = require('path');

module.exports = {
    // 指定模式(这个指定可以用来区分开发模式和生产模式,可选值有:'devvlopment','production','none')
    mode: 'none',
    // 入口
    entry: {
        main: path.resolve(__dirname, './src/index.js')
    },

    // 出口
    output: {
        // 出口文件
        path: path.resolve(__dirname, './dist'),
        // 文件名
        filename: './[name].js',
    },

    // 加载器
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader'
                    }
                ]
            }
        ]
    }
}

之后再是 babel.config.js 文件

module.exports = {
    presets: [
        ['@babel/preset-env', {
            'useBuiltIns': 'entry',
        }]
    ]
}

这儿我发现,即使我的 babel.config.js 不配置 'useBuiltIns': 'entry', 这句,打包出来的 main.js 虽然内容有差别,但是还是能运行成功,IE11 亲测可用上面 js 的 promise 语法。暂时我还没把 useBuiltIns 这个属性搞的特别清楚,只是知道在使用 usage 属性值时可以按需加载 polyfill。

接下来使用 'useBuiltIns': 'usage' 来试一试,我发现在保持其他文件不变的情况下,(这儿有个问题是,使用 usage 时在 index.js 中还是要写这句 import '@babel/polyfill',不然找不到包,这与我上面单独使用 babel 来转换时说不在单独引入 polyfill 有点出入,还没怎么明白,后期我会补充)。然后我比较了两种方式生成的 main.js 的代码,使用 entry 属性值时,main.js 代码八千多行,大小大概 250kb,使用 usage 参数时,代码一千多行,大小大概 37kb,所以按需引入效果还是明显。而且亲测在 IE11 上两种方式生成的代码均可正常运行。

使用 core-js 代替 @babel/polyfill

官方网站上面说从 babel 7.4.0 后开始放弃 babel/polyfill,转而使用 core-js。这儿我们又来试一试 core-js
首先卸载@babel/polyfill,然后执行以下命令安装 core-js@2:

npm install core-js@2 -S

这儿还有个 core-js@3 的版本,暂时没有用过,不知道区别。安装完成后修改一下 babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env', {
            'useBuiltIns': 'usage',
            'corejs': 2
        }]
    ]
}

然后你的 index.js 的上面去掉 import '@babel/polyfill' ,这时执行过后的 main.js 能完美运行在 IE11 上,而且这儿也不需要手动引入 core-js@2,使用 usage 属性值时自动按需引入,比较稳。如果我不写 'corejs': 2 这个参数,转换也可以成功,但是会报 warning,叫我去安装 core-js 并制定版本,问题不大,还是写上,毕竟官方要求。

然后如果我直接把 useBuiltIns 的参数值改为 entry,发现 class 这些 es6 语法会转换,但是 Promise 的 polyfill 没有引入,看来没 usage 参数好使,需要手动引入才行,我在 index.js 手动引入 import 'core-js',然后执行 webpack 但是却报错,提示 Module not found: Error: Can't resolve 'regenerator-runtime/runtime' ,这个就尴尬了,还要安装 regenerator-runtime/runtime,但是使用 usage 却没有出现这个问题,暂时无解,这儿我单独安装一次:

npm install regenerator-runtime -D

这儿我单独安装一次 regenerator-runtime 就不会报错了,暂时没找到原因,试着在安装了 regenerator-runtime 的情况下使用 usage 参数值,同时去除 index.js 里面的 import 'core-js'这句话,不然会报警告。内容像这样:When setting useBuiltIns: 'usage' , polyfills are automatically imported when needed.
Please remove the import '@babel/polyfill' call or use useBuiltIns: 'entry' instead.
。我试了下,使用 webpack 打包没得问题,可以运行。这儿建议把 regenerator-runtime 安装一次,避免不必要的麻烦。

这里我总结下吧(这儿总结的地方我都没有显示的指定 corejs 参数,也就是上面代码的‘corejs’: 2 这句话)

1、不安装 core-js 和 regenerator-runtime,不论你的@babel/preset-env 的 useBuiltIns 设置为哪个参数值都不能打包成功, usage 参数值提示找不到 core-js 的包, entry 直接不会引入相关的东西,因为包都没有,会提示你去下载 corejs。 false 效果跟 entry 一样。

2、只安装了 core-js(这儿安装的 core-js@2 版本), usage 参数可以打包成功,但是提示指定 core-js 版本,这个正常,因为我没写那句 'corejs: 2' 了,测试在 IE11 上正常运行。 entry 参数需要手动在 index.js 中引入 core-js,打包提示 Can't resolve 'regenerator-runtime/runtime' ,打包失败, false 参数打包成功,但是文件巨大,代码九千多行-_-,IE11 运行成功。

3、安装 core-js 和 regenerator-runtime, usage 参数可以打包成功,但是提示指定 core-js 版本,这个正常,因为我没写那句 'corejs: 2' 了,测试在 IE11 上正常运行。 entry 参数可以打包成功,但是提示指定 core-js 版本,这个正常,因为我没写那句 'corejs: 2' 了,测试在 IE11 上正常运行,打包过后文件巨大,我安装了 regenerator-runtime,没有在 index.js 里面手动引入,只引入了 core-js,这次打包没报上面找不到包的错误。 false 参数可以打包成功,但是提示指定 core-js 版本,这个正常,因为我没写那句 'corejs: 2' 了,测试在 IE11 上正常运行,打包过后文件巨大。


所以我得出一个配置结果,在 babel.config.js 里面这样写:

module.exports = {
   presets: [
       ['@babel/preset-env', {
           'useBuiltIns': 'usage',
           'corejs': 2
       }]
   ]
}

package.json 里面包含这些包:

{
  "name": "webpack-babel",
  "version": "1.0.0",
  "description": "practice",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js"
  },
  "author": "lee",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.7.0",
    "@babel/preset-env": "^7.7.1",
    "babel-loader": "^8.0.6",
    "regenerator-runtime": "^0.13.3",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.9"
  },
  "dependencies": {
    "core-js": "^2.6.10"
  }
}

core-js 和 regenerator-runtime(这个为了避免 entry 参数时包错找不到包)都安装起。这样就可以了。

使用@babel/plugin-transform-runtime 和@babel/runtime-corejs2 来实现 API 的 polyfill

前面使用了@babel/preset-env 的 useBuiltIns 来实现 API 填充,这儿试一试另一种方案,首先卸载掉 core-js 和 regenerator-runtime,然后安装一下两个包

npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs2 -S

安装完成后配置一下 babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env']
    ],
    plugins: [
        ['@babel/plugin-transform-runtime', {
            'corejs': 2
        }]
    ]
}

注意:这儿我是安装的 @babel/runtime-corejs2,所以 'corejs': 2 ​这个参数不能少,否者会报 Can't resolve '@babel/runtime/helpers/createClass' ​这种错误,上面这种方式不会污染全局,适合第三方开发。

四、总结

暂时 babel 就总结到这儿了,顺便熟悉了一下 webpack,如有不准确的地方还请各位多多指正,文章里面还有几个没理解清楚的地方,后期再琢磨琢磨!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

树深时见影

暂无简介

文章
评论
28 人气
更多

推荐作者

mb_XvqQsWhl

文章 0 评论 0

我不在是我

文章 0 评论 0

依 靠

文章 0 评论 0

L.W.

文章 0 评论 0

暗里之光

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文