Developing a Babel Plugin to Automatically Add Optional Chaining (?.) and Prevent TypeError
The article explains how to create a custom Babel plugin that automatically transforms risky member and call expressions into optional‑chaining equivalents during bundling, using configurable include/exclude patterns and short‑circuit optimizations, thereby preventing TypeError in large JavaScript codebases while noting a modest bundle‑size increase.
Background
In JavaScript, accessing a property of null or undefined throws a TypeError. ECMAScript 2020 introduced the optional chaining operator ?., which safely returns undefined when a property does not exist, thus preventing the error.
Project Pain Points
Large codebases contain many direct property accesses such as a.b.c.d, which are potential TypeError sources and costly to maintain.
Manually writing optional chaining ( a?.b?.c?.d) is verbose and reduces code readability.
Automatically inserting ?. during the build process can solve both problems.
Solution Overview
A custom Babel plugin is created to transform risky member and call expressions into their optional equivalents at bundling time, eliminating TypeError without changing the original source code.
Core Plugin Implementation
<span>import { declare } from '@babel/helper-plugin-utils';</span>
<span>import * as t from '@babel/types';</span>
<span>export default declare((api, options) => {</span>
<span> // Only Babel 7 is supported</span>
<span> api.assertVersion(7);</span>
<span> return {</span>
<span> name: 'babel-plugin-auto-optional-chaining',</span>
<span> visitor: {</span>
<span> 'MemberExpression|CallExpression|OptionalMemberExpression|OptionalCallExpression'(path) {</span>
<span> // Skip already processed nodes</span>
<span> if (path.node.extra?.hasAoc) return;</span>
<span> const isMeCe = path.isMemberExpression() || path.isCallExpression();</span>
<span> if (isMeCe && !isValidPath(path, options)) return;</span>
<span> // Property access</span>
<span> if (path.isMemberExpression() || path.isOptionalMemberExpression()) {</span>
<span> const ome = t.OptionalMemberExpression(path.node.object, path.node.property, path.node.computed, true);
<span> if (!shortCircuitOptimized(path, ome)) {</span>
<span> path.replaceWith(ome);</span>
<span> }</span>
<span> }</span>
<span> // Method call</span>
<span> if (path.isCallExpression() || path.isOptionalCallExpression()) {</span>
<span> const oce = t.OptionalCallExpression(path.node.callee, path.node.arguments, false);
<span> if (!shortCircuitOptimized(path, oce)) {</span>
<span> path.replaceWith(oce);</span>
<span> }</span>
<span> }</span>
<span> // Mark as processed</span>
<span> path.node.extra = { ...(path.node.extra || {}), hasAoc: true };
<span> }</span>
<span> }</span>
<span> };</span>
<span>});</span>The plugin recognises MemberExpression and CallExpression nodes, optionally filters them using includes / excludes configuration, and replaces them with OptionalMemberExpression or OptionalCallExpression. It also optimises short‑circuit && expressions.
Configuration
<span>module.exports = {</span>
<span> plugins: [</span>
<span> ['babel-plugin-auto-optional-chaining', {</span>
<span> excludes: [</span>
<span> 'new .*', // e.g., new a.b() should not be transformed</span>
<span> 'process.env.*' // environment variables stay unchanged</span>
<span> ],</span>
<span> // includes: [],</span>
<span> // optimizer: false</span>
<span> }]</span>
<span> ]</span>
<span>};</span>Three options are supported: includes – only transform the specified patterns (higher priority than excludes). excludes – patterns that should be ignored. optimizer – set to false to disable &&‑expression optimisation.
Limitations
Automatic insertion of ?. may increase the final bundle size slightly, which could affect page load performance.
Related Plugins
For browsers that do not support optional chaining (e.g., Chrome < 80), the official @babel/plugin-transform-optional-chaining can be used to transpile the code back to a compatible form.
Testing
Test cases are written with babel-plugin-tester . Example input and expected output snippets demonstrate the transformation of regular property accesses, method calls, and &&‑optimised expressions.
<span>// Input</span>
<span>const x = a.b.c.d;</span>
<span>// Output</span>
<span>const x = a?.b?.c?.d;</span>Comprehensive test suites cover edge cases such as bracket notation, unary operators, short‑circuit logic, and exclusion patterns.
Conclusion
The article shows how to build a Babel plugin that automatically adds optional chaining during the build step, effectively preventing TypeError in JavaScript projects and improving code robustness.
References
tc39/proposal-optional-chaining
Optional chaining (?.) on MDN
Babel Plugin Handbook
Babel's optional chaining AST spec
ESTree Specification
eslint/no-unsafe-optional-chaining
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
