Transform optional chaining operators into a series of nil checks for backward compatibility
npx @tessl/cli install tessl/npm-babel--plugin-transform-optional-chaining@7.27.0@babel/plugin-transform-optional-chaining is a Babel plugin that transforms JavaScript optional chaining operators (?.) into backward-compatible series of nil checks, enabling the use of modern optional chaining syntax in environments that don't natively support it. The plugin handles both optional member expressions (obj?.prop) and optional call expressions (func?.()) by converting them into conditional logic that safely checks for null or undefined values.
npm install --save-dev @babel/plugin-transform-optional-chainingimport pluginTransformOptionalChaining from "@babel/plugin-transform-optional-chaining";For CommonJS:
const pluginTransformOptionalChaining = require("@babel/plugin-transform-optional-chaining");Named imports for transformation functions and types:
import { transform, transformOptionalChain, type Options } from "@babel/plugin-transform-optional-chaining";
import type { NodePath, types as t } from "@babel/core";// babel.config.js
module.exports = {
plugins: [
["@babel/plugin-transform-optional-chaining", { loose: false }]
]
};// With TypeScript configuration
import type { Options } from "@babel/plugin-transform-optional-chaining";
const options: Options = {
loose: true // Enable loose transformation mode
};Input code:
// Optional member expressions
const name = obj?.user?.name;
// Optional call expressions
const result = func?.();
// Chained optional calls
const data = api?.getUser?.()?.profile?.data;Output (strict mode):
var _obj$user, _func;
// Optional member expressions
const name = (_obj$user = obj == null ? void 0 : obj.user) == null ? void 0 : _obj$user.name;
// Optional call expressions
const result = (_func = func) == null ? void 0 : _func();
// Chained optional calls
var _api$getUser, _api$getUser2, _api$getUser2$profil;
const data = (_api$getUser = api == null ? void 0 : api.getUser) == null ? void 0 : (_api$getUser2 = _api$getUser.call(api)) == null ? void 0 : (_api$getUser2$profil = _api$getUser2.profile) == null ? void 0 : _api$getUser2$profil.data;Creates a Babel plugin configuration with specified options.
/**
* Main plugin factory function that returns a Babel plugin configuration
* @param api - Babel API object providing version checking and assumptions
* @param options - Plugin configuration options
* @returns Babel plugin configuration object
*/
export default function(api: any, options: Options): BabelPlugin;
interface BabelPlugin {
name: string;
manipulateOptions?: (opts: any, parser: any) => void;
visitor: {
"OptionalCallExpression|OptionalMemberExpression"(
path: NodePath<t.OptionalCallExpression | t.OptionalMemberExpression>
): void;
};
}Main transformation function for optional chaining expressions that determines the appropriate nullish value and wrapping logic.
/**
* Main transformation function for optional chaining expressions
* @param path - AST path to the optional chaining expression
* @param assumptions - Configuration object containing pureGetters and noDocumentAll boolean properties
*/
export function transform(
path: NodePath<t.OptionalCallExpression | t.OptionalMemberExpression>,
assumptions: { pureGetters: boolean; noDocumentAll: boolean }
): void;The core transformation logic that converts optional chaining into conditional checks.
/**
* Core transformation logic for optional chaining with configurable assumptions
* @param path - AST path to the optional chaining expression
* @param assumptions - Configuration object containing pureGetters and noDocumentAll boolean properties
* @param replacementPath - Path where the transformed expression should be placed
* @param ifNullish - Expression to use when the chain is nullish
* @param wrapLast - Optional function to wrap the final expression
*/
export function transformOptionalChain(
path: NodePath<t.OptionalCallExpression | t.OptionalMemberExpression>,
assumptions: { pureGetters: boolean; noDocumentAll: boolean },
replacementPath: NodePath<t.Expression>,
ifNullish: t.Expression,
wrapLast?: (value: t.Expression) => t.Expression
): void;/**
* Configuration options for the optional chaining transformation plugin
*/
export interface Options {
/** Enable loose transformation mode for more compact output */
loose?: boolean;
}These types are required from @babel/core when using the transformation functions:
import type { NodePath, types as t } from "@babel/core";
// Key types used in the API signatures
type OptionalCallExpression = t.OptionalCallExpression;
type OptionalMemberExpression = t.OptionalMemberExpression;
type Expression = t.Expression;Strict Mode (default):
=== null || === void 0)document.all edge case properlyLoose Mode (loose: true):
== null)document.all edge caseThe plugin respects Babel's assumption system:
api.assumption("noDocumentAll") ?? looseapi.assumption("pureGetters") ?? looseWhen pureGetters is true, the plugin avoids using Function#call for method invocations on simple member expressions.
The plugin visitor targets:
OptionalCallExpression: Expressions like func?.()OptionalMemberExpression: Expressions like obj?.propFor each matched node, it:
eval?.() becomes (0,eval)() for indirect evaluationthis context using .call() or .bind()delete obj?.prop becomes conditional with true fallback