Transform optional chaining operators into a series of nil checks
npx @tessl/cli install tessl/npm-babel--plugin-proposal-optional-chaining@7.21.0@babel/plugin-proposal-optional-chaining is a Babel plugin that transforms ES2020 optional chaining syntax (obj?.prop, obj?.method(), obj?.[key]) into conditional null/undefined checks compatible with older JavaScript environments. It enables modern optional chaining syntax in applications that need to support legacy browsers or JavaScript environments that don't natively support ES2020 features.
npm install --save-dev @babel/plugin-proposal-optional-chainingThe plugin is designed to be used within Babel configuration:
// babel.config.js
module.exports = {
plugins: ["@babel/plugin-proposal-optional-chaining"]
};For programmatic use:
import optionalChainingPlugin from "@babel/plugin-proposal-optional-chaining";CommonJS:
const optionalChainingPlugin = require("@babel/plugin-proposal-optional-chaining");// babel.config.js
module.exports = {
plugins: [
["@babel/plugin-proposal-optional-chaining", { loose: false }]
]
};Input ES2020 code:
// Optional member access
const name = obj?.user?.name;
const value = obj?.['property-name'];
// Optional method calls
const result = obj?.method?.();
const data = api?.getData?.();
// Chained optional access
const nested = obj?.prop?.method?.()?.result;
// Delete operations
delete obj?.property;Output (transformed):
// Strict mode output
var _obj, _obj$user;
const name = (_obj = obj) === null || _obj === void 0 ? void 0 : (_obj$user = _obj.user) === null || _obj$user === void 0 ? void 0 : _obj$user.name;
var _obj2;
const value = (_obj2 = obj) === null || _obj2 === void 0 ? void 0 : _obj2['property-name'];
var _obj3, _obj3$method;
const result = (_obj3 = obj) === null || _obj3 === void 0 ? void 0 : (_obj3$method = _obj3.method) === null || _obj3$method === void 0 ? void 0 : _obj3$method.call(_obj3);
var _api, _api$getData;
const data = (_api = api) === null || _api === void 0 ? void 0 : (_api$getData = _api.getData) === null || _api$getData === void 0 ? void 0 : _api$getData.call(_api);Creates a Babel plugin that transforms optional chaining expressions.
/**
* Creates a Babel plugin for transforming optional chaining syntax using declare helper
* @param api - Babel plugin API object with version checking and assumptions
* @param options - Plugin configuration options
* @returns Babel plugin configuration object
*/
export default declare(
(api: PluginAPI, options: Options) => PluginObject
);
interface Options {
/** Enable loose transformation mode for smaller output (default: false) */
loose?: boolean;
}
interface PluginObject {
name: "proposal-optional-chaining";
inherits: typeof syntaxOptionalChaining.default;
visitor: {
"OptionalCallExpression|OptionalMemberExpression": (
path: NodePath<OptionalCallExpression | OptionalMemberExpression>
) => void;
};
}Core transformation logic for converting optional chaining AST nodes (re-exported from internal module).
/**
* Transforms optional chaining AST nodes into conditional expressions
* @param path - NodePath for the optional chaining expression to transform
* @param options - Transformation behavior options with pureGetters and noDocumentAll flags
*/
export function transform(
path: NodePath<OptionalCallExpression | OptionalMemberExpression>,
{ pureGetters, noDocumentAll }: { pureGetters: boolean; noDocumentAll: boolean }
): void;{
"plugins": [
["@babel/plugin-proposal-optional-chaining", {
"loose": false // Use strict null checks (default)
}]
]
}Enables simplified transformation for smaller output:
{
"plugins": [
["@babel/plugin-proposal-optional-chaining", {
"loose": true // Use simplified null checks
}]
]
}Loose mode output:
// Input: obj?.prop
// Loose output: obj == null ? void 0 : obj.prop
// Strict output: obj === null || obj === void 0 ? void 0 : obj.propThe plugin integrates with Babel's assumption system:
// babel.config.js
module.exports = {
assumptions: {
"pureGetters": true, // Assume getters have no side effects
"noDocumentAll": true // Use simplified null checks
},
plugins: ["@babel/plugin-proposal-optional-chaining"]
};obj?.prop, obj?.['key']obj?.method(), obj?.method?.()obj?.prop?.method?.()delete obj?.prop(obj?.prop as any)?.method?.()The plugin ensures proper this context for method calls:
// Input
obj?.method?.();
// Output (strict mode)
var _obj, _obj$method;
(_obj = obj) === null || _obj === void 0 ? void 0 : (_obj$method = _obj.method) === null || _obj$method === void 0 ? void 0 : _obj$method.call(_obj);Prevents duplicate evaluation of expressions with side effects:
// Input
getObject()?.property?.method?.();
// Output - memoizes getObject() call
var _getObject, _getObject$property, _getObject$property$m;
(_getObject = getObject()) === null || _getObject === void 0 ? void 0 : (_getObject$property = _getObject.property) === null || _getObject$property === void 0 ? void 0 : (_getObject$property$m = _getObject$property.method) === null || _getObject$property$m === void 0 ? void 0 : _getObject$property$m.call(_getObject$property);When pureGetters assumption is enabled, the plugin optimizes member access:
// Input: obj?.prop?.method?.()
// With pureGetters: simpler output avoiding Function.call when possible
// Without pureGetters: always uses Function.call for method contextWhen noDocumentAll assumption is enabled, uses simplified null checks:
// Input: obj?.prop
// With noDocumentAll: obj != null ? obj.prop : void 0
// Without noDocumentAll: obj !== null && obj !== void 0 ? obj.prop : void 0// Core types from @babel/helper-plugin-utils and @babel/core
function declare<State = {}, Option = {}>(
builder: (
api: PluginAPI,
options: Option,
dirname: string,
) => PluginObject<State & PluginPass>
): (api: PluginAPI, options: Option, dirname: string) => PluginObject<State & PluginPass>;
interface PluginAPI {
assertVersion(version: number | string): void;
assumption(name: string): boolean | undefined;
version: string;
targets(): any;
env: any;
caller(): any;
types: typeof t;
template: any;
parse: any;
transform: any;
traverse: any;
}
interface PluginObject<State = {}> {
name?: string;
inherits?: any;
visitor?: any;
pre?: (state: State) => void;
post?: (state: State) => void;
manipulateOptions?: any;
}
interface PluginPass {
key?: string;
file: any;
opts: any;
}
interface NodePath<T = any> {
node: T;
scope: Scope;
parentPath: NodePath;
isOptionalMemberExpression(): boolean;
isOptionalCallExpression(): boolean;
isPattern(): boolean;
get(key: string): NodePath;
replaceWith(node: any): void;
findParent(callback: (path: NodePath) => boolean): NodePath | null;
}
// AST Node types
interface OptionalMemberExpression {
type: "OptionalMemberExpression";
object: Expression;
property: Expression;
computed: boolean;
optional: boolean;
}
interface OptionalCallExpression {
type: "OptionalCallExpression";
callee: Expression;
arguments: Expression[];
optional: boolean;
}
interface Expression {
type: string;
}
interface Scope {
isStatic(node: any): boolean;
maybeGenerateMemoised(node: any): any;
path: NodePath;
}The plugin handles various edge cases:
this context for method calls@babel/helper-plugin-utils: Core plugin utilities@babel/helper-skip-transparent-expression-wrappers: Expression wrapper handling@babel/plugin-syntax-optional-chaining: Syntax parsing support@babel/core: Babel core library (^7.0.0-0)The plugin requires Node.js >= 6.9.0 and integrates with the Babel 7+ ecosystem.