feat(tools-babel): Add tools-babel package for babel configuration and parsing support#4087
feat(tools-babel): Add tools-babel package for babel configuration and parsing support#4087
Conversation
incubator/tools-babel/README.md
Outdated
| | Domain | Frequency | What is traced | | ||
| | -------------- | --------- | ---------------------------------------------------- | | ||
| | `transform` | medium | Parse operations (OXC native, AST conversion, Babel) | | ||
| | `babel-plugin` | high | Individual plugin visitor method calls | |
There was a problem hiding this comment.
| | `babel-plugin` | high | Individual plugin visitor method calls | | |
| | `babel-plugin` | high | Individual plugin visitor method calls | |
incubator/tools-babel/README.md
Outdated
| trackPerformance({ enable: "babel-plugin", strategy: "timing", frequency: "high" }); | ||
| ``` | ||
|
|
||
| ## TransformerSettings | ||
|
|
||
| Settings that persist across transformation passes: | ||
|
|
||
| | Field | Type | Default | Description | | ||
| | ---------------------- | -------------------------- | ------- | ------------------------------------------------------------ | | ||
| | `configCallerMixins` | `Record<string, string>` | -- | Extra fields added to Babel's `caller` config | | ||
| | `configDisabledPlugins`| `Set<string>` | -- | Plugin keys to remove from the resolved config | | ||
| | `parseDisableOxc` | `boolean` | -- | Disable OXC parser | | ||
| | `parseDisableHermes` | `boolean` | -- | Disable Hermes parser | | ||
| | `parseFlowDefault` | `boolean` | `true` | Assume Flow in `.js`/`.jsx` files under `node_modules` | | ||
| | `parseFlowWorkspace` | `boolean` | `false` | Assume Flow in workspace `.js`/`.jsx` files | | ||
| | `parseExtDefault` | `SrcSyntax` | `"js"` | Syntax for unknown file extensions (unset to skip) | | ||
| | `parseExtAliases` | `Record<string, SrcSyntax>`| -- | Map extensions to syntax types (e.g. `{ ".svg": "jsx" }`) | | ||
|
|
||
| ## API Reference | ||
|
|
||
| ### Config | ||
|
|
||
| | Function | Description | | ||
| | ------------------------------------- | -------------------------------------------------------------------------- | | ||
| | `getBabelConfig(args, settings?)` | Build a per-file Babel config from cached base config + file-specific settings | | ||
| | `filterConfigPlugins(config, disabled?)` | Resolve presets/overrides and filter plugins by key | | ||
|
|
||
| ### Parsing | ||
|
|
||
| | Function | Description | | ||
| | ----------------------------- | -------------------------------------------------------------- | | ||
| | `parseToAst(args)` | Parse with fallback chain: OXC -> Hermes -> Babel | | ||
| | `oxcParseToAst(args, trace?)` | Parse with OXC and convert ESTree to Babel AST | | ||
| | `hermesParseToAst(args)` | Parse with Hermes | | ||
| | `toBabelAST(program, source, isTypeScript?, comments?)` | Convert OXC ESTree to Babel AST | | ||
|
|
||
| ### Transformer | ||
|
|
||
| | Function | Description | | ||
| | ------------------------------------------------- | ------------------------------------------------------- | | ||
| | `makeTransformerArgs(babelArgs, settings?, updateContext?)` | Build `TransformerArgs` with context and Babel config | | ||
| | `initTransformerContext(filename, settings)` | Initialize file context without building Babel config | | ||
|
|
||
| ### Plugins | ||
|
|
||
| | Function | Description | | ||
| | --------------------------------------- | --------------------------------------------------------------- | | ||
| | `isConfigItem(plugin)` | Check if plugin is a Babel `ConfigItem` | | ||
| | `isPluginObj(plugin)` | Check if plugin is a resolved `PluginObj` | | ||
| | `getPluginTarget(plugin)` | Extract the plugin target (function or string) | | ||
| | `getPluginKey(plugin)` | Extract the key from a resolved plugin | | ||
| | `updateTransformOptions(options, visitor)` | Walk and modify plugins/presets/overrides in a config | |
There was a problem hiding this comment.
| trackPerformance({ enable: "babel-plugin", strategy: "timing", frequency: "high" }); | |
| ``` | |
| ## TransformerSettings | |
| Settings that persist across transformation passes: | |
| | Field | Type | Default | Description | | |
| | ---------------------- | -------------------------- | ------- | ------------------------------------------------------------ | | |
| | `configCallerMixins` | `Record<string, string>` | -- | Extra fields added to Babel's `caller` config | | |
| | `configDisabledPlugins`| `Set<string>` | -- | Plugin keys to remove from the resolved config | | |
| | `parseDisableOxc` | `boolean` | -- | Disable OXC parser | | |
| | `parseDisableHermes` | `boolean` | -- | Disable Hermes parser | | |
| | `parseFlowDefault` | `boolean` | `true` | Assume Flow in `.js`/`.jsx` files under `node_modules` | | |
| | `parseFlowWorkspace` | `boolean` | `false` | Assume Flow in workspace `.js`/`.jsx` files | | |
| | `parseExtDefault` | `SrcSyntax` | `"js"` | Syntax for unknown file extensions (unset to skip) | | |
| | `parseExtAliases` | `Record<string, SrcSyntax>`| -- | Map extensions to syntax types (e.g. `{ ".svg": "jsx" }`) | | |
| ## API Reference | |
| ### Config | |
| | Function | Description | | |
| | ------------------------------------- | -------------------------------------------------------------------------- | | |
| | `getBabelConfig(args, settings?)` | Build a per-file Babel config from cached base config + file-specific settings | | |
| | `filterConfigPlugins(config, disabled?)` | Resolve presets/overrides and filter plugins by key | | |
| ### Parsing | |
| | Function | Description | | |
| | ----------------------------- | -------------------------------------------------------------- | | |
| | `parseToAst(args)` | Parse with fallback chain: OXC -> Hermes -> Babel | | |
| | `oxcParseToAst(args, trace?)` | Parse with OXC and convert ESTree to Babel AST | | |
| | `hermesParseToAst(args)` | Parse with Hermes | | |
| | `toBabelAST(program, source, isTypeScript?, comments?)` | Convert OXC ESTree to Babel AST | | |
| ### Transformer | |
| | Function | Description | | |
| | ------------------------------------------------- | ------------------------------------------------------- | | |
| | `makeTransformerArgs(babelArgs, settings?, updateContext?)` | Build `TransformerArgs` with context and Babel config | | |
| | `initTransformerContext(filename, settings)` | Initialize file context without building Babel config | | |
| ### Plugins | |
| | Function | Description | | |
| | --------------------------------------- | --------------------------------------------------------------- | | |
| | `isConfigItem(plugin)` | Check if plugin is a Babel `ConfigItem` | | |
| | `isPluginObj(plugin)` | Check if plugin is a resolved `PluginObj` | | |
| | `getPluginTarget(plugin)` | Extract the plugin target (function or string) | | |
| | `getPluginKey(plugin)` | Extract the key from a resolved plugin | | |
| | `updateTransformOptions(options, visitor)` | Walk and modify plugins/presets/overrides in a config | | |
| trackPerformance({ | |
| enable: "babel-plugin", | |
| strategy: "timing", | |
| frequency: "high", | |
| }); |
TransformerSettings
Settings that persist across transformation passes:
| Field | Type | Default | Description |
|---|---|---|---|
configCallerMixins |
Record<string, string> |
-- | Extra fields added to Babel's caller config |
configDisabledPlugins |
Set<string> |
-- | Plugin keys to remove from the resolved config |
parseDisableOxc |
boolean |
-- | Disable OXC parser |
parseDisableHermes |
boolean |
-- | Disable Hermes parser |
parseFlowDefault |
boolean |
true |
Assume Flow in .js/.jsx files under node_modules |
parseFlowWorkspace |
boolean |
false |
Assume Flow in workspace .js/.jsx files |
parseExtDefault |
SrcSyntax |
"js" |
Syntax for unknown file extensions (unset to skip) |
parseExtAliases |
Record<string, SrcSyntax> |
-- | Map extensions to syntax types (e.g. { ".svg": "jsx" }) |
API Reference
Config
| Function | Description |
|---|---|
getBabelConfig(args, settings?) |
Build a per-file Babel config from cached base config + file-specific settings |
filterConfigPlugins(config, disabled?) |
Resolve presets/overrides and filter plugins by key |
Parsing
| Function | Description |
|---|---|
parseToAst(args) |
Parse with fallback chain: OXC -> Hermes -> Babel |
oxcParseToAst(args, trace?) |
Parse with OXC and convert ESTree to Babel AST |
hermesParseToAst(args) |
Parse with Hermes |
toBabelAST(program, source, isTypeScript?, comments?) |
Convert OXC ESTree to Babel AST |
Transformer
| Function | Description |
|---|---|
makeTransformerArgs(babelArgs, settings?, updateContext?) |
Build TransformerArgs with context and Babel config |
initTransformerContext(filename, settings) |
Initialize file context without building Babel config |
Plugins
| Function | Description |
|---|---|
isConfigItem(plugin) |
Check if plugin is a Babel ConfigItem |
isPluginObj(plugin) |
Check if plugin is a resolved PluginObj |
getPluginTarget(plugin) |
Extract the plugin target (function or string) |
getPluginKey(plugin) |
Extract the key from a resolved plugin |
updateTransformOptions(options, visitor) |
Walk and modify plugins/presets/overrides in a config |
1ab7c49 to
d67e42a
Compare
…/jasonvmo/tools-babel
…/jasonvmo/tools-babel
|
|
||
| const { value, raw } = node; | ||
|
|
||
| if (raw !== undefined) { |
There was a problem hiding this comment.
| if (raw !== undefined) { | |
| if (raw != null) { |
| } else { | ||
| n.type = "OptionalCallExpression"; | ||
| } | ||
| if (!n.optional) n.optional = false; |
There was a problem hiding this comment.
| if (!n.optional) n.optional = false; | |
| if (n.optional == null) n.optional = false; |
| if (lowestOptional < 0) return; | ||
|
|
There was a problem hiding this comment.
Nit: Technically, this isn't necessary as the for-loop already performs the same check
| if (lowestOptional < 0) return; |
| const { key, value } = node; | ||
| if (!value) return; | ||
|
|
||
| const keyType = key && key.type; |
There was a problem hiding this comment.
| const keyType = key && key.type; | |
| const keyType = key?.type; |
| node.type = "ClassPrivateMethod"; | ||
| if (keyType === "PrivateIdentifier") convertPrivateIdentifierToName(key); | ||
| if (node.kind === "get" || node.kind === "set") { | ||
| if (node.computed === undefined) node.computed = false; |
There was a problem hiding this comment.
| if (node.computed === undefined) node.computed = false; | |
| if (node.computed == null) node.computed = false; |
| parseExtAliases, | ||
| parseExtDefault = "js", | ||
| } = settings; | ||
| const isNodeModule = filename.includes("node_modules"); |
There was a problem hiding this comment.
We should also include separators to avoid false positives
| const isNodeModule = filename.includes("node_modules"); | |
| const isNodeModule = /[/\\]node_modules[/\\]/.test(filename); |
| const isTypeScript = | ||
| context.srcSyntax === "ts" || context.srcSyntax === "tsx"; |
There was a problem hiding this comment.
We should move this before oxcResult so we don't have to check twice.
| } | ||
| const result: T[] = []; | ||
| for (let i = 0; i < base.length; i++) { | ||
| const entry = i in updates ? updates[i] : base[i]; |
There was a problem hiding this comment.
Should we be using Object.hasOwn here to avoid issues related to inheritance?
| const entry = i in updates ? updates[i] : base[i]; | |
| const entry = Object.hasOwn(updates, i) ? updates[i] : base[i]; |
| /** disable the oxc parser */ | ||
| parseDisableOxc?: boolean; | ||
|
|
||
| /** disable the hermes parser */ | ||
| parseDisableHermes?: boolean; |
There was a problem hiding this comment.
These are awkwardly named, can we change them?
| /** disable the oxc parser */ | |
| parseDisableOxc?: boolean; | |
| /** disable the hermes parser */ | |
| parseDisableHermes?: boolean; | |
| /** disable the oxc parser */ | |
| parseOxcDisabled?: boolean; | |
| /** disable the hermes parser */ | |
| parseHermesDisabled?: boolean; |
Or even invert the names?
| "project": [ | ||
| "src/**/*.{js,ts,tsx}" | ||
| "src/**/*.{js,ts,tsx}", | ||
| "test/**/*.{js,ts,tsx}" |

NOTE: this currently includes the tools-performance change as it depends on that change. Ignore those files here.
Description
This adds a tools project for dealing with babel with regards to react-native transformations. This builds upon some of the work from the metro-transformer-oxc package as well as some of the work I was looking at for using native tools for transformation.
This package has tools that deal with babel configuration and parsing.
Babel Configuration
Several utilities are provided here:
@react-native/metro-babel-transformer, which is the standard upstream babel transformer for react-native.Babel Parsing
Parsing is one of the most time consuming and memory intensive parts of the transformation process. There are faster parsers but they generally produce estree ASTs rather than babel-ASTs. The current metro-transformer-oxc uses a utility estree-to-babel to convert the AST, which mostly works, but when timing the performance it ends up being almost as slow as babel parsing.
The parsing in here:
The tests are fairly wordy for output but this is useful for allowing tools like Claude to gain information about what is happening and what the differences are.