-
Notifications
You must be signed in to change notification settings - Fork 0
Description
🐬 Babel编译转码的范围
Babel默认只转换新的JavaScript语法,而不转换新的API。 例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。 如果想使用这些新的对象和方法,则需要为当前环境提供一个垫片(polyfill)。
🐟 Babel的工作流:
输入字符串 -> @babel/parser parser -> AST -> transformer[s] -> AST -> @babel/generator -> 输出字符串
其中在 transformer[s] 阶段,就是使用plugins来转换代码的阶段。不同的plugin转换特定的代码,而preset是一组完成特定转换的plugins,比如babel-preset-es2017即包含syntax-trailing-function-commas | transform-async-to-generator两个plugin,用于支持ES2017的新特性。
plugin 分为 transform plugin(实际转换代码) 和 syntax-plugin(语法支持,即parse阶段),一般transform plugin包含对应的syntax plugin
以前常用的yearly presets(preset-es2015 | preset-es2016 | preset-es2017)在 Babel 7 中已经不推荐使用了,建议用 preset-env 代替。
🍥 主要的测试方法
// npm install @babel/core @babel/cli @babel/preset-env -D // npm install @babel/polyfill -S // npm install @babel/runtime-corejs2 -D // 文件1:polyfill.js // import 'babel-polyfill' console.log([1, 2, 3].includes(2)) console.log(Object.assign({}, {a: 1})) console.log(Array.from([1,2,3])) //文件2: babel.config.js const presets = [ ["@babel/env", { targets: { node: '0.10.8', // node: 'current' }, useBuiltIns: 'usage' }] ]; const plugins = [ ["@babel/plugin-transform-runtime", { "corejs": 2, "helpers": true, "regenerator": true, "useESModules": false }] ] module.exports = { presets, plugins };
// npx babel ./polyfill.js -d dist 得到 "use strict"; var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault"); var _from = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/from")); var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign")); // import 'babel-polyfill' console.log([1, 2, 3].includes(2)); console.log((0, _assign.default)({}, { a: 1 })); console.log((0, _from.default)([1, 2, 3])); // node dist/polifill.js 得到 true { a: 1 } [ 1, 2, 3 ]
🐠 babel编译代码的几种方式
- 直接执行es6代码
# babel-node 默认是加载了 babel-polyfill 的,所以各种新的API 都能用。 npm i -g babel-cli #babel7的cli安装方式 npm install --save-dev @babel/core @babel/cli babel-node es6.js
- babel-register
Node中另一种直接执行ES6代码的方式是使用 babel-register,该库引入后会重写你的require加载器,让你的Node代码中require模块时自动进行ES6转码。例如在你的 index.js 中使用 babel-register:
// index.js require('babel-register') ... require('./abc.js') // abc.js可以用ES6语法编写,require时会自动使用babel编译
- babel命令
# 编译 example.js 输出到 compiled.js babel example.js -o compiled.js # 或 整个目录转码 # --out-dir 或 -d 参数指定输出目录 $ babel src --out-dir lib # 或者 $ babel src -d lib # -s 参数生成source map文件 $ babel src -d lib -s
- API调用babel实现源码编译
npm install babel-core --save-dev // 老版本的babel npm install @babel/core --save-dev // babel7 var babel = require("@babel/core"); import { transform } from "@babel/core"; import * as babel from "@babel/core"; babel.transform("code();", options, function(err, result) { result.code; result.map; result.ast; });
- 通过babel-loader调用babel
babel-loader 是无法独立存在运行的。在babel-loader的package.json里你会发现有个 peerDependencies,其中就定义了一个依赖项是webpack。peerDependencies依赖表示了一个模块所依赖的宿主运行环境(一般各种插件的包内会使用 peerDependencies 来表明自己的宿主依赖)。
{ module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: ['es2015'] } } ] } ] } }
🐡 @babel/polyfill
babel-polyfill通过向全局对象和内置对象的prototype上添加方法来模拟完整的 ES2015+ 环境。
- @babel/polyfill主要用于应用程序(如浏览器,babel-node), 而不是库/工具, 并且使用babel-node时,这个polyfill会自动加载。
- @babel/polyfill包括 core-js2和regenerator runtime模块。
- @babel/polyfill是一次性引入你的项目中的,并且同项目代码一起编译到生产环境。
- 问题1: @babel/polyfill包含所有补丁,不管浏览器是否支持,也不管你的项目是否有用到,都全量引了, 在浏览器中,这些代码体积比较大。
- 问题2: 会污染全局变量。像Map,Array.prototype.find这些就存在于全局空间中。
- 新版本@babel/polyfill 加入了core-js3支持。
shim、polyfill所谓的垫片技术,是通过提前加载一个脚本,给宿主环境添加一些额外的功能。从而让宿主拥有更多的能力。例如可以基于JavaScript的原型能力,给Array.prototype增加额外的方法,就可以一定程度上让宿主环境拥有ES6的能力。除了对ES6+之外,我们还得根据项目情况,添加一些额外的shim或者polyfill。比如fetch、requestAnimationFrame 这种浏览器API,如果我们需要兼容IE8,还需要添加 ES5 shim来兼容更早的JS语法。
🦈 babel-runtime
babel转译代码的时候需要一些工具方法,这些方法默认都会加到用到他们的文件的开头,有时这些方法会很长。babel-runtime就是为了优化这种情况的,将这些工具方法抽离出来,不用每个文件都引入。
- babel-runtime可以看作是一种库/工具,搭配babel-plugin-transform-runtime,一般在开发第三方类库时使用。
- babel-runtime不会污染全局空间和内置对象原型。
- 多次使用只会打包一次。
- 弥补了babel-polyfill的缺点,达到了按需加载的效果。
- 一般在开发第三方类库或工具时使用,一般放在'devDependencies'里。
- babel-runtime的问题: 不能转码实例方法。
// 这只能通过 babel-polyfill来转译,因为 babel-polyfill 是直接在原型链上增加方法。
'!!!'.repeat(3);
'hello'.includes('h');
transform-runtime开启corejs的方案, preset-env的useBuiltIns设置为"usage", preset-env的useBuiltIns可以按需在全局进行polyfill,会require相应实例方法的垫片。
babel-plugin-transform-runtime的作用是分析我们的 ast,通过映射关系插入 babel-rumtime 中的垫片, runtime 编译器插件做了以下三件事:
- 当你使用 generators/async 函数时,自动引入 babel-runtime/regenerator 。
- 自动引入 babel-runtime/core-js 并映射 ES6 静态方法和内置插件。
- 移除内联的 Babel helper 并使用模块 babel-runtime/helpers 代替。
console.log({ ...a }); // 编译后是: function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } console.log(_objectSpread({}, a)); // 然后我们会有很多文件,每个文件都引入一遍 helper 方法,会有很多冗余。所以我们通常会使用 @babel/plugin-transform-runtime 来复用这些 helper 方法。 // 在 .babelrc 里配置: { "plugins": [ "@babel/transform-runtime" ] } // 编译后是: var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread")); console.log((0, _objectSpread2.default)({}, a));
🦐 补丁方案
// .babelrc的配置说明: { "presets": [ // 预设, Babel 插件的组合套餐 // 插件的执行顺序: // 插件在 Presets 前运行。 // 插件顺序从前往后排列。 // Preset 顺序是颠倒的(从后往前) "@babel/preset-react", // 支持react特有的一些语法,如jsx等 ["@babel/preset-env", { "modules": false, //该参数的含义是:启用将ES6模块语法转换为另一种模块类型/模块的导入导出方式。将该设置为false就不会转换模块。默认为 auto, 基本会是'commonjs'。 //该值可以有如下:'amd' | 'umd' | 'systemjs' | 'commonjs' | auto | false //设置为false的原因: 以前我们需要使用babel来将ES6的模块语法转换为AMD, CommonJS,UMD之类的模块化标准语法,但是现在webpack都帮我做了这件事了,所以我们不需要babel来做,因此需要在babel配置项中设置modules为false,这里导成其他的模块类型也没问题,因为webpack的模块支持这些方式。 "targets": { // tagets 指支持的运行环境 // 运行环境: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron // 如果不设置,就相当于声明了所有ES2015+的plugin "browsers": ["last 2 versions", "safari 7", "chrome": 49], // 支持每个浏览器最后两个版本和safari大于等于7版本所需的polyfill代码转换。 // 支持chrome49所需的polyfill代码转换。 "node": "current" // 如果通过Babel编译Node.js代码的话,可以设置 "target.node" 是 'current', 含义是 支持的是当前运行版本的nodejs。 }, "loose": false, // 含义是:允许它们为这个 preset 的任何插件启用"loose" 转换, 默认为false。 // babel编译时,对class的属性采用赋值表达式,而不是Object.defineProperty(更简洁) // 默认情况下,当在 Babel 下使用模块(module)时,将导出(export)一个不可枚举的 __esModule 属性。 "useBuiltIns": "usage", // "usage" | "entry" | false, 默认是false // entry的含义是找到入口文件里引入的 @babel/polyfill,并替换为 targets 浏览器/环境需要的补丁列表。 // usage指按需引入。 // false指不动态添加polyfills,并且不转译 import "core-js" 和 import "@babel/polyfill",会引入全部的polyfills。 "corejs": 2, // 新版本的@babel/polyfill包含了core-js@2和core-js@3版本,所以需要声明版本,否则webpack运行时会报warning,此处使用core-js@2版本 "debug": false, // 是否在转码时打印debug信息 "include": [], // 总是启用的 plugins, 可以让babel加载指定名称的插件。 "exclude": [], // 强制不启用的 plugins,可以让babel去除指定名称的插件。 "forceAllTransforms": false, // 强制使用所有的plugins,用于只能支持ES5的uglify可以正确压缩代码 }] ], "plugins": [ [ "transform-runtime", { // enables the re-use of Babel's injected helper code to save on codesize. "corejs": false, // 默认false,或者数字:{ corejs: 2/3 },代表需要使用corejs的版本。 // 如果是false则使用@babel/runtime,如果是2则使用@babel/runtime-corejs2,除了runtime中的helpers,另外含有Promise,Symbol等。 "helpers": true, // 默认是true,是否替换helpers。 "polyfill": false, // v7无该属性 "regenerator": true, // 默认true,generator是否被转译成用regenerator runtime包装不污染全局作用域的代码。 "useBuiltIns": false, // v7无该属性 "useESModules": false, // 默认false,如果是true将不会用@babel/plugin-transform-modules-commonjs进行转译,这样会减小打包体积,因为不需要保持语义。 "absoluteRuntime": false }], [ "import", { "libraryName": "antd", "style": true } ] ] "env": { "development":{ "plugins": [ "dva-hmr" ] }, "production": {} } }
- useESModules:如果是true将不会用@babel/plugin-transform-modules-commonjs进行转译,这样会减小打包体积,因为不需要保持语义。
//useESModules: false exports.__esModule = true; exports.default = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; //useESModules: true export default function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
- useBuiltIns: 'usage':当每个文件里用到(需要polyfill的特性)时,在文件中添加特定的import语句。这可以保证每个polyfill的特性仅load一次。
/// input var a = new Promise(); // a.js var b = new Map(); // b.js /// output // a.js import "core-js/modules/es6.promise"; var a = new Promise(); // b.js import "core-js/modules/es6.map"; var b = new Map();
- useBuiltIns: 'entry':替换import "@babel/polyfill" / require("@babel/polyfill")语句为独立的(根据环境)需要引入的polyfill特性的import语句。
// input import "@babel/polyfill"; // output import "core-js/modules/es7.string.pad-start"; import "core-js/modules/es7.string.pad-end";
- useBuiltIns: false,babel不会帮你处理任何polyfill的事,你必须手动处理。
- 关于loose mode可参考Babel 6: loose模式。
方案一: corejs2, @babel/preset-env + useBuiltins: entry + targets
- babel-preset-env 能根据当前的运行环境,自动确定你需要的 plugins 和 polyfills。通过各个 es标准 feature 在不同浏览器以及 node 版本的支持情况,再去维护一个 feature 跟 plugins 之间的映射关系,最终确定需要的 plugins。
- 用于替代原来的babel-preset-es20xx/latest(babel-preset-es2015/babel-preset-es2016/babel-preset-es2017)
// 1, 安装的需要的全部依赖。 // npm insall babel-loader@8 @babel/core @babel/preset-env -D // npm install @babel/polyfill // 2, 在js代码第一行import '@babel/polyfill',或在webpack的入口entry中写入模块@babel/polyfill。 // 3, 配置文件.babelrc { "presets": [ [ "@babel/preset-env", { "modules": false, // 推荐 "useBuiltIns": "entry", // 推荐 "browsers": ["last 2 versions", "safari 7", "chrome": 49] "corejs": 2, // 新版本的@babel/polyfill包含了core-js@2和core-js@3版本,所以需要声明版本,否则webpack运行时会报warning,此处使用core-js@2版本 } ] ], "plugins": [] }
方案二: corejs3
// 1, 安装的需要的全部依赖。 // npm insall babel-loader@8 @babel/core @babel/preset-env -D // npm insall core-js regenerator-runtime // 2, 在js代码第一行,或者入口文件中引入相应的polyfill // import "core-js/stable" // import "regenerator-runtime/runtime" // 3, 配置文件.babelrc { "presets": [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "entry", "browsers": ["last 2 versions", "safari 7", "chrome": 49] // https://babeljs.io/docs/en/babel-preset-env#usebuiltins // https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md "corejs": { "version": 3, // 使用core-js@3 "proposals": true, }, "debug": false } ], "@babel/preset-react", "@babel/preset-flow" ], "plugins": [ "dva-hmr", // 动态导入, 异步加载语法编译插件 "@babel/plugin-syntax-dynamic-import", // 可以使用 export v from 'mod' "@babel/plugin-proposal-export-default-from", // 装饰器 [ "@babel/plugin-proposal-decorators", { "legacy": true } ], // 类属性, 实例属性 [ "@babel/plugin-proposal-class-properties", { "loose": true } ], // antd支持 [ "import", { "libraryName": "antd", "style": true } ] ] }
关于使用了@babel/preset-env后,是否还需要使用transform-runtime的问题
基本是不需要的,参考creeperyang/blog#25 里的评论。
🎏 bable7 理解
- core-js(v2)这个库有两个核心的文件夹,分别是 library 和 modules。@babel/runtime-corejs2 使用 library 这个文件夹,@babel/polyfill 使用 modules 这个文件夹。
library 使用 helper 的方式,局部实现某个 api,不会污染全局变量;modules 以污染全局变量的方法来实现 api;
- @babel/polyfill 和@babel/runtime-corejs2 都使用了 core-js(v2)这个库来进行 api 的处理。
- @babel/polyfill: 引用两个包
core-js和regenerator-runtime - @babel/runtime: 引用一个包
regenerator-runtime, 它的devDependencies引用@babel-helpers - @babel/runtime-corejs2: 引用两个包
core-js和regenerator-runtime, devDependencies引用@babel/helpers - regenerator-runtime 用来实现generator和async/await
- @babel-helpers 是babel需要的一些函数,默认是放在每个文件的头部的,现在将其抽出来放到一起供各个文件引用这些函数,可以减小代码体积。
- runtime-corejs2 是polyfill和runtime的并集,所以只需要引入@babel/runtime-corejs2就可以替代@babel/polyfill和@babel/runtime了,然后要配置一下
["@babel/plugin-transform-runtime", {"corejs": 2}]
🎣 如何为 Babel 创建插件
有用的资源
babel-polyfill VS babel-runtime VS babel-preset-env