# babel

Babel是一个 工具链 ,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript;包括:语法转换、源代码转换等;

在 7.0 版本中,对 Babel 的包做了一次大调整,统一改成域级包,将原先以“babel-”为前缀的包迁移到@babel 的命名空间,如@babel/core、@babel/cli 等。这种模块化的设计,既能区分是否为官方发布的,也能避免命名冲突。

域级包是在包的基础上增加命名空间的概念,其形式如下所示。在@符号和斜杠之间的所有内容(不能包含点号和下划线)就是命名空间。

@scope/package_name

# 单独命令行使用babel

babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。

如果我们希望在命令行尝试使用babel,需要安装如下库:

  • @babel/core:babel的核心代码,必须安装;
  • @babel/cli:可以让我们在命令行使用babel;如果在webpack中可以不使用
npm install @babel/cli @babel/core -D

使用babel来处理我们的源代码:

  • src:是源文件的目录;
  • --out-dir:指定要输出的文件夹dist;
npx babel src --out-dir dist //dist目录
npx babel src --out-file dist.js //dist文件
npx babel demo.js --out-file dist.js //dist文件

# babel插件

比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:

cnpm install @babel/plugin-transform-arrow-functions -D
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions

再增加一个转化作用域

npm install @babel/plugin-transform-block-scoping -D 
npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions

# babel预设

但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset)

cnpm i --save-dev babel-loader @babel/core @babel/preset-env
//也可以类似上面用逗号隔开进行多个预设

在命令行中使用:

npx babel src --out-dir dist --presets=@babel/preset-env

在webpack配置中配置规则:

rules: [
		{
			test: /\.js$/,
			exclude:/node_modules/,
			loader:"babel-loader",
			options:{
				presets:["@babel/preset-env"]
			}
		},		
]
  • 既可以使用options中配置单独的babel解析es6的插件,也可以使用presets预设的方法。如果预设的方法需要额外的操作,可以改成数组的形式。
  {
         test: /\.js$/,
         use: {
           loader: "babel-loader",
           options: {
             // plugins: [
             //   "@babel/plugin-transform-arrow-functions",
             //   "@babel/plugin-transform-block-scoping",
             // ]
             presets: [
               "@babel/preset-env"
             ]
			 //也可以是数组形式
			 // presets: [
			 //   ["@babel/preset-env",{
				   
			 //   }]
			 // ]
           }
         }
}

这种语法转换有局限性,因为低版本对于某些转换后的js还是不认识,如map,promise等

# Babel的配置文件

像之前一样,我们可以将babel的配置信息放到一个独立的文件中,babel给我们提供了两种配置文件的编写:

  • babel.config.json(或者.js,.cjs,.mjs)文件;

  • .babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件;

  • .babelrc.json:早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的;

  • babel.config.json(babel7):可以直接作用于Monorepos项目的子包,更加推荐;

抽离出babel,放到单独文件babel.config.js

//webpack中就不需要继续配置babel的具体操作
{
	test: /\.js$/,
	loader: "babel-loader"
}
module.exports = {
  presets: [
    "@babel/preset-env"
  ]
}

babel.config.js 这是 Babel 7 最新引入的配置文件,存在于根目录中(即 package.json文件所在的目录)。它不仅能以编程的方式声明全局生效的配置参数,还能利用 overrides 字段对不同的子目录进行针对性的配置,从而就能避免为相关目录创建一个.babelrc 文件了。在下面的示例中,为 test 目录单独配置了预设(presets)。

module.exports = {
 presets: [...],
 overrides: [{
  test: ["./test"],
  presets: [...]
 }]
};

当运行下面两条命令进行编译时,第一条读取的是最外层的预设,第二条读取的是overrides 中的预设。

npx babel src.js
npx babel ./test/src.js

.babelrc:babel.config.js 并不是.babelrc 的替代品,.babelrc 文件用于局部配置(代码如下所示),可放置于所有目录中。如果当前目录没有.babelrc 文件,那么就会往上查找直至找到为止。

{
 "presets": [...]
}

注意,如果.babelrc 的扩展名是“.js”(即.babelrc.js),那么在文件中可以通过 JavaScript配置参数。

在 package.json 文件中,可以声明一个 babel 字段,其值就是.babelrc文件中的配置参数,如下所示。

{
  "babel": {
  "presets": [...]
 }
}

注意,不能让.babelrc 和声明过 babel 字段的 package.json 处在相同的目录中。

# 配置函数或方法

前面 3 种都是用单独的文件来配置 Babel 的参数,其实还可以通过相关的函数或方法来达到相同的目的。在下面的示例中,引入 gulp-babel 包后就能通过得到的babel()函数来配置 Babel 的参数。

let babel = require("gulp-babel");
babel({
 presets: [...]
}); 

# babel支持Vue的jsx插件

npm install @vue/babel-plugin-jsx -D
module.exports = {
  presets: [
    "@babel/preset-env"
  ],
  plugins:[
	"@vue/babel-plugin-jsx" 
  ]
}

# polyfill

Polyfill是一个js库,主要抚平不同浏览器之间对js实现的差异。

cnpm i --save @babel/polyfill

在原先的业务代码前加上import '@babel/polyfill';不过在最新版本的webpack中只要配置了useBuiltIns:'usage', import '@babel/polyfill';会自动被引入

而且可以采用合适的方法来减少体积

{
			test: /\.js$/,
			exclude:/node_modules/,
			loader:"babel-loader",
			options:{
				presets:[["@babel/preset-env",
					{
						targets: {
						chrome: "67",//目标浏览器版本
						
					  },
					useBuiltIns:'usage',//使用了哪些ES6语法然后只转换用到的ES6
					}
				]]
			}
		},	

# 另外一种方法 不污染全局

cnpm install --save-dev @babel/plugin-transform-runtime
cnpm install --save @babel/runtime
cnpm install --save @babel/runtime-corejs2
//如果设置corejs为2时需要安装

# .babelrc

可把options里的配置单独抽出来放在.babelrc 中

{
			test: /\.js$/,
			exclude:/node_modules/,
			loader:"babel-loader",
			
		},	
{
	"plugins": [["@babel/plugin-transform-runtime",{
		 "corejs": 2,
		"helpers": true,
		"regenerator": true,
		"useESModules": false
	}]]
} 

# 降低babel编译后体积

Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

可以将这些辅助代码作为一个独立模块,来避免重复引入。

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

npm i @babel/plugin-transform-runtime -D
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");

// cpu核数
const threads = os.cpus().length;

module.exports = {
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            use: [
              {
                loader: "thread-loader", // 开启多进程
                options: {
                  workers: threads, // 数量
                },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                },
              },
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
      threads, // 开启多进程
    })
  ],
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是如果需要进行其他配置,就要重新写了
      new TerserPlugin({
        parallel: threads, // 开启多进程
      }),
    ]
  ],
  mode: "production",
  devtool: "source-map",
};

# core-js处理es6+ 代码兼容性处理

babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。

core-js 是专门用来做 ES6 以及以上 API 的 polyfill。

npm i core-js
// 会将未用到的es6+代码也会处理,这样会造成代码体积过大。
// 如果配置了splitChunks:all,那么会单独生成一个很大的文件
import "core-js";


// 按需加载,不过这样如果es6+内容过多,也是很不方便
//import "core-js/es/promise";

// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
  console.log("hello promise");
});
  • 自动按需引入

babel.config.js

module.exports = {
  // 智能预设:能够编译ES6语法
  presets: [
    [
      "@babel/preset-env",
      // 按需加载core-js的polyfill
      { useBuiltIns: "usage", corejs: { version: "3", proposals: true } },
    ],
  ],
};

# babel+corejs单独使用

不在webpack中使用,安装@babel/core,@babel/preset-env,core-js

import babel from "@babel/core"
import fs from 'node:fs'
import presetEnv from "@babel/preset-env"

const code = fs.readFileSync('./demo.js', 'utf-8') // 不加utf-8是字符串
// console.log(code)

// 同步转换 第二个参数是一个预设
babel.transformAsync(code, {
  presets: [[
    presetEnv,
    {
      useBuiltIns: 'usage', // 新特性会按需引入 垫片
      corejs: 3
    }
  ]]
}).then((res) => {
  console.log(res.code) // es6 => es5
})

需要处理的demojs

//demo.js
const x = [1, 2, 3]
const y = x.filter(item => item > 1)


const promise = new Promise()

package.json中type修改成module

{
  "devDependencies": {
    "@babel/core": "^7.24.5",
    "@babel/preset-env": "^7.24.5",
    "core-js": "^3.37.0"
  },
  "type": "module"
}

打印出的结果

"use strict";

require("core-js/modules/es.array.filter.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
var x = [1, 2, 3];
var y = x.filter(function (item) {
  return item > 1;
});
var promise = new Promise();

# babel对应的插件

  1. @babel/core
var babel = require("@babel/core");
// 编译代码
babel.transform(code, options, function(err, result) {
 console.log(result); // => { code, map, ast }
});
babel.transformSync(code, options) // => { code, map, ast }
babel.transformAsync(code, options) // => Promise<{ code, map, ast }>

如果要编译文件或 AST,那么也有类似的 3 个方法可供选择。除了能编译之外,利用这个包还能把代码解析成 AST(即传入一段代码,得到一个 AST)以便其他插件对其进行语法分析,可使用的方法有 parse()、parseSync()和 parseAsync()。

  1. @babel/cli

@babel/cli 是一个 Babel 内置的命令行工具,可通过命令来编译文件

假设有一个 src.js 文件,代码如下所示,用到了 ES6 中的箭头函数。

let fn = () => true;

在运行下面的命令后,可以看到输出和输入的代码相同,这是因为此时还未指定任何转译插件。

./node_modules/.bin/babel src.js

如果当前的 npm 版本是 5.2 以上,那么可将./node_modules/.bin 缩短为 npx,如下所示。

npx babel src.js

在 babel 中,有多个可用的参数,下面只列举其中的 4 个,其余参数的用法可参考官方文档。

npx babel src --out-dir dist
npx babel src.js --out-file dist.js
npx babel src.js --out-file dist.js --plugins=@babel/plugin-transform-classes,@babel/transformmodules-amd
npx babel src.js --out-file dist.js --presets=@babel/preset-env,@babel/flow

1)“--out-dir”参数可编译整个 src 目录下的文件并输出到 dist 目录中。

2)“--out-file”参数可编译 src.js 文件并输出到 dist.js 文件中。

3)“--plugins”参数可指定编译过程中要使用的插件,多个插件用逗号隔开。

4)“--presets”参数可指定编译过程中要使用的预设,多个预设用逗号隔开。

在 package.json 文件中,可以通过 scripts 字段声明脚本命令。如果想要简化 babel 命令,那么可以将它们迁移到 scripts 字段中,如下所示。

{
 "scripts": {
 "compile": "npx babel src.js --out-file dist.js"
 }
}

现在要编译目录,只要执行下面的这条 npm 命令即可。

npm run compile
最后更新: 12/4/2024, 10:48:10 AM