Skip to content

Latest commit

 

History

History
87 lines (67 loc) · 3.7 KB

File metadata and controls

87 lines (67 loc) · 3.7 KB

babel-plugin-optional-chaining-retro

What is this:

This is a retrospetive trasformation plugin of EcmaScript optional chaining syntax.

Why?

  • 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 :)

Example

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

Transpiled by the vanilla babel

(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;

Transpiled by babel with this retro plugin

// 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.

Sounds good, but what's the catch?

I'm glad you asked.

  • A constant cost of code length from runtime keyPath function, depending on its implementation. Here we provide 2 options:
  • 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.