什么是Babel?
Babel 是一个 JavaScript 编译器,提供了JavaScript的编译过程,能够将源代码转换为目标代码。AST -> Transform -> Generate
官网 Babel · Babel
查看AST https://astexplorer.net/
Babel所有的包 @babel/traverse · Babel
Babel 是一个广泛使用的 JavaScript 编译器,它主要用于将现代的 JavaScript 代码转换成向后兼容的版本,以便可以在旧版浏览器中运行。Babel 的编译过程可以分为三个主要阶段:解析(Parse)、转换(Transform)和生成(Generate)。这三个阶段分别对应于上文提到的 AST -> Transform -> Generate。
-
解析 (Parse) - 生成抽象语法树 (AST) 在这个阶段,Babel 将源代码作为输入,并通过解析器将其转换为抽象语法树 (Abstract Syntax Tree, AST)。AST 是一种表示代码结构的树形数据结构,其中每个节点代表源代码中的一个构造,比如变量声明、函数调用等。这个阶段的主要工作是读取源代码并创建相应的 AST 结构,这样就可以以编程方式来理解和操作代码了。
-
转换 (Transform) 一旦有了 AST,Babel 就可以通过遍历这棵树来应用各种转换插件。这些插件能够修改 AST,从而实现从一种 JavaScript 版本到另一种版本的转换。例如,一个插件可能将 ES6 的箭头函数转换为 ES5 中的普通函数。转换阶段是 Babel 功能的核心部分,因为它允许开发者根据需要添加或移除特定的功能,或者执行其他形式的代码转换。Babel 使用访问者模式来遍历 AST,并在适当的地方插入或删除节点,或者改变节点属性。
-
生成 (Generate) 最后,在所有必要的转换完成后,Babel 需要将更新后的 AST 转换回实际的 JavaScript 代码。这个阶段被称为代码生成。在这个过程中,Babel 会遍历修改过的 AST 并输出新的源代码。生成的代码应该与原始代码具有相同的行为,但可能使用了不同的语言特性或语法结构。最终的结果就是转换后的代码,它可以被更广泛的环境所支持。
AST抽象语法树
核心功能
- 语法转换:将新版本的 JavaScript 语法转换为旧版本的语法
- Polyfill:通过引入额外的代码,使新功能在旧浏览器中可用如filter这些新特性在旧浏览器中无法使用需要处理
- JSX: 将JSX语法转换成普通的JavaScript语法
- 插件: 为Babel提供自定义功能
案例
案例1 es6转es5
语法转换:将新版本的 JavaScript 语法转换为旧版本的语法
npm install --save-dev @babel/core @babel/cli @babel/preset-env
//如何支持新特性例如 Object.assign Array.prototype.find 等
npm i core-js -D
目录结构:
test.js 测试用例
//语法
const a = (params = 2) => 1 + params;
const b = [1, 2, 3]
const c = [...b, 4, 5]
class Babel {}
new Babel()
//API
const x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter((x) => x % 2 === 0)
const y = Object.assign({}, { name: 1 })
index.js 核心转换代码
///记得设置package.json的type为module
import Babel from '@babel/core'
import presetEnv from '@babel/preset-env'
import fs from 'node:fs'
const file = fs.readFileSync('./test.js', 'utf8')
const result = Babel.transform(file, {//usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加//corejs 3 是corejs的版本presets: [// useBuiltIns参数有很多entry,usage等,corejs是版本// entry手动引入 usage按需引入[presetEnv, { useBuiltIns: "usage", corejs: 3 }]]
})
console.log(result.code)
转换之后的代码
"use strict";require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.symbol.to-primitive.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.date.to-primitive.js");
require("core-js/modules/es.number.constructor.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.filter.js");
require("core-js/modules/es.object.assign.js");
require("core-js/modules/es.object.to-string.js");
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
//语法
var a = function a() {var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;return 1 + params;
};
var b = [1, 2, 3];
var c = [].concat(b, [4, 5]);
var Babel = /*#__PURE__*/_createClass(function Babel() {_classCallCheck(this, Babel);
});
new Babel();
//API
var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(function (x) {return x % 2 === 0;
});
var y = Object.assign({}, {name: 1
});
案例2jsx代码转换react
npm i react react-dom -D
npm install @babel/preset-react -D //之前的babel预设也需要
测试用例 test.jsx
import react from 'react'
import { createRoot } from 'react-dom/client'const App = () => {return <div>是谁?????</div>
}createRoot(document.getElementById('root')).render(<App />)
index.js
//记得设置package.json的type为module
import Babel from '@babel/core'
import presetEnv from '@babel/preset-env'
import fs from 'node:fs'
import react from '@babel/preset-react' //支持jsx语法
// const file = fs.readFileSync('./test.js', 'utf8')
const file = fs.readFileSync('./test.jsx', 'utf8')
const result = Babel.transform(file, {//usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加//corejs 3 是corejs的版本presets: [// useBuiltIns参数有很多entry,usage等,corejs是版本// entry手动引入 usage按需引入[presetEnv, { useBuiltIns: "usage", corejs: 3 }],react]
})
console.log(result.code)
转换的结果
其实也就是调用了
React.createElement
去创建元素
"use strict";var _react = _interopRequireDefault(require("react"));
var _client = require("react-dom/client");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
var App = function App() {return /*#__PURE__*/React.createElement("div", null, "\u5C0F\u6EE1\u662F\u8C01\uFF1F\uFF1F\uFF1F\uFF1F\uFF1F");
};
(0, _client.createRoot)(document.getElementById('root')).render(/*#__PURE__*/React.createElement(App, null));
手写babel插件
import Babel from '@babel/core'
import fs from 'node:fs'
const file = fs.readFileSync('./test.js', 'utf8')
//babel会注入一个types对象里面包含了各种ast节点的方法
const transformFunction = ({ types: t }) => {return {name: 'babel-transform-function',//visitor 是一个对象,它包含了一组方法,这些方法对应于 AST 中的不同节点类型。每当 Babel 遇到某种类型的节点时,都会调用 visitor 中对应的方法。visitor: {//匹配 箭头函数 当然也可以匹配别的东西 这儿只是案例ArrowFunctionExpression(path) {const node = path.nodeconst arrowFunction = t.functionExpression(null, //node.id 是一个 Identifier 节点,表示函数名node.params, //node.params 是一个数组,表示函数的参数// BlockStatement 是 JavaScript 抽象语法树(AST)中的一种节点类型,表示一个由大括号 {} 包围的语句块。它是函数体、循环体、条件分支(如 if 语句)等代码块的基础结构t.blockStatement([t.returnStatement(node.body)]), //node.body 是函数的主体,通常是一个 BlockStatement 节点node.async //node.async 是一个布尔值,表示函数是否是异步的 (async 函数))path.replaceWith(arrowFunction) //替换当前节点}}}
}
const result = Babel.transform(file, {plugins: [transformFunction]
})
console.log(result.code)
很多插件的写法都是一样的,如vite rollup esbuild都是差不多的 除了webpack插件不同,
const test =(params) =>{
//都是返回一个对象,含各种配置信息
return {
//插件包名
//插件的生命周期
name: '',
visitor: {
}
}
}
创建基本的 Vite 插件
1.定义插件: 你需要定义一个对象或函数作为你的插件。这个对象可以包含多个钩子(hooks),这些钩子会在构建过程中被调用
// my-plugin.js
export default function myPlugin(options) {return {name: 'vite-plugin-my-plugin', // 插件名称,用于调试和日志记录// 在这里定义各种钩子config(config, env) {// 修改 Vite 配置console.log('config hook');return {...config,define: {__APP_VERSION__: JSON.stringify(process.env.npm_package_version)}};},configureServer(server) {// 自定义 dev serverconsole.log('configureServer hook');// 可以在这里添加中间件、监听事件等},transform(code, id) {// 转换代码if (id.endsWith('.js')) {console.log(`transforming ${id}`);code = code.replace(/console\.log/g, 'console.info');}return code;},buildStart() {// 构建开始时执行console.log('buildStart hook');},closeBundle() {// 打包结束时执行console.log('closeBundle hook');}};
}
2.在 Vite 配置中使用插件: 一旦你有了插件,你可以在 vite.config.js
中引入并使用它。
import { defineConfig } from 'vite';
import myPlugin from './my-plugin';export default defineConfig({plugins: [myPlugin({ /* 传递给插件的选项 */ })]
});
使用现有的 Vite 插件接口
Vite 提供了许多标准的插件钩子,比如 config
, configureServer
, transform
, buildStart
, closeBundle
等等。你可以根据需要实现相应的钩子来扩展 Vite 的功能。