乐闻世界logo
搜索文章和话题

面试题手册

Babel 如何与 Webpack、Vite、Rollup 等构建工具集成?

Babel 与主流构建工具集成1. Webpack + Babel安装依赖npm install --save-dev babel-loader @babel/core @babel/preset-env基础配置// webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { cacheDirectory: true, // 启用缓存 presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript' ] } } } ] }};高级配置// webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.(js|ts)x?$/, include: [ path.resolve(__dirname, 'src'), // 包含需要编译的第三方库 path.resolve(__dirname, 'node_modules/some-es6-lib') ], use: [ { loader: 'thread-loader', // 多线程 options: { workers: 2 } }, { loader: 'babel-loader', options: { cacheDirectory: true, cacheCompression: false } } ] } ] }};2. Vite + BabelVite 默认使用 esbuild,但可以通过插件使用 Babel。安装依赖npm install --save-dev vite-plugin-babel配置// vite.config.jsimport { defineConfig } from 'vite';import babel from 'vite-plugin-babel';export default defineConfig({ plugins: [ babel({ babelConfig: { babelrc: false, configFile: false, presets: ['@babel/preset-env'] } }) ]});使用 @vitejs/plugin-react// vite.config.jsimport { defineConfig } from 'vite';import react from '@vitejs/plugin-react';export default defineConfig({ plugins: [ react({ // 使用 Babel 替代默认的 esbuild babel: { plugins: [ ['@babel/plugin-proposal-decorators', { legacy: true }] ] } }) ]});3. Rollup + Babel安装依赖npm install --save-dev @rollup/plugin-babel基础配置// rollup.config.jsimport babel from '@rollup/plugin-babel';import resolve from '@rollup/plugin-node-resolve';export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'esm' }, plugins: [ resolve(), babel({ babelHelpers: 'bundled', // 或 'runtime' exclude: 'node_modules/**', presets: ['@babel/preset-env'] }) ]};库开发配置// rollup.config.jsimport babel from '@rollup/plugin-babel';export default { input: 'src/index.js', output: [ { file: 'dist/index.cjs.js', format: 'cjs' }, { file: 'dist/index.esm.js', format: 'esm' } ], plugins: [ babel({ babelHelpers: 'runtime', // 库开发推荐 plugins: ['@babel/plugin-transform-runtime'] }) ], external: [/@babel\/runtime/] // 不打包 runtime};4. Parcel + BabelParcel 内置 Babel 支持,自动识别 .babelrc 或 babel.config.js。// babel.config.jsmodule.exports = { presets: ['@babel/preset-env', '@babel/preset-react']};5. Gulp + Babel// gulpfile.jsconst gulp = require('gulp');const babel = require('gulp-babel');function transpile() { return gulp.src('src/**/*.js') .pipe(babel({ presets: ['@babel/preset-env'] })) .pipe(gulp.dest('dist'));}exports.default = transpile;各构建工具对比| 特性 | Webpack | Vite | Rollup | Parcel ||------|---------|------|--------|--------|| 默认转译器 | babel-loader | esbuild | @rollup/plugin-babel | 内置 Babel || 配置复杂度 | 高 | 低 | 中 | 极低 || 开发速度 | 中 | 极快 | 快 | 快 || 生产优化 | 强 | 强 | 强 | 中 || 适用场景 | 大型应用 | 现代应用 | 库开发 | 快速原型 |最佳实践1. 共享 Babel 配置// babel.config.js - 所有工具共享module.exports = { presets: ['@babel/preset-env'], env: { test: { presets: [['@babel/preset-env', { targets: { node: 'current' } }]] } }};2. 条件配置// babel.config.jsmodule.exports = (api) => { const isWebpack = api.caller((caller) => caller?.name === 'babel-loader'); const isTest = api.env('test'); return { presets: [ ['@babel/preset-env', { modules: isWebpack ? false : 'auto', targets: isTest ? { node: 'current' } : { browsers: ['> 1%'] } }] ] };};3. Monorepo 配置// babel.config.js (根目录)module.exports = { presets: ['@babel/preset-env'], overrides: [ { test: /packages\/app-a/, presets: ['@babel/preset-react'] }, { test: /packages\/app-b/, presets: ['@babel/preset-typescript'] } ]};常见问题1. Webpack 中 Babel 不生效// 确保正确配置 resolveresolve: { extensions: ['.js', '.jsx', '.ts', '.tsx']}2. Vite 中 JSX 转换问题// 使用 @vitejs/plugin-react 替代手动配置import react from '@vitejs/plugin-react';export default { plugins: [react()]};3. Rollup 库开发的 helpers 问题// 使用 runtime helpers 避免重复代码babel({ babelHelpers: 'runtime', plugins: ['@babel/plugin-transform-runtime']})
阅读 0·3月7日 19:42

如何优化 Babel 编译性能?有哪些最佳实践?

Babel 性能优化策略1. 精准配置目标环境避免过度编译,只转换必要的语法。// babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', { targets: { // 精确指定目标浏览器 browsers: ['last 2 Chrome versions', 'last 2 Firefox versions'] }, // 不转换 ES 模块,让 Webpack/Rollup 处理 modules: false }] ]};2. 缓存配置babel-loader 缓存// webpack.config.jsmodule.exports = { module: { rules: [{ test: /\.js$/, use: { loader: 'babel-loader', options: { cacheDirectory: true, // 启用缓存 cacheCompression: false, // 禁用压缩以提高速度 compact: process.env.NODE_ENV === 'production' // 生产环境才压缩 } } }] }};持久化缓存// babel.config.jsmodule.exports = { cache: true, // Babel 7.7+ 支持 presets: ['@babel/preset-env']};3. 排除不必要的文件// webpack.config.jsmodule.exports = { module: { rules: [{ test: /\.js$/, exclude: [ /node_modules/, // 排除 node_modules /\.min\.js$/ // 排除已压缩的文件 ], use: 'babel-loader' }] }};4. 按需加载 Polyfills// babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', // 按需引入 corejs: 3 }] ]};5. 并行编译// webpack.config.jsconst HappyPack = require('happypack');const os = require('os');module.exports = { module: { rules: [{ test: /\.js$/, use: 'happypack/loader?id=babel' }] }, plugins: [ new HappyPack({ id: 'babel', threads: os.cpus().length, loaders: ['babel-loader'] }) ]};6. 开发 vs 生产配置分离// babel.config.jsmodule.exports = { presets: ['@babel/preset-env'], env: { development: { // 开发环境:快速编译,不压缩 compact: false, minified: false }, production: { // 生产环境:优化输出 compact: true, minified: true, plugins: [ 'transform-remove-console', // 移除 console 'transform-remove-debugger' // 移除 debugger ] } }};高级优化技巧1. 使用 SWC 替代 Babel对于纯语法转换,可以考虑更快的 SWC。// webpack.config.js - 使用 swc-loadermodule.exports = { module: { rules: [{ test: /\.js$/, use: { loader: 'swc-loader', options: { jsc: { parser: { syntax: 'ecmascript', jsx: true }, target: 'es5' } } } }] }};2. 增量编译// webpack.config.jsmodule.exports = { watchOptions: { ignored: /node_modules/, aggregateTimeout: 300 // 防抖延迟 }};3. 按需编译// 使用 include 替代 excludemodule.exports = { module: { rules: [{ test: /\.js$/, include: [ path.resolve(__dirname, 'src'), // 只编译需要转换的第三方库 path.resolve(__dirname, 'node_modules/some-es6-lib') ], use: 'babel-loader' }] }};4. 配置解析优化// 使用 babel.config.js 而非 .babelrc// babel.config.js 支持配置缓存,性能更好module.exports = { // 使用函数形式,支持动态配置 presets: (api) => { api.cache(true); // 启用配置缓存 return [ ['@babel/preset-env', { targets: api.env('test') ? { node: 'current' } : { browsers: ['> 1%'] } }] ]; }};性能监控1. 测量编译时间# 使用环境变量测量BABEL_PROFILE=true npx babel src --out-dir dist# 使用 webpack 分析npx webpack --profile --json > stats.jsonnpx webpack-bundle-analyzer stats.json2. 分析插件耗时// 自定义性能监控插件const timerPlugin = () => { return { name: 'timer-plugin', visitor: { Program: { enter() { console.time('babel-transform'); }, exit() { console.timeEnd('babel-transform'); } } } };};最佳实践清单✅ 推荐做法使用 cacheDirectory - 启用 babel-loader 缓存精确配置 targets - 避免不必要的转换设置 modules: false - 让打包工具处理 ES 模块使用 include 替代 exclude - 白名单模式更安全分离开发和生产配置 - 开发环境追求速度按需引入 polyfills - 使用 useBuiltIns: 'usage'启用配置缓存 - api.cache(true)❌ 避免做法不要编译 node_modules 中的所有文件不要在开发环境启用压缩不要过度使用插件不要忽略缓存配置不要使用过时的 preset(如 es2015、es2016 等)性能对比示例| 优化项 | 编译时间 | 输出大小 ||--------|----------|----------|| 无优化 | 15s | 500KB || 启用缓存 | 3s | 500KB || 精确 targets | 8s | 350KB || 按需 polyfill | 8s | 280KB || 全部优化 | 2s | 280KB |
阅读 0·3月7日 19:42

Babel 中 preset 和 plugin 的区别是什么?如何配置?

Preset 和 Plugin 的区别Plugin(插件)粒度更细:每个插件只负责单一的转换功能功能单一:例如 @babel/plugin-transform-arrow-functions 只转换箭头函数按需使用:可以精确控制需要哪些转换Preset(预设)插件集合:是一组插件的集合,用于简化配置批量处理:一次配置,包含多个相关插件常见预设:@babel/preset-env:根据目标环境自动选择转换@babel/preset-react:React/JSX 转换@babel/preset-typescript:TypeScript 转换配置方式1. 使用 Plugin// babel.config.jsmodule.exports = { plugins: [ '@babel/plugin-transform-arrow-functions', '@babel/plugin-transform-classes', // 带参数的插件 ['@babel/plugin-transform-runtime', { corejs: 3 }] ]};2. 使用 Preset// babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', { targets: { browsers: ['> 1%', 'last 2 versions'] }, useBuiltIns: 'usage', corejs: 3 }], '@babel/preset-react' ]};3. Plugin 和 Preset 的执行顺序重要规则:Plugin 先于 Preset 执行Plugin 从前往后执行Preset 从后往前执行module.exports = { plugins: [ 'plugin-a', // 第1个执行 'plugin-b' // 第2个执行 ], presets: [ 'preset-b', // 第4个执行(preset 逆序) 'preset-a' // 第3个执行 ]};最佳实践1. 优先使用 @babel/preset-envmodule.exports = { presets: [ ['@babel/preset-env', { targets: '> 0.25%, not dead' }] ]};2. 开发自定义 Plugin// 简单的 Babel 插件示例module.exports = function(babel) { const { types: t } = babel; return { name: 'my-custom-plugin', visitor: { Identifier(path) { // 转换逻辑 } } };};3. 开发自定义 Preset// 自定义 presetmodule.exports = function() { return { plugins: [ 'plugin-a', 'plugin-b' ] };};常见面试追问为什么 preset 是逆序执行?为了符合大多数用户的直觉,通常将更具体的 preset 放在后面如何查看 Babel 实际使用了哪些插件?使用 DEBUG=* babel src 查看调试信息plugin 和 preset 可以混用吗?可以,且非常常见,preset 处理大部分转换,plugin 处理特殊需求
阅读 0·3月7日 19:38

什么是 Babel AST?如何编写一个自定义的 Babel 插件来操作 AST?

什么是 AST?AST(Abstract Syntax Tree,抽象语法树)是源代码的树状表示形式,它将代码结构化为节点层次结构,每个节点代表代码中的一个构造(如变量声明、函数调用等)。Babel AST 规范Babel 使用基于 ESTree 规范的 AST,并扩展了 JSX、TypeScript 等语法支持。AST 节点类型常见节点类型| 节点类型 | 说明 | 示例 || --------------------- | ----- | ---------------------- || Program | 程序根节点 | 整个文件 || Identifier | 标识符 | 变量名、函数名 || Literal | 字面量 | 1, "hello", true || VariableDeclaration | 变量声明 | const, let, var || FunctionDeclaration | 函数声明 | function foo() {} || CallExpression | 函数调用 | foo() || BinaryExpression | 二元表达式 | a + b || MemberExpression | 成员表达式 | obj.prop |AST 示例// 源代码const sum = (a, b) => a + b;// AST(简化版){ "type": "VariableDeclaration", "kind": "const", "declarations": [{ "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "sum" }, "init": { "type": "ArrowFunctionExpression", "params": [ { "type": "Identifier", "name": "a" }, { "type": "Identifier", "name": "b" } ], "body": { "type": "BinaryExpression", "operator": "+", "left": { "type": "Identifier", "name": "a" }, "right": { "type": "Identifier", "name": "b" } } } }]}编写自定义 Babel 插件1. 基础插件结构// my-plugin.jsmodule.exports = function(babel) { const { types: t } = babel; return { name: 'my-custom-plugin', visitor: { // 访问者方法 Identifier(path) { console.log('Found identifier:', path.node.name); } } };};2. 实用插件示例示例 1:替换 console.log// remove-console-plugin.jsmodule.exports = function(babel) { const { types: t } = babel; return { name: 'remove-console', visitor: { CallExpression(path) { const { callee } = path.node; // 检查是否是 console.log 调用 if ( t.isMemberExpression(callee) && t.isIdentifier(callee.object, { name: 'console' }) && t.isIdentifier(callee.property, { name: 'log' }) ) { // 移除该节点 path.remove(); } } } };};示例 2:自动添加函数名// add-function-name-plugin.jsmodule.exports = function(babel) { const { types: t } = babel; return { name: 'add-function-name', visitor: { FunctionDeclaration(path) { const { node } = path; // 如果函数没有名称,添加一个默认名称 if (!node.id) { node.id = t.identifier('anonymous'); } } } };};示例 3:国际化字符串提取// i18n-plugin.jsmodule.exports = function(babel) { const { types: t } = babel; const strings = []; return { name: 'i18n-extractor', visitor: { StringLiteral(path) { const { node } = path; // 收集所有字符串 strings.push(node.value); // 替换为国际化函数调用 path.replaceWith( t.callExpression( t.identifier('t'), [t.stringLiteral(node.value)] ) ); } }, post(state) { // 输出收集到的字符串 console.log('Extracted strings:', strings); } };};3. 使用插件// babel.config.jsmodule.exports = { plugins: [ './remove-console-plugin.js', ['./i18n-plugin.js', { /* 插件选项 */ }] ]};Path 对象详解Path 的核心方法// 访问者中的 path 对象visitor: { Identifier(path) { // 节点信息 console.log(path.node); // AST 节点 console.log(path.parent); // 父节点 console.log(path.parentPath); // 父路径 // 节点操作 path.remove(); // 删除节点 path.replaceWith(newNode); // 替换节点 path.insertBefore(newNode); // 在前面插入 path.insertAfter(newNode); // 在后面插入 // 遍历 path.traverse({ ... }); // 子树遍历 path.skip(); // 跳过子树 path.stop(); // 停止遍历 // 检查 path.isIdentifier(); // 检查节点类型 path.findParent((p) => ...); // 查找父节点 path.getFunctionParent(); // 获取函数父节点 path.getStatementParent(); // 获取语句父节点 // 作用域 path.scope.hasBinding('name'); // 检查绑定 path.scope.rename('old', 'new'); // 重命名 path.scope.generateUid('name'); // 生成唯一标识符 }}高级插件技巧1. 状态管理module.exports = function(babel) { return { name: 'stateful-plugin', pre(state) { // 遍历前初始化状态 this.counter = 0; }, visitor: { Identifier(path) { this.counter++; } }, post(state) { // 遍历后输出结果 console.log(`Found ${this.counter} identifiers`); } };};2. 处理 JSX// 将 <div>Hello</div> 转换为 h('div', null, 'Hello')module.exports = function(babel) { const { types: t } = babel; return { name: 'jsx-transform', visitor: { JSXElement(path) { const { openingElement, children } = path.node; const tagName = openingElement.name.name; // 创建 h() 调用 const callExpr = t.callExpression( t.identifier('h'), [ t.stringLiteral(tagName), t.nullLiteral(), ...children.map(child => { if (t.isJSXText(child)) { return t.stringLiteral(child.value.trim()); } return child; }) ] ); path.replaceWith(callExpr); } } };};3. 源码映射支持module.exports = function(babel) { return { name: 'sourcemap-plugin', visitor: { Identifier(path) { // 保留原始位置信息 path.addComment('leading', ` Original: ${path.node.name} `); } } };};调试技巧// 查看 ASTconst parser = require('@babel/parser');const code = 'const a = 1';const ast = parser.parse(code);console.log(JSON.stringify(ast, null, 2));// 使用 @babel/template 简化节点创建const template = require('@babel/template').default;const buildRequire = template(` var IMPORT_NAME = require(SOURCE);`);const ast2 = buildRequire({ IMPORT_NAME: t.identifier('myModule'), SOURCE: t.stringLiteral('my-module')});最佳实践使用 path 而非直接操作 node - Path 提供更多上下文信息善用 path.scope - 正确处理变量作用域使用 @babel/template - 简化复杂 AST 节点的创建测试插件 - 使用 @babel/core 的 transformSync 进行单元测试参考官方插件 - 学习 Babel 官方插件的实现方式
阅读 0·3月7日 12:10

Babel 中的 @babel/preset-env 是如何工作的?useBuiltIns 选项有什么区别?

@babel/preset-env 工作原理核心概念@babel/preset-env 是一个智能预设,它根据你指定的目标环境(浏览器或 Node.js 版本)自动确定需要的 Babel 插件和 polyfills,而不需要手动配置每一个转换。工作机制// babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', { targets: { browsers: ['> 1%', 'last 2 versions', 'not dead'], node: 'current' }, useBuiltIns: 'usage', corejs: 3 }] ]};工作流程:目标环境分析:解析 targets 配置插件选择:根据目标环境支持情况,确定需要哪些语法转换插件Polyfill 处理:根据 useBuiltIns 配置处理 polyfills代码转换:应用选定的插件进行转换useBuiltIns 选项详解1. false(默认值)不自动添加 polyfills,需要手动引入。// babel.config.js{ useBuiltIns: false}// 需要手动在入口文件引入import 'core-js/stable';import 'regenerator-runtime/runtime';特点:完全手动控制 polyfills可能引入不必要的 polyfills包体积可能较大2. "entry"根据目标环境,将 import 'core-js' 替换为具体需要的 polyfills。// babel.config.js{ useBuiltIns: 'entry', corejs: 3}// 源代码import 'core-js/stable';// 转换后(假设目标环境需要)import 'core-js/modules/es.array.iterator';import 'core-js/modules/es.promise';// ... 其他需要的 polyfills特点:替换入口文件的 core-js 导入根据目标环境过滤不需要的 polyfills全局污染(修改原生原型)3. "usage"根据代码中实际使用的特性,按需引入 polyfills。// babel.config.js{ useBuiltIns: 'usage', corejs: 3}// 源代码const arr = [1, 2, 3];arr.includes(2);const promise = Promise.resolve(1);// 转换后import 'core-js/modules/es.array.includes';import 'core-js/modules/es.promise';var arr = [1, 2, 3];arr.includes(2);var promise = Promise.resolve(1);特点:按需引入,最小化 polyfill 体积无需手动导入 core-js全局污染(修改原生原型)推荐用于应用程序开发对比总结| 选项 | 引入方式 | 包体积 | 全局污染 | 适用场景 ||------|----------|--------|----------|----------|| false | 手动 | 最大 | 是 | 库开发(不推荐) || entry | 入口文件替换 | 中等 | 是 | 应用程序 || usage | 按需自动 | 最小 | 是 | 应用程序(推荐) |与 @babel/plugin-transform-runtime 的区别useBuiltIns: 'usage' + core-js// 全局污染方式Array.prototype.includes = ... // 修改全局原型@babel/plugin-transform-runtime + core-js// babel.config.js{ plugins: [ ['@babel/plugin-transform-runtime', { corejs: 3 // 使用 core-js 的模块化版本 }] ]}特点:不污染全局环境适合库/工具开发使用沙盒化的 polyfills// 转换前const arr = [1, 2, 3];arr.includes(2);// 转换后(不污染全局)import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";var arr = [1, 2, 3];_includesInstanceProperty(arr).call(arr, 2);最佳实践1. 应用程序开发// babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', { targets: { browsers: ['> 1%', 'last 2 versions'] }, useBuiltIns: 'usage', corejs: 3 }] ]};2. 库/工具开发// babel.config.jsmodule.exports = { presets: ['@babel/preset-env'], plugins: [ ['@babel/plugin-transform-runtime', { corejs: 3, helpers: true, regenerator: true }] ]};3. 配置 browserslist// package.json{ "browserslist": [ "> 1%", "last 2 versions", "not dead" ]}调试技巧# 查看实际使用的插件和 polyfillsDEBUG=* npx babel src/index.js# 仅查看 preset-env 信息DEBUG=@babel/preset-env npx babel src/index.js
阅读 0·3月6日 23:08

Babel 的编译流程是怎样的?请详细说明各个阶段

Babel 的编译过程主要分为三个阶段:解析(Parsing):将源代码转换为 AST转换(Transforming):遍历和修改 AST生成(Generating):将 AST 转换回代码详细流程第一阶段:解析(Parsing)将源代码字符串转换为抽象语法树(AST)。// 源代码const add = (a, b) => a + b;// 解析后的 AST(简化版){ "type": "VariableDeclaration", "declarations": [{ "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "add" }, "init": { "type": "ArrowFunctionExpression", "params": [ { "type": "Identifier", "name": "a" }, { "type": "Identifier", "name": "b" } ], "body": { "type": "BinaryExpression", "operator": "+", "left": { "type": "Identifier", "name": "a" }, "right": { "type": "Identifier", "name": "b" } } } }]}涉及的包:@babel/parser(基于 Babylon)第二阶段:转换(Transforming)遍历 AST 并应用插件进行转换。// 转换前(ArrowFunctionExpression)const add = (a, b) => a + b;// 转换后(FunctionExpression)var add = function add(a, b) { return a + b;};转换过程:遍历(Traverse):深度优先遍历 AST访问(Visit):遇到节点时调用对应的访问者方法修改(Modify):通过访问者模式修改节点// 插件示例:转换箭头函数const arrowFunctionPlugin = { visitor: { ArrowFunctionExpression(path) { // 将箭头函数转换为普通函数 path.replaceWith( t.functionExpression( null, path.node.params, t.blockStatement([ t.returnStatement(path.node.body) ]) ) ); } }};涉及的包:@babel/traverse第三阶段:生成(Generating)将修改后的 AST 转换回代码字符串。// AST 节点{ "type": "FunctionExpression", "id": null, "params": [...], "body": { ... }}// 生成的代码var add = function add(a, b) { return a + b;};涉及的包:@babel/generator完整流程图┌─────────────────┐│ 源代码输入 ││ const add = ... │└────────┬────────┘ │ ▼┌─────────────────┐│ @babel/parser ││ 解析阶段 │└────────┬────────┘ │ ▼┌─────────────────┐│ AST ││ 抽象语法树 │└────────┬────────┘ │ ▼┌──────────────────┐│ @babel/traverse ││ 转换阶段 ││ (插件应用) │└────────┬─────────┘ │ ▼┌─────────────────┐│ 修改后的 AST │└────────┬────────┘ │ ▼┌──────────────────┐│ @babel/generator ││ 生成阶段 │└────────┬─────────┘ │ ▼┌─────────────────┐│ 编译后代码 ││ var add = ... │└─────────────────┘核心概念1. Visitor 模式const visitor = { // 进入节点时调用 Identifier: { enter(path) { console.log('Enter:', path.node.name); }, // 离开节点时调用 exit(path) { console.log('Exit:', path.node.name); } }};2. Path 对象表示节点在树中的位置提供节点操作方法(替换、删除、插入等)包含作用域信息3. State 状态在遍历过程中传递数据插件间共享信息调试技巧// 查看 ASTconst parser = require('@babel/parser');const ast = parser.parse('const a = 1');console.log(JSON.stringify(ast, null, 2));// 使用 babel-node 调试// npx babel-node --inspect-brk script.js​
阅读 0·3月6日 23:01

如何在项目中配置 Babel 以支持 TypeScript 和 React?

配置方案1. 安装必要的依赖# 核心依赖npm install --save-dev @babel/core @babel/cli @babel/preset-env# TypeScript 支持npm install --save-dev @babel/preset-typescript# React 支持npm install --save-dev @babel/preset-react# 运行时支持(可选但推荐)npm install --save @babel/runtimenpm install --save-dev @babel/plugin-transform-runtime2. 基础配置(babel.config.js)module.exports = { presets: [ // 根据目标环境自动选择转换 ['@babel/preset-env', { targets: { browsers: ['> 1%', 'last 2 versions', 'not ie <= 8'] }, useBuiltIns: 'usage', corejs: 3 }], // TypeScript 支持 '@babel/preset-typescript', // React 支持(包含 JSX 转换) ['@babel/preset-react', { runtime: 'automatic', // React 17+ 新 JSX 转换 development: process.env.NODE_ENV === 'development' }] ], plugins: [ // 运行时优化 ['@babel/plugin-transform-runtime', { corejs: 3, helpers: true, regenerator: true }] ]};3. 配合 Webpack 使用// webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react' ] } } } ] }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] }};4. TypeScript 配置(tsconfig.json){ "compilerOptions": { "target": "ESNext", "module": "ESNext", "jsx": "preserve", // 保留 JSX,让 Babel 处理 "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, // Babel 需要 "noEmit": true // Babel 负责编译,TS 只负责类型检查 }, "include": ["src/**/*"], "exclude": ["node_modules"]}高级配置1. 环境特定配置// babel.config.jsmodule.exports = { presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'], env: { development: { plugins: [ 'react-refresh/babel' // React Fast Refresh ] }, production: { plugins: [ 'transform-remove-console' // 移除 console ] }, test: { presets: [ ['@babel/preset-env', { targets: { node: 'current' } }] ] } }};2. 装饰器支持npm install --save-dev @babel/plugin-proposal-decoratorsmodule.exports = { presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'], plugins: [ ['@babel/plugin-proposal-decorators', { legacy: true }], '@babel/plugin-proposal-class-properties' ]};3. 路径别名配置// babel.config.jsmodule.exports = { presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'], plugins: [ ['module-resolver', { root: ['./src'], alias: { '@': './src', '@components': './src/components', '@utils': './src/utils' } }] ]};// tsconfig.json{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"] } }}常见问题1. 类型检查与编译分离// package.json{ "scripts": { "type-check": "tsc --noEmit", "build": "npm run type-check && babel src --out-dir dist --extensions '.ts,.tsx'" }}2. 处理 CSS/SCSS 导入// 在 TypeScript 中声明模块declare module '*.scss' { const content: { [className: string]: string }; export default content;}3. 热更新配置// webpack.config.jsconst ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');module.exports = { module: { rules: [{ test: /\.(ts|tsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { plugins: [ process.env.NODE_ENV === 'development' && 'react-refresh/babel' ].filter(Boolean) } } }] }, plugins: [ process.env.NODE_ENV === 'development' && new ReactRefreshWebpackPlugin() ].filter(Boolean)};
阅读 0·3月6日 21:43