@vettvangur/framework
v0.0.27
Published
Tiny runtime that lazy-mounts "features" onto DOM nodes by CSS selector. The selector → loader map decides what runs where; the runtime handles when (initial paint, scroll into view, dynamic insertions) and how to clean up.
Maintainers
Keywords
Readme
@vettvangur/framework
Tiny runtime that lazy-mounts "features" onto DOM nodes by CSS selector. The selector → loader map decides what runs where; the runtime handles when (initial paint, scroll into view, dynamic insertions) and how to clean up.
Install
pnpm add @vettvangur/frameworkQuick start
import { vettvangur } from '@vettvangur/framework'
const dispose = vettvangur(document, {
'.accordion, [data-accordion]': () => import('./features/accordion'),
'.tabs': () => import('./features/tabs'),
})
// On SPA navigation:
dispose()A feature module exports a factory as default:
// features/accordion.ts
import type { Feature } from '@vettvangur/framework'
const accordion: Feature = (root) => {
const onClick = () => { /* ... */ }
root.addEventListener('click', onClick)
return {
mount() { /* runs after import resolves */ },
unmount() {
root.removeEventListener('click', onClick)
},
}
}
export default accordionWhat you get
- Eager-mount in viewport, lazy-mount everywhere else. Elements within ~200px of the viewport import their feature module immediately; everything else waits for
IntersectionObserver. - Dynamic insertions are handled. A
MutationObservermounts new matching nodes and unmounts removed ones automatically. - Multiple features per element. If an element matches several selectors, every matching feature is mounted independently.
- De-duplicated imports. A
WeakMap-cached loader promise means N elements using the same() => import('./x')only fetch the module once. - Disposers.
vettvangur()returns a function that disconnects both observers; each feature'sunmountis called when its host element is removed.
API
vettvangur(root, moduleMap) → () => void
Mount features under root. Returns a disposer that disconnects the IntersectionObserver and MutationObserver — call it on SPA route change to free up listeners.
| Param | Type | Description |
| --- | --- | --- |
| root | ParentNode | Search root. Defaults to document. |
| moduleMap | Record<string, () => Promise<{ default: Feature }>> | Selector list → dynamic import. Keys may contain comma-separated selectors. |
Helpers
onDOMContentLoaded(fn)— SSR-safe; returns a disposer.preloadTimeout()— Togglespreload-*body classes on a 100/400ms cadence; returns a disposer.enableZoom()— Adds theenable-zoombody class once.verticalResizeCheck()— Watches resize events and exposesisVerticalResize()getter; returns a disposer.
Types
type FeatureProps = Record<string, unknown>
interface FeatureInstance {
mount(): void
unmount(): void
}
type Feature = (root: HTMLElement, props?: FeatureProps) => FeatureInstance
type Loader = () => Promise<{ default: Feature } | Feature>Notes
- A feature is mounted at most once per
(element, loader)pair. Callingvettvangur()again with the same map is safe. - Errors in
mount()are logged toconsole.errorand swallowed so a single broken feature doesn't break the whole page. - Errors in
unmount()are silently swallowed to avoid breaking DOM tear-down flows.
