This is a retrospetive trasformation plugin of EcmaScript optional chaining syntax.
- Because the output of default built-in @babel/plugin-syntax-optional-chaining is too wordy and unreadable
- Remember the good old day when we use keyPath helpers like lodash.get to safely get a key deep inside an object?
- To sqeeze every possible bit of space in the javascript bundle.
- I want to try fiddling with babel myself :)
Assume we have this object with some deeply nested keys:
const obj = {
alpha: {
beta: {
charlie: {
delta: "hello",
},
},
},
};
And we have some code snippets getting optional values deep inside
// 108 Bytes (comments excluded)
// 1x size, take it as a baseline
const x = obj?.alpha?.beta?.charlie;
// x = {delta:"hello"}
const y = obj?.alpha?.beta.charlie?.delta;
// y = "hello"
const z = obj?.alpha?.fox
// z = undefined
(Try it yourself on the Babel Repl)
// 790 Bytes, 7.31x inflation
// Code length complexity ~ O(7N)
"use strict";
var _obj, _obj$alpha, _obj$alpha$beta, _obj2, _obj2$alpha, _obj2$alpha$beta$char, _obj3, _obj3$alpha;
const x = (_obj = obj) === null || _obj === void 0 ? void 0 : (_obj$alpha = _obj.alpha) === null || _obj$alpha === void 0 ? void 0 : (_obj$alpha$beta = _obj$alpha.beta) === null || _obj$alpha$beta === void 0 ? void 0 : _obj$alpha$beta.charlie;
const y = (_obj2 = obj) === null || _obj2 === void 0 ? void 0 : (_obj2$alpha = _obj2.alpha) === null || _obj2$alpha === void 0 ? void 0 : (_obj2$alpha$beta$char = _obj2$alpha.beta.charlie) === null || _obj2$alpha$beta$char === void 0 ? void 0 : _obj2$alpha$beta$char.delta;
const z = (_obj3 = obj) === null || _obj3 === void 0 ? void 0 : (_obj3$alpha = _obj3.alpha) === null || _obj3$alpha === void 0 ? void 0 : _obj3$alpha.fox;
// runtime keyPath helper
// a constant size dependency
import get from "lodash.get"
// 165 Bytes below, 1.53x inflation
// Code length complexity ~ O(C+1.5N)
"use strict";
const x = get(obj, ["alpha", "beta", "charlie"]);
const y = get(get(obj, ["alpha", "beta"]).charlie, "charlie");
const z = get(obj, ["alpha", "fox"]);
We can see the transpiled code using a runtime keyPath
function costs 1.53x
inflation vs the 7.31x
overhead in the current babel transpiler.
That is ~4.78x
save of space in our naive example. Of course, the optional chaining won't normally happen that much in your code.
I'm glad you asked.
- A constant cost of code length from runtime
keyPath
function, depending on its implementation. Here we provide 2 options:lodash.get
: The most commonly usedkeyPath
helper, costs 4.4KB minified, or 1.8KB minified and gzippeddlv
: A geniously minimalist implementation from the author of PreactJS, costs 253B minified, or 191B minified and gzipped.- The extra cost is easily negated after a few or even one usage of optional chaining.
- You can technically get the helper function for free if you have it already in your dependency.
- Some minor performance overhead.
- The retro output using the
dlv
helper sets me back by ~30% on my laptop vs the vanilla babel output. - Run the benchmark here on your own device.
- The retro output using the