A former colleague, who is a regular employee at SplitmediaLabs, started to contribute to XJS 3.0, and we're currently having a dilemma whether to go and fill the world with objects which we are doing right now, or make a u-turn, change our approach, and go with the functional route.

I always wanted the framework to be modular, ala lodash, ala date-fns. This is due to one of the primary objective that the resulting bundle of the consumer would not get bloated a lot by importing XJS. With that said, I am torn apart from the 2 approaches.

The Functional approach

Here's how it would've look like if we would go for the functional route:

const xjs = createXjs();
const view = await getView(xjs, 0);
const scene = await getCurrentScene(view);
const items = await getItems(scene);

items.forEach(async (item) => {
  const name = getCustomName(item);
  console.log(name);
});

This would look a lot better if JavaScript pipe operator gets implemented:

const xjs = createXjs();

const items = await xjs
|> getView(0)
|> getCurrentScene
|> getItems;

items.forEach(async (item) => {
  const name = getCustomName(item);
  console.log(name);
});

Now that looks like a proper FP flow.

To dig in a little deeper on how the implementation would work, createXjs would return an object that would contain the configuration of the "xjs instance". Off the top of my head, the object would be something like:

type Configuration = {
  id: string; // could be generated by uniqid
  internal: Internal;
  type: string; // Could be `local`, `remote`, or `proxy`
}

type Internal = {
  exec: (fn: string, ...args: string[]) => Promise<string>;
  // maybe something else here in the future?
}

The return value of each other functions would be some sort of a tuple (yes, yes, there's no "tuple" in JS, but we can make use of array destructuring to get a similar effect). The first value would be a copy of the XJS configuration, while the second value would be the actual value.

// In view.ts
async function getView(xjs: Configuration, index: number) {
  // While wrting this down, maybe it does not make sense to have
  // a separate function to "get a view"?
  return [xjs, index];
}

// In scene.ts
async function getCurrentScene(config: [Configuration, number]) {
  const [xjs, index] = config;
  const scenes = await xjs.internal.exec(`scene:${index}`);
  
  return [xjs, scenes];
}

Now, since we had a glimpse on how a FP approach would look like, let us move on to an OOP approach, which is similar to how it's currently done.

The OOP approach

The code usage would be something like:

const xjs = new Xjs();

const view = await xjs.getView(0);
const scene = await view.getCurrentScene();
const items = await scene.getItems();

items.forEach((item) => {
  const name = await item.getCustomName();
  console.log(name);
});

Now, that's easy to grasp isn't it?

But the problem that would arise is that simply importing Xjs would import all of the other files. Let's annotate the code above with their corresponding file source:

const view = await xjs.getView(0); // returns a View from view.ts
const scene = await view.getCurrentScene(); // returns a Scene from scene.ts
const items = await scene.getItems(); // returns a Item[] from item.ts

Now, imagine that xjs also has other methods to get, lets say, the plugin configuration window. We're not using it in the sample code above, but with the OOP approach, the resulting bundled file will include the configuration window's file.

The reason is because xjs.ts would need to import plugin-config-window.ts to use it in its getPluginConfigurationWindow method.

So, the end result is that we're importing code that we're not going to use in our plugin. We're "bloating" our bundled JS file with unnecessary code that would never be called.

Conclusion

The functional approach certainly does allow us to import the code that we only need.

The OOP approach feels a little more natural though. Developers are usually familiar with OOP, and thus the friction to use the library would be less than it would be if we opt to go for the functional approach.

I honestly am not yet settled on either, and would need to talk about this with another developer.