/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
babel.config.ts
643 строки
19 KB
liuxingbaoyu
chore: Update prettier v3.9 (#18147)
20 июл 2026, 21:37
Не верифицирован
20 июл 2026, 21:37
61bfda5
Код
Авторство
О чём код?
import pathUtils from "node:path"; import fs from "node:fs"; import type { PluginItem } from "@babel/core"; import { parseSync, traverse, type InputOptions, type types as t, type NodePath, type PluginObject, type PluginAPI, type ConfigAPI, } from "@babel/core"; import packageJson from "./package.json" with { type: "json" }; import babelCorePackageJson from "./packages/babel-core/package.json" with { type: "json" }; import pluginToggleBooleanFlag from "./scripts/babel-plugin-toggle-boolean-flag/plugin.ts"; import pluginBitDecorator from "./scripts/babel-plugin-bit-decorator/plugin.ts"; import pluginTransformNodeProtocolImport from "./scripts/babel-plugin-transform-node-protocol-import/plugin.ts"; import { commonJS } from "$repo-utils"; let jestSnapshot = false; if (typeof it === "function") { // Jest loads the Babel config to parse file and update inline snapshots. // This is ok, as it's not loading the Babel config to test Babel itself. if (!new Error().stack!.includes("jest-snapshot")) { throw new Error("Monorepo root's babel.config.js loaded by a test."); } jestSnapshot = true; } const { __dirname } = commonJS(import.meta.url); function normalize(src: string) { return src.replace(/\//, pathUtils.sep); } export default function (api: ConfigAPI) { const env = api.env(); const sources = ["packages/*/src", "codemods/*/src", "eslint/*/src"]; const envOpts = { shippedProposals: true, modules: false, exclude: [ "transform-typeof-symbol", // We need to enable useBuiltIns "transform-object-rest-spread", ], debug: false, }; const presetTsOpts = { onlyRemoveTypeImports: true, optimizeConstEnums: true, }; // These are "safe" assumptions, that we can enable globally const assumptions = { constantSuper: true, ignoreFunctionLength: true, ignoreToPrimitiveHint: true, mutableTemplateObject: true, noClassCalls: true, noDocumentAll: true, noNewArrows: true, setClassMethods: true, setComputedProperties: true, setSpreadProperties: true, skipForOfIteratorClosing: true, superIsCallableConstructor: true, }; // These are "less safe": we only enable them on our own code // and not when compiling dependencies. const sourceAssumptions = { objectRestNoSymbols: true, pureGetters: true, setPublicClassFields: true, }; const parserAssumptions = { iterableIsArray: true, }; let targets = {}; let replaceTSImportExtension = true; let ignoreLib = true; const nodeVersion = "22.18"; // The vast majority of our src files are modules, but we use // unambiguous to keep things simple until we get around to renaming // the modules to be more easily distinguished from CommonJS const unambiguousSources = [ ...sources, "packages/*/test", "codemods/*/test", "eslint/*/test", ]; switch (env) { // Configs used during bundling builds. case "standalone": replaceTSImportExtension = false; ignoreLib = false; // rollup-commonjs will converts node_modules to ESM unambiguousSources.push( "/**/node_modules", "packages/babel-preset-env/data", "packages/babel-compat-data", "packages/babel-runtime/regenerator" ); targets = { ie: 7 }; break; case "rollup": replaceTSImportExtension = false; ignoreLib = false; // rollup-commonjs will converts node_modules to ESM unambiguousSources.push( "/**/node_modules", "packages/babel-preset-env/data", "packages/babel-compat-data" ); targets = { node: nodeVersion }; break; case "test-legacy": // In test-legacy environment, we build babel on latest node but test on minimum supported legacy versions // fall through case "production": // Config during builds before publish. targets = { node: nodeVersion }; break; case "test": targets = { node: "current" }; break; case "development": envOpts.debug = true; targets = { node: "current" }; break; } const config: InputOptions = { targets, assumptions, babelrc: false, browserslistConfigFile: false, // Our dependencies are all standard CommonJS, along with all sorts of // other random files in Babel's codebase, so we use script as the default, // and then mark actual modules as modules farther down. sourceType: "script", comments: false, ignore: ( [ // These may not be strictly necessary with the newly-limited scope of // babelrc searching, but including them for now because we had them // in our .babelignore before. "packages/*/test/fixtures", ignoreLib ? "packages/*/lib" : null, "packages/babel-standalone/babel.js", ].filter(Boolean) as string[] ).map(normalize), parserOpts: { createImportExpressions: true, }, presets: [ // presets are applied from right to left ["@babel/env", envOpts], ["@babel/preset-typescript", presetTsOpts], ], plugins: [ env === "standalone" && [ "@babel/transform-object-rest-spread", { useBuiltIns: true }, ], pluginBitDecorator, pluginTransformNodeProtocolImport, ].filter(Boolean) as PluginItem[], overrides: [ { test: ["packages/babel-parser"].map(normalize), plugins: [ "babel-plugin-transform-charcodes", pluginBabelParserTokenType, ], assumptions: parserAssumptions, }, { test: [ "packages/babel-code-frame", "packages/babel-generator", "packages/babel-helper-create-class-features-plugin", "packages/babel-helper-string-parser", "packages/babel-helper-validator-identifier", ].map(normalize), plugins: ["babel-plugin-transform-charcodes"], }, { test: ["packages/babel-generator/src"].map(normalize), plugins: [pluginGeneratorOptimization], }, { test: sources.map(normalize), assumptions: sourceAssumptions, plugins: [ transformNamedBabelTypesImportToDestructuring, replaceTSImportExtension ? pluginReplaceTSImportExtension : null, [ pluginToggleBooleanFlag, { name: "IS_STANDALONE", value: env === "standalone" }, "flag-IS_STANDALONE", ], [ pluginToggleBooleanFlag, { name: "process.env.IS_PUBLISH", value: bool(process.env.IS_PUBLISH), }, "flag-IS_PUBLISH", ], [ pluginToggleBooleanFlag, { name: "process.env.BABEL_9_BREAKING", value: process.env.STRIP_BABEL_VERSION_FLAG ? bool(process.env.BABEL_9_BREAKING) : null, }, "flag-BABEL_9_BREAKING", ], pluginPackageJsonMacro, [ pluginRequiredVersionMacro, { overwrite(requiredVersion: string) { if (!process.env.IS_PUBLISH || env === "standalone") { if (bool(process.env.BABEL_9_BREAKING)) { // Match packages/babel-core/src/index.ts return babelCorePackageJson.version.replace( /0*$/, "999999999" ); } return babelCorePackageJson.version; } if (packageJson.version.includes("-")) { // for pre-releases requiredVersion = `${requiredVersion} || ${packageJson.version}`; } return requiredVersion; }, }, ], ].filter(Boolean) as PluginItem[], }, { test: unambiguousSources.map(normalize), sourceType: "unambiguous", } satisfies InputOptions, ].filter(Boolean), }; if (jestSnapshot) { config.plugins = []; config.presets = []; config.overrides = []; config.parserOpts = { plugins: ["typescript"], }; config.sourceType = "unambiguous"; } return config; } // env vars from the cli are always strings, so !!ENV_VAR returns true for "false" function bool(value: unknown) { return Boolean(value) && value !== "false" && value !== "0"; } // A minimum semver GTE implementation // Limitation: // - it only supports comparing major and minor version, assuming Node.js will never ship // features in patch release so we will never need to compare a version with "1.2.3" // // @example // semverGte("8.10", "8.9") // true // semverGte("8.9", "8.9") // true // semverGte("9.0", "8.9") // true // semverGte("8.9", "8.10") // false // TODO: figure out how to inject it to the `@babel/template` usage so we don't need to // copy and paste it. // `((v,w)=>(v=v.split("."),w=w.split("."),+v[0]>+w[0]||v[0]==w[0]&&+v[1]>=+w[1]))`; // eslint-disable-next-line @typescript-eslint/no-unused-vars function pluginPolyfillsOldNode() { const polyfills: { name: string; necessary: (path: NodePath<t.MemberExpression>) => boolean; supported: (path: NodePath<t.MemberExpression>) => boolean; replacement: () => t.Expression; }[] = [ // EXAMPLE: // { // name: "Object.entries", // necessary({ parent, node }) { // // To avoid infinite replacement loops // return !t.isLogicalExpression(parent, { operator: "||", left: node }); // }, // supported: path => // path.parentPath.isCallExpression({ callee: path.node }), // replacement: template`Object.entries || (o => Object.keys(o).map(k => [k, o[k]]))`, // }, ]; return { visitor: { MemberExpression(path) { for (const polyfill of polyfills) { if (!path.matchesPattern(polyfill.name)) continue; if (!polyfill.necessary(path)) return; if (!polyfill.supported(path)) { throw path.buildCodeFrameError( `This '${polyfill.name}' usage is not supported by the inline polyfill.\n` + path.parentPath.toString() ); } path.replaceWith(polyfill.replacement()); break; } }, }, } satisfies PluginObject; } function pluginPackageJsonMacro({ types: t }: PluginAPI) { const fnName = "PACKAGE_JSON"; return { visitor: { ReferencedIdentifier(path) { if (path.isIdentifier({ name: fnName })) { throw path.buildCodeFrameError( `"${fnName}" is only supported in member expressions.` ); } }, MemberExpression(path) { if (!path.get("object").isIdentifier({ name: fnName })) return; if (path.node.computed) { throw path.buildCodeFrameError( `"${fnName}" does not support computed properties.` ); } const field = (path.node.property as t.Identifier).name; // TODO: When dropping old Node.js versions, use require.resolve // instead of looping through the folders hierarchy let pkg; for (let dir = pathUtils.dirname(this.filename!); ;) { try { pkg = fs.readFileSync(pathUtils.join(dir, "package.json"), "utf8"); break; } catch (_) {} const prev = dir; dir = pathUtils.resolve(dir, ".."); // We are in the root and didn't find a package.json file if (dir === prev) return; } const value = JSON.parse(pkg)[field]; path.replaceWith(t.valueToNode(value)); }, }, } satisfies PluginObject; } function pluginRequiredVersionMacro( { types: t }: PluginAPI, { overwrite, }: { overwrite: (requiredVersion: string, filename: string) => string | null; } ) { const fnName = "REQUIRED_VERSION"; return { visitor: { ReferencedIdentifier(path) { if (path.isIdentifier({ name: fnName })) { throw path.buildCodeFrameError( `"${fnName}" is only supported in call expressions.` ); } }, CallExpression(path) { if (!path.get("callee").isIdentifier({ name: fnName })) return; if (path.node.arguments.length !== 1) { throw path.buildCodeFrameError( `"${fnName}" expects exactly one argument.` ); } const arg = path.get("arguments.0").evaluate().value; if (!arg) { throw path.buildCodeFrameError( `"${fnName}" expects a literal argument.` ); } const version = overwrite(arg, this.filename!); if (version != null) { path.replaceWith(t.stringLiteral(version)); return; } path.replaceWith(path.node.arguments[0]); }, }, } satisfies PluginObject; } // transform `import { x } from "@babel/types"` to `import * as _t from "@babel/types"; const { x } = _t; function transformNamedBabelTypesImportToDestructuring({ types: { cloneNode, importNamespaceSpecifier, objectPattern, objectProperty, variableDeclarator, variableDeclaration, }, }: PluginAPI) { return { name: "transform-babel-types-named-imports", visitor: { ImportDeclaration(path) { const { node } = path; if ( node.importKind === "value" && node.source.value === "@babel/types" && node.specifiers[0].type === "ImportSpecifier" ) { const hoistedDestructuringProperties = []; for (const { imported, local, } of node.specifiers as t.ImportSpecifier[]) { hoistedDestructuringProperties.push( objectProperty( imported, local, false, (imported as t.Identifier).name === local.name ) ); } const babelTypeNsImport = path.scope.generateUidIdentifier("t"); node.specifiers = [importNamespaceSpecifier(babelTypeNsImport)]; path.insertAfter([ variableDeclaration("const", [ variableDeclarator( objectPattern(hoistedDestructuringProperties), cloneNode(babelTypeNsImport) ), ]), ]); } }, }, } satisfies PluginObject; } function pluginReplaceTSImportExtension() { return { visitor: traverse.explode({ "ImportDeclaration|ExportDeclaration"({ node, }: NodePath<t.ImportDeclaration | t.ExportDeclaration>) { if (!("source" in node)) return; const { source } = node; if (source) { source.value = source.value.replace(/(\.[mc]?)ts$/, "$1js"); } }, TSImportEqualsDeclaration({ node }) { const { moduleReference } = node; if (moduleReference.type !== "TSExternalModuleReference") return; const { expression } = moduleReference; expression.value = expression.value.replace(/(\.[mc]?)ts$/, "$1js"); }, }), } satisfies PluginObject; } const tokenTypesMapping = new Map(); const tokenTypeSourcePath = "./packages/babel-parser/src/tokenizer/types.ts"; function getTokenTypesMapping() { if (tokenTypesMapping.size === 0) { const tokenTypesAst = parseSync( fs.readFileSync(tokenTypeSourcePath, { encoding: "utf-8", }), { configFile: false, parserOpts: { attachComment: false, plugins: ["typescript"] }, } )!; let typesDeclaration; for (const n of tokenTypesAst.program.body) { if (n.type === "ExportNamedDeclaration" && n.exportKind === "value") { // @ts-expect-error checked by the if const declarations = n.declaration!.declarations; if (declarations !== undefined) typesDeclaration = declarations[0]; if ( typesDeclaration !== undefined && typesDeclaration.id.name === "types" ) { break; } } } if (typesDeclaration === undefined) { throw new Error( "The plugin can not find TokenType definition in " + tokenTypeSourcePath ); } const tokenTypesDefinition = typesDeclaration.init.expression.properties; for (let i = 0; i < tokenTypesDefinition.length; i++) { tokenTypesMapping.set(tokenTypesDefinition[i].key.name, i); } } return tokenTypesMapping; } function pluginBabelParserTokenType({ types: { isIdentifier, numericLiteral }, }: PluginAPI) { const tokenTypesMapping = getTokenTypesMapping(); return { visitor: { MemberExpression(path) { const { node } = path; if ( isIdentifier(node.object, { name: "tt" }) && isIdentifier(node.property) && !node.computed ) { const tokenName = node.property.name; const tokenType = tokenTypesMapping.get(node.property.name); if (tokenType === undefined) { throw path.buildCodeFrameError( `${tokenName} is not defined in ${tokenTypeSourcePath}` ); } path.replaceWith(numericLiteral(tokenType)); } }, }, } as PluginObject; } function pluginGeneratorOptimization({ types: t }: PluginAPI) { const generatorNames: string[] = []; fs.globSync( pathUtils.join(__dirname, "./packages/babel-generator/src/generators/*.ts") ).forEach(file => { if (file.endsWith("deprecated.ts")) return; const content = fs.readFileSync(file, "utf8"); const ast = parseSync(content, { configFile: false, parserOpts: { plugins: ["typescript"] }, }); t.traverseFast(ast, node => { let name; if (t.isExportSpecifier(node)) { name = t.isStringLiteral(node.exported) ? node.exported.value : node.exported.name; } else if ( t.isExportNamedDeclaration(node) && t.isFunctionDeclaration(node.declaration) ) { name = node.declaration.id!.name; } if (name && !name.startsWith("_")) generatorNames.push(name); }); }); generatorNames.sort(); return { visitor: { CallExpression: { exit(path) { const { callee, arguments: args } = path.node; if (t.isIdentifier(callee) && callee.name === "__node") { if (!t.isStringLiteral(args[0])) { throw new Error("Expected StringLiteral"); } const type = args[0].value; const generatorIndex = generatorNames.indexOf(type); if (generatorIndex === -1) { throw path.buildCodeFrameError(`Unknown generator type: ${type}`); } path.replaceWith(t.numericLiteral(generatorIndex)); return; } if ( t.isMemberExpression(callee) && t.isIdentifier(callee.property) && t.isThisExpression(callee.object) ) { if ( callee.property.name === "token" && args.length === 1 && t.isStringLiteral(args[0]) ) { const str = args[0].value; if (str.length === 1) { callee.property.name = "tokenChar"; args[0] = t.numericLiteral(str.charCodeAt(0)); } } } }, }, }, } satisfies PluginObject; }