企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] # Babel **[Babel](https://new.babeljs.io/) 主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。** 作用如下: * 语法转换 * 通过 Polyfill 方式在目标环境中添加缺失的特性(通过第三方 polyfill 模块,例如 core-js,实现) * 源码转换 (codemods) 换而言之就是 Babel 能让我们现在就使用新的语法,无需等待浏览器的支持。 为了命令行执行编译,你可以安装`@babel/cli`。不过一般都是靠 webpack 等工具进行自动编译。 babel 的功能都是通过 plugin 来完成的。每个 plugin 都有特定的代码处理的功能,只有将它配置到 babel 里面运行,才能对代码进行转换。 babel 目前有很多的 plugin,既有官方的,也有第三方的。 在加入 plugins 测试之前我们需要知道一些前置知识,babel 将 ECMAScript 2015+ 版本的代码分为了两种情况处理: * syntax(语法层面): let、const、class、arrow function等,这些需要在构建时进行转译,是指在**语法层面上的转译,babel 是可以直接转换**的。 * features(api 方法层面):Promise、Set、Map等`ES6+` 标准推出的新特性,babel 不能直接转换,这些是在全局或者 Object、Array 等的原型上新增的方法,它们可以由相应 ES5 的方式重新定义,即**需要为 Babel 做一些配置,也就是 polyfill 来实现**,polyfill 翻译成中文就是**垫片**的意思,用来垫平不同浏览器环境之前差异。 # 例子 ``` mkdir babel-demo npm init -y touch index.js #👇 index.js内容 npm i -D @babel/cli @babel/core npx babel index.js --out-file compiled.js # 编译成 compiled.js ``` `index.js`内容: ``` const fn = () => { console.log("wens"); }; const p = new Promise((resolve, reject) => { resolve("wens"); }); const list = [1, 2, 3, 4].map(item => item * 2); ``` # 基础配置 有三种方式对 babel 进行[配置](https://babeljs.io/docs/usage/babelrc/): 1. `.babelrc` 等专用配置文件。 2. `package.json` 中`babel`字段进行配置。 如果你同时使用了这两种方式,那么`package.json`中的配置将被忽略! *.babelrc*: ```json { "presets": [ [ "@babel/preset-env", { "debug": true, # 查看哪些 API 被 polyfill "useBuiltIns": "usage", "targets": { "ie": 11 } } ], "@babel/preset-typescript" // presets是自下而上执行的 ], "plugins": [ "@babel/proposal-class-properties", "@babel/proposal-object-rest-spread", [ "@babel/plugin-transform-runtime", { "corejs": { "version": 3, // 指定 runtime-corejs 的版本,目前有 2 3 两个版本 "proposals": true // 使用尚在 提议 阶段 ECMAScript 特性的 polyfill }, "helpers": true, "regenerator": true, "useESModules": false } ] ] } ``` *.browserlistrc*: ```json > 0.25% not dead ``` ## 配置项 ### `sourceType: 'unambiguous'` 由于 Babel 默认将文件视为 ES modules,因此通常这些 plugins/presets 将插入`import`语句。插入`import`语句会导致 Webpack 和其他工具将一个文件看作是 ES modules,从而破坏了一个正常的 CommonJS 文件,一般会出现: <b style="color:red">`Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'`</b> 所以最好不要混用`import`and`module.exports`。在默认的 ES module 中,你需要进行更改: ``` // Change this module.exports = foo; // To this export default foo; ``` 如果你确实不能更改文件为 ES module ,那么你需要设置: ~~~ "sourceType": "unambiguous" ~~~ > [打包时遇到Cannot assign to read only property 'exports' of object 'Object'问题的解决方法](https://blog.csdn.net/fjh1997/article/details/88544354) > [issues/3650#issuecomment-397830621](https://github.com/vercel/next.js/issues/3650#issuecomment-397830621) > [options#sourcetype](https://babeljs.io/docs/en/options#sourcetype) ### `comments: false` > [options#comments](https://babeljs.io/docs/en/options#comments) # @babel/core 包含了核心的转换逻辑,需要对应的plugins/preset才能发挥作用。 ## @babel/helpers 一系列工具,比如`class`语法的实现就是 helper 提供的。被`@babel/core`依赖。 # @babel/preset-env 官网:https://www.babeljs.cn/docs/babel-preset-env 顾名思义,preset 即**预设插件**,它可以将 ES6 转换为 ES5,包含了各种可能用到的转译工具。**之前的以年份为准的 preset (如:babel-preset-2015)已经废弃了,现在统一用这个总包**。 同时,babel 已经放弃开发 `stage-*` 包,以后的转译组件都只会放进 `@babel/preset-env`包里。 `@babel/preset-env` 对于插件的选择是基于某些开源项目的,比如[`browserslist`](https://github.com/browserslist/browserslist)、[`compat-table`](https://github.com/kangax/compat-table)以及[`electron-to-chromium`](https://github.com/Kilian/electron-to-chromium),比如常用`.browserslistrc`来设置我们预想满足的目标运行环境。 接下来说的是`@babel/preset-env`的重要配置。 ## **useBuiltIns** 从其名字来说是“使用内置”,“内置”的什么呢? 从官方看来是“**polyfills(垫片)**”,控制 `@babel/preset-env` 使用何种方式帮我们导入 **polyfill** 的核心,`corejs`是 Babel 使用的内置 polyfills 库。 它的取值可以是以下三种: ### `false` 默认值,不做任何 polyfil l处理。 ### `entry` 一种入口导入方式, 需要我们在`打包配置入口` 或者 *文件入口*写入 `import "core-js"` 这样一串代码, babel 就会替我们根据当前你所配置的目标浏览器(browserslist 配置)来引入所需要的 polyfill。其他多余引入的无论有没有用到,会全部引入进来,`entry`的覆盖面积全,打包体积自然就大。 我们先使用`entry`,除此之外我们还指定了 corejs 的版本,然后我们在文件顶部手动引入 polyfill 也就是 core-js: ``` import "core-js/stable"; import "regenerator-runtime/runtime"; const message = "hello world"; ... ``` 然后执行`npm run build`,会发现转换后的代码里面引入了所有polyfill,包括我们需要的`Promise`: ``` ... require("core-js/modules/es.object.to-string"); require("core-js/modules/es.object.values"); require("core-js/modules/es.promise"); ... ``` 这样,我们的代码就可以在低版本浏览器中使用了。 ### `usage` 即 **按需引用**,如果目标浏览器不支持需要的 feature,那么就引入 polyfill,不然的话就不引用。由于目前的打包工具越发智能,随着 tree shaking 的完善,这样可以最低限度引入 polyfill。但是对第三方依赖包无效,常用来在开发第三方库时使用,因为开发者无法控制库的浏览器运行环境。 ### `corejs` `corejs`只在`useBuiltIns`取值为`entry`或`usage`的时候有用,因为 Babel 内置的 polyfills 就是 `core-js`。 先安装 corejs 的版本,可以配置为`2`或`3`: ~~~shell npm i -S core-js@3 ~~~ ``` { "presets": [ [ "@babel/preset-env", { "targets": "> 5%", "useBuiltIns": "usage", "corejs": 3 } ] ] } ``` 绝大部分情况,推荐使用 `@babel/preset-env + useBuiltIns: 'usage'` 这种方式。 这种方式打包体积不大,但是如果我们排除`node_modules`目录,遇上没有经过转译的第三方包,就检测不到第三方包内部的 `'hello'.includes('h')`这种语法,这时候我们就会遇到 bug(这种情况可以使用,参考本文中的`@babel/plugin-transform-runtime`)。 # `core-js@2`和`core-js@3` **[core-js](https://github.com/zloirock/core-js) 是一个 JavaScript 的模块化标准库。包括 ECMAScript 到 2021 年的 polyfill。** `core-js@2`分支中不包含一些最新的实例方法特性,新特性都会添加到 `core-js@3`,现在 2 版本被废弃使用。例如:`core-js@2`不包含 `Array.prototype.flat()`。 由于`core-js@2`版本包的体积太大(~2M),并且有很多重复的文件被引用。`core-js@3`对包进行拆分,三个核心的包分别是: * [core-js](https://github.com/zloirock/core-js/tree/master/packages/core-js):定义全局的 polyfill(~500k, 40k minified and gzipped) * [core-js-pure](https://github.com/zloirock/core-js/tree/master/packages/core-js-pure):提供不污染全局环境的 polyfill,等价于 core-js@2/library(~440k) * [core-js-compat](https://github.com/zloirock/core-js/tree/master/packages/core-js-compat):包含了 core-js 模块和 API 必要的数据,通过 browserslist 来生成所需要的 core-js 模块的列表 对于`core-js@3`的入口文件,我们可以这样使用: ~~~js // polyfill all `core-js` features: import "core-js"; // polyfill only stable `core-js` features - ES and web standards: import "core-js/stable"; // polyfill only stable ES features: import "core-js/es"; // if you want to polyfill `Set`: // all `Set`-related features, with ES proposals: import "core-js/features/set"; // stable required for `Set` ES features and features from web standards // (DOM collections iterator in this case): import "core-js/stable/set"; // only stable ES features required for `Set`: import "core-js/es/set"; // the same without global namespace pollution: import Set from "core-js-pure/features/set"; import Set from "core-js-pure/stable/set"; import Set from "core-js-pure/es/set"; // if you want to polyfill just required methods: import "core-js/features/set/intersection"; import "core-js/stable/queue-microtask"; import "core-js/es/array/from"; ~~~ 我们还可以使用`core-js-compat`用来提供目标引擎所需要的core-js的模块信息: ~~~js const { list, // array of required modules targets, // object with targets for each module } = require('core-js-compat')({ targets: '> 2.5%', // browserslist query filter: 'es.', // optional filter - string-prefix, regexp or list of modules }); console.log(targets); ~~~ # babel 的 polyfill 包 ## `@babel/polyfill` **在 babel v7.4.0 版本中已经明确表示不推荐使用**,官方建议我们使用`core-js`来替代。 如果使用导入`@babel/polyfill`的方式的话,现在会报错了: > `@babel/polyfill` is deprecated. Please, use required parts of `core-js` and `regenerator-runtime/runtime` separately `@babel/polyfill`内部就是引用`core-js`、`regenerator-runtime`这两个包。从而完整的模拟`ES2015+`环境。 ----------- **通过向全局对象和内置对象的`prototype`上添加方法来实现**,比如目标运行环境中不支持 `Array.prototype.find`,引入polyfill,前端就可以放心的在代码里用 ES6 的语法来写; ----------- `@babel/polyfill`没有提供从`core-js@2`到`core-js@3`平滑升级路径:因为该原因,**决定弃用`@babel/polyfill`代之以分别引入需要的`core-js`和`regenerator-runtime`**。 `core-js@3`中等价替换`@babel/polyfill`是: ``` import "core-js/stable"; import "regenerator-runtime/runtime"; ``` 这里的`core-js`不一定要导入stable,具体视项目而定,点击[这里](https://github.com/zloirock/core-js#commonjs-api)可以查看更多的`core-js`模式。 ## `@babel/plugin-transform-runtime` 官网:[@babel/plugin-transform-runtime](https://github.com/babel/website/blob/main/docs/plugin-transform-runtime.md) 它将开发者依赖的全局内置对象等,抽取成单独的模块,并通过模块导入的方式引入,避免了对全局作用域的修改(污染)。 1. babel 在转码过程中,会加入很多 babe l自己的 helper 函数,这些 helper 函数,在每个文件里可能都会重复存在,transform-runtime 插件可以把这些重复的 helper 函数,转换成公共的、单独的依赖引入,从而节省转码后的文件大小。 2. transform-runtime 可以帮助这种项目创建一个沙盒环境,即使在代码里用到了新的 ES 特性,它能将这些特性对应的全局变量,转换为对 core-js 和 regenerator-runtime 非全局变量版本的引用。这其实也应该看作是一种给代码提供 polyfill 的方式。 Important to note here is that`@babel/preset-env`**respects your targets**, and doesn't include unnecessary pollyfills, * > **注意**:`@babel/preset-env`会识别`.browserslistrc`,不包含不必要的 polyfills;而`@babel/transform-runtime`不识别`.browserslistrc`,会包含所有可填充的特性,这会导致许多不必要的腻子填充。 ### 是2还是3 `@babel/plugin-transform-runtime` 就是一个工具库,默认使用`@babel/runtime` 提供的帮助函数(helpers)进行模块隔离(即`corejs: false`)。如果想启用`transform-runtime`对`core-js`的 polyfill 的话,就得使用`@babel/runtime`另外的两个版本: * core-js@2 对应的`@babel/runtime`版本是:`@babel/runtime-corejs2`; * core-js@3 对应的`@babel/runtime`版本是:`@babel/runtime-corejs3`。 | `corejs`option | Install command | | --- | --- | | `false` | `npm install --save @babel/runtime` | | `2` | `npm install --save @babel/runtime-corejs2` | | `3` | `npm install --save @babel/runtime-corejs3` | > [babel-plugin-transform-runtime#corejs](https://babeljs.io/docs/en/babel-plugin-transform-runtime#corejs) ### 使用 ``` npm i @babel/runtime-corejs3 npm i -D @babel/plugin-transform-runtime ``` `@babel/runtime-corejs3`是一个核心, 一种实现方式,在局部文件中以模块化引用的形式导入,不会污染全局变量;而 `@babel/plugin-transform-runtime` 负责更好的重复使用`@babel/runtime-corejs3`。两个包是一起使用的。 ``` // webpack.config.js module.exports = { presets: ["@babel/env"], plugins: [ [ "@babel/plugin-transform-runtime", { corejs: { version: 3 } // 指定 runtime-corejs 的版本,目前有 2 3 两个版本 } ] ] }; ``` 去掉了`@babel/env`的相关参数,而给`@babel/plugin-transform-runtime`添加了`corejs`参数,最终转换后的文件不会再出现 polyfill 的`require`的方法了。解决转译 api 层出现的**全局变量污染**。 主要是为了解决转换之后代码重复使用而造成的包体积较大的问题,因为 babel 在转换代码时会使用一些 helpers 辅助函数,比如下面的代码: ``` class Person { constructor(name) { this.name = name; } say() { console.log(this.name); } } ``` 转换之后,我们会发现生成的代码除了一些 polyfill 和实际的代码之外,还有一些 helpers 代码: ~~~ ... "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")); ... ~~~ 如果有很多文件需要转换,那这些代码可能就会重复,为了解决这个问题,我们可以使用`@babel/plugin-transform-runtime`将这些helpers辅助函数的使用方式改为引用的方式,让它们都去引用`@babel/runtime`包里的代码,这样他们就是重复引用同一个代码,避免了内容上的重复,以节省代码的冗余,从而减小了程序包的体积。其中`@babel/runtime`这个包里面就包含了所有的 helpers 辅助函数。 `useBuiltIns`的`entry` 和 `@babel/runtime` 不要同时使用,会产生各种帮助函数还引入了许多 polyfill,导致包体积增大! > [@babel/preset-env 与@babel/plugin-transform-runtime 使用及场景区别](https://segmentfault.com/a/1190000021188054) ## `@babel/runtime` Babel modular runtime helpers(Babel模块化运行时助手); 源码包含两个文件夹: * helpers(定义了一些处理新的语法关键字的帮助函数 * regenerator(`regenerator-runtime`包的一个版本)。 它只是包含模块化方式导出了一系列函数的包。 举例: ```js class Circle {} // ------------转译后------------------ function _classCallCheck(instance, Constructor) { //... } var Circle = function Circle() { _classCallCheck(this, Circle); }; ``` babel通常会对这类代码进行转译,`_classCallCheck`会被多次生成,这样就很不好,所有我们还需要借用`@babel/plugin-transform-runtime`变为从`@babel/runtime`导入的方式: ```js var _classCallCheck = require("@babel/runtime/helpers/classCallCheck"); var Circle = function Circle() { _classCallCheck(this, Circle); }; ``` ## `@babel/runtime-corejs2` [@babel/runtime-corejs2 · Babel 中文网](https://www.babeljs.cn/docs/babel-runtime-corejs2) ## `@babel/runtime-corejs3` 源码包含四个文件夹: * core-js(引用`core-js`这个包) * core-js-stable(引用`core-js`这个包) * helpers(定义了一些处理新的语法关键字的帮助函数) * regenerator(仅仅是引用`regenerator-runtime`这个包) —————————————————————————————— 可以看出,`@babel/runtime-corejs3 ≈ @babel/runtime + @babel/polyfill`: `@babel/runtime`只能处理语法关键字,而`@babel/runtime-corejs3`还能处理新的全局变量(例如,`Promise`)、新的原生方法(例如,`String.padStart` ); 使用了`@babel/runtime-corejs3`,就无需再使用`@babel/runtime`了。 因此,该插件可以代替 polyfill,将`Promise`或`Symbol`转换为引用`core-js`库里的函数,但不能对内置对象的实例方法进行转换。 ```js Promise // ------转换为:------ var _Promise = require("@babel/runtime-corejs3/core-js/promise.js"); ``` > [core-js@3, babel展望未来](https://juejin.im/post/5e355be0f265da3e491a53c5#heading-15) # @babel/preset-typescript 只做语法的转换,不做类型检查,因为类型检查的任务可以交给 IDE (或者用 `tsc`)去做。 # 小结 Babel 负责两件事: 1)语法转换,由各种 transform 插件、helpers 完成; 2)对于可*polyfill*的 API 的提供,由 `corejs` 实现。 已经在 `@babel/preset-env` 中配置了polyfill,那么你连 `@babel/plugin-transform-runtime` 都是不必要的(他们二者都可以提供ES 新 API 的垫片,在这一项功能上是重复的。 `@babel/preset-env` 除了提供 polyfill 垫片,还提供 ES 新语法的转译,这一点 `@babel/plugin-transform-runtime` 做不了;`@babel/preset-env` 提供的 polyfill 垫片会污染原型链,这个既是缺点,也是优点,缺点是在开发第三方 JS 库时不能这么干,会影响使用方的代码。 ## 情况选择 * 如果你是应用开发(一般我们都不是开发工具库,只是使用一些前端框架做业务~),推荐使用`@babel/preset-env`搭配 `useBuiltIns` 并按规则导入 polyfill 即可,而无需再使用`@babel/plugin-transform-runtime`,参考[issues]https://github.com/babel/babel/issues/10008"),,一劳永逸,不必使用 `@babel/plugin-transform-runtime`。 * 如果是框架/库开发,需要安装`@babel/runtime`、`@babel/plugin-transform-runtime`,同时使用 `@babel/preset-env` 去转译语法,但不用它的 polyfill(如前面推荐的那样去配置即可)。 # issues > [Using @babel/runtime-corejs2 and @babel/runtime-corejs3 leads to larger bundle sizes](https://github.com/babel/babel/issues/9853) > [regeneratorRuntime error with 'external' helpers](https://github.com/rollup/plugins/issues/356#issuecomment-626398978) # 参考 > [从零开始配置Babel](https://juejin.cn/post/6844904090833518600#heading-10) > [用了babel还需要polyfill吗??](https://juejin.cn/post/6845166891015602190) > [jamiebuilds/babel-handbook](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/user-handbook.md) > [Babel 7 下配置 TypeScript 支持](https://zhuanlan.zhihu.com/p/102250469) > [折腾 @babel/preset-env](https://blog.meathill.com/js/some-tips-of-babel-preset-env-config.html) > [babel详解](https://blog.liuyunzhuge.com/2019/09/02/babel%E8%AF%A6%E8%A7%A3%EF%BC%88%E5%9B%9B%EF%BC%89-core-js/)