Last active
August 27, 2024 12:47
-
-
Save barneycarroll/ba7f6c253ae04e44b2cb95a7cda67e63 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Mithril dev harness backend | |
// | |
// Exports a `registry` of components, active `instances`, an | |
// `activeTree` with the hierarchicy of components in vdom structure, | |
// and a `wrapper` to be used instead of `m` hyperscript. | |
// | |
// Intended for use as a backend to a GUI for visual inspection & | |
// debugging of the Mithril virtual DOM tree. | |
// Each component called by Mithril needs to be wrapped in a | |
// special component that monitors lifecycle and input so | |
// we can observe the state of the application | |
const wrappedComponents = new Map | |
// A runtime registry of components: | |
// keys are the original component definitions, | |
// values are sets of all instances represented by symbol identifiers | |
export const registry = new Map | |
// A map of active component instances: | |
// keys are the symbol identifiers as stored in the registry | |
// values are the latest vnode for that that instance | |
export const instances = new Map | |
// A representation of the currently rendered component tree | |
export const activeTree = [] | |
// Current position in the active tree during render | |
let cursor | |
// Clear the tree on every draw loop | |
m.mount(document.createDocumentFragment(), { | |
view(){ | |
activeTree.length = 0 | |
cursor = activeTree | |
} | |
}) | |
// The function will be used instead of Mithril's `m` | |
export function wrapper(tag, ...input){ | |
// If it's not a component, pass straight through to Mithril | |
if(typeof tag !== 'function' && typeof tag.view !== 'function') | |
return m(...arguments) | |
// If it is, create a wrapped component or retrieve the existing one | |
const WrappedComponent = getSet(wrappedComponents, tag, wrapComponent) | |
return m(WrappedComponent, ...input) | |
} | |
// Factory for creating wrapped components | |
function wrappedComponent(Component){ | |
// Create a registry for component instances of this type | |
registry.set(Component, new Set) | |
// The actual wrapped component definition | |
return function DevComponent(){ | |
const id = new Symbol('component identifier') | |
registry.get(Component).add(id) | |
return { | |
view({attrs, children}){ | |
const branch = cursor | |
const subtree = [] | |
cursor.push(id, subtree) | |
cursor = subtree | |
const vnode = m(Component, attrs, children) | |
cursor = branch | |
instances.set(id, vnode) | |
return vnode | |
}, | |
onremove(){ | |
instances.delete(id) | |
registry.get(Component).delete(id) | |
}, | |
// To ensure teardown expectations, we must ensure any | |
// `onbeforeremoves` bound to the underlying component are | |
// executed in the place of this one | |
onbeforeremove(){ | |
return Promise.all( | |
[it.state.onbeforeremove, it.attrs.onbeforeremove].map(obr => | |
obr?.apply?.(this, arguments) | |
) | |
) | |
}, | |
} | |
} | |
} | |
// If map has key, return it; otherwise create it, set it & return it | |
function getSet(map, key, factory){ | |
if(map.has(key)) | |
return map.get(key) | |
const value = factory(key) | |
map.set(key, value) | |
return value | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment