Interface in Vanilla JavaScript

TL;DR Here's what an interface looks like in Vanilla JavaScript, using VS Code IntelliSense: var interface = () => null; var InterfaceOptions = () => ({ name: '', }); InterfaceOptions = interface; let opt = InterfaceOptions`` ?? { name: 'Bagel', }; function createItem(options = InterfaceOptions``) { // ... } createItem(opt); Discovery 1) I want VS Code IntelliSense to suggest the "shape" of the createBox() options. 2) Using default parameter works, but I want to place it somewhere else to declutter a bit. 3) Declaring the options outside the function creates a bug because anyone can tinker with the value. 4) So It must be an object builder. On line 5, I use backticks instead of parentheses to differentiate an "interface" from a function invocation. Actually, I should just use a unique prefix for the variable name such as InterfaceBoxOptions or something for this post, oh well. 5) Okay, that works, but what if I declare the options as their own variable? How am I supposed to tell IntelliSense that an object has the shape of an interface? 6). As you may know, IntelliSense assumes the interface shape if I first assign the interface to the object. 7) Apparently, though, it still works even after reassign the variable itself with a new object. Now, that's wild west! 8) But this looks too cluttered. Can I make it a one-liner at least? 9) Answer is yes, using the nullish coalescing (??) operator. This is the only way I’ve found. One problem though, to assign the new object instead of the interface, I need to somehow make the boxOptions returns null. 10) Luckily—or perhaps intentionally by design—IntelliSense keeps suggesting the initial shape of the interface even after reassigning it to a function that returns null. And just like that, I've got a working interface-like setup in vanilla JavaScript. Should probably use typescript from the start, but I belong to the wild west. In Production For object declaration, I write a build script to replace interfaceName ?? with empty string before passing it to Terser because the compressor can't tell that it is unused. Before: let opt = InterfaceOptions`` ?? { name: null, } After: let opt = { name: null, } Compressed code if you don't remove the interface: let opt = () => null ?? { name: null, } Trivia 1. Use Var for Interfaces For interfaces, you want to use var instead of letor const. This makes sure it got removed when you compress at top level using Terser. var interface = () => null; var InterfaceOptions = () => ({ name: null, }); InterfaceOptions = interface; // terser options { toplevel: true, compress: true, // ... } 2. Null Interface Alternative If global interface function is not available, e.g. if you're writing library for someone else, instead of this: var interface = () => null; var InterfaceOptions = () => ({ name: null, }); InterfaceOptions = interface; you can do this: var interface = () => null; var InterfaceOptions = () => ({ name: null, }); InterfaceOptions = () => null;

Jan 17, 2025 - 08:17
Interface in Vanilla JavaScript

TL;DR

Here's what an interface looks like in Vanilla JavaScript, using VS Code IntelliSense:

var interface = () => null;
var InterfaceOptions = () => ({
  name: '',
}); InterfaceOptions = interface;

let opt = InterfaceOptions`` ?? {
  name: 'Bagel',
};

function createItem(options = InterfaceOptions``) {
  // ...
}

createItem(opt);

Image description

Image description

Discovery

1) I want VS Code IntelliSense to suggest the "shape" of the createBox() options.

2) Using default parameter works, but I want to place it somewhere else to declutter a bit.
Declaring default parameter outside the function

3) Declaring the options outside the function creates a bug because anyone can tinker with the value.

Possible bug: accidental value change

4) So It must be an object builder. On line 5, I use backticks instead of parentheses to differentiate an "interface" from a function invocation. Actually, I should just use a unique prefix for the variable name such as InterfaceBoxOptions or something for this post, oh well.

Creating object builder for interface

5) Okay, that works, but what if I declare the options as their own variable? How am I supposed to tell IntelliSense that an object has the shape of an interface?

Interface for object

InteliSense on object properties

6). As you may know, IntelliSense assumes the interface shape if I first assign the interface to the object.

InteliSense assuming an object shappe

7) Apparently, though, it still works even after reassign the variable itself with a new object. Now, that's wild west!

InteliSense assuming an object shape after reassigning

8) But this looks too cluttered. Can I make it a one-liner at least?

Cluttered object with interface-like declaration

9) Answer is yes, using the nullish coalescing (??) operator. This is the only way I’ve found. One problem though, to assign the new object instead of the interface, I need to somehow make the boxOptions returns null.

Using nullish coalescing to trick IntelliSense to assume object shapes

10) Luckily—or perhaps intentionally by design—IntelliSense keeps suggesting the initial shape of the interface even after reassigning it to a function that returns null.

And just like that, I've got a working interface-like setup in vanilla JavaScript. Should probably use typescript from the start, but I belong to the wild west.

A working example of interface in Vanilla JavaScript

In Production

For object declaration, I write a build script to replace interfaceName ?? with empty string before passing it to Terser because the compressor can't tell that it is unused.

Before:

let opt = InterfaceOptions`` ?? {
  name: null,
}

After:

let opt = {
  name: null,
}

Compressed code if you don't remove the interface:

let opt = () => null ?? {
  name: null,
}

Trivia

1. Use Var for Interfaces

For interfaces, you want to use var instead of letor const. This makes sure it got removed when you compress at top level using Terser.

var interface = () => null;

var InterfaceOptions = () => ({
  name: null,
}); InterfaceOptions = interface;
// terser options
{
  toplevel: true,
  compress: true,
  // ...
}

2. Null Interface Alternative

If global interface function is not available, e.g. if you're writing library for someone else, instead of this:

var interface = () => null;
var InterfaceOptions = () => ({
  name: null,
}); InterfaceOptions = interface;

you can do this:

var interface = () => null;
var InterfaceOptions = () => ({
  name: null,
}); InterfaceOptions = () => null;