Lunchbox Overview

Lunchbox aims to be as thin a layer as possible between Vue and Three.js, but there's still plenty of baseline work to do before that layer is functional. We'll focus on two key components of Lunchbox here: the MiniDom and the Lunchbox core.

MiniDom

TL;DR

Lunchbox uses a custom library called MiniDom (in minidom.ts) to replicate HTML's built-in ancestor/descendent/sibling capabilities.

You shouldn't need to worry about this most of the time, but it's important to note that a Lunchbox app's structural source of truth is the MiniDom, and that the MiniDom is built of nodes similar to the nodes in the DOM.

Since Vue 3 is a DOM-first framework, many of its operations are geared toward properties and methods the DOM naturally contains (see MDN for Element, its parent interface Node, and Node's parent interface EventTarget, for a more complete list).

const standardDomElement = document.querySelector('div')

// standardDomElement contains:
// * parentElement
// * parentNode
// * nextSibling
// * insertBefore
// * removeChild
// * ...and many more

In Vue 3's DOM renderer, we can see this functionality used in several places in the runtime-dom package (from time of writing, spring/summer 2022 - permalink):

// examples pulled from Vue's DOM renderer
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },
// ...
  remove: child => {
    const parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },
// ...
  parentNode: node => node.parentNode as Element | null,

  nextSibling: node => node.nextSibling,
// ...etc

MiniDom aims to recreate some of those properties (as well as a few from the tree-model npm package that was originally used) to make a DOM-like but otherwise agnostic hierarchy structure.

You can see its implementation in minidom.ts in the source code. It should be mostly complete for Lunchbox's purposes, but it's important to remember that it exists - a Lunchbox app's structural source of truth is the MiniDom.

Lunchbox Core

With the MiniDom in mind, we can talk about Lunchbox internals in a few steps:

  • Bootstrapping
  • App mounting & assumptions

Bootstrapping

When you create a new Lunchbox app, a few things happen:

  1. A renderer is created using Vue's custom renderer API and Lunchbox's nodeOps.
  2. An app is created with this renderer using the provided root, like a standard Vue app.
    • In this app, the components listed here are automatically registered.
    • A few extra functions are attached to the app to allow managing custom renderers, registering new components, etc (see Composables in the main docs).
    • The mount function is modified to create a MiniDom root node.
  3. The app is returned for the user to mount, like a standard Vue app.

Most of the implementation is left to Vue to handle - the original mount function becomes part of the updated mount function, for example, and the only assumption made about Three.js so far is that the automatically-generated components exist in the library.

App Mounting & Assumptions

When a Lunchbox app mounts, the hierarchy looks like this in the MiniDom:

| Root

Per the documentation, the first component should be a <Lunchbox> component, whose configuration is contained here.

Several things happen in a <Lunchbox> component's onMounted callback, in this order:

  • A Renderer is pulled from the corresponding slot or created (default: WebGLRenderer) if none provided (which is the majority of use cases)
  • A Scene is pulled from the corresponding slot or created if none provided (which is the majority of use cases)
  • A Camera is pulled from the corresponding slot or created (default: PerspectiveCamera or OrthographicCamera, depending on <Lunchbox> props) if none provided (which is the majority of use cases)
  • Device pixel ratio is saved and the canvas (provided in <Lunchbox>'s render function) has resize observers attached (handling canvas size, camera calculations, etc).
  • Callbacks registered via clientside onStart functions are run
  • The main update loop is started and its teardown function is prepped for onBeforeUnmount.

In our experience, the vast majority of Three.js apps contain one Renderer, Camera, and Scene. Setting all of these up on each app was very repetitive in development, so Lunchbox apps needed a way to make sure those entities existed by default. This means that by the time onStart functions run, the MiniDom hierarchy looks like this:

| Root
| -- Renderer
| -- Scene
| -- Camera

At this point, the app can start mounting other components, usually as children of the Scene node.

Continue to the next section to learn more about overriding these assumptions.