Skip to content

Instantly share code, notes, and snippets.

@barneycarroll
Last active August 27, 2024 12:47
Show Gist options
  • Save barneycarroll/ba7f6c253ae04e44b2cb95a7cda67e63 to your computer and use it in GitHub Desktop.
Save barneycarroll/ba7f6c253ae04e44b2cb95a7cda67e63 to your computer and use it in GitHub Desktop.
// 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