@saasira/holi
v0.1.9
Published
Modern UI Components Library with i18n, themes, PWA, SW
Readme
Holi
Holi is a template-driven frontend component library focused on progressive enhancement, declarative rendering, and secure expression-based bindings.
It is designed for teams that want reusable UI components without committing to a virtual DOM framework or Shadow DOM component model.
Philosophy
Holi follows strict architectural principles:
- HTML templates are the source of truth (
<template>+<slot>). - Component JS must not generate structure using inline HTML strings.
- Rendering is declarative through attributes such as
data-if,data-repeat, and@{...}interpolation. - Components auto-discover and auto-render on page load (with explicit lazy transform opt-in).
- Expression evaluation is secure-by-design (no
eval, nonew Function). - No Shadow DOM.
- Progressive enhancement first.
- Multiple component libraries and pluggable content providers are supported.
Canonical reference: docs/holi-principles.md.
What You Get
- Auto initialization on
DOMContentLoadedviaHoliApp.init(document). - Three discovery styles for each component:
- Tag:
<tabs></tabs> - Attribute:
<section component="tabs"></section> - Role:
<section role="tabs"></section>
- Tag:
- Component-template bundling (
dist/components.html) and runtime in-memory template registration. - Template bindings:
@{expression}interpolationdata-if,data-show,data-open,visibledata-repeatloops with contextual item/index
- Lifecycle-aware component registry with DOM mutation observation.
- Content provider pattern for dynamic/lazy content per component.
- jQuery-like utility surface (
Q) and native HTTP helper (HTTP). - Prebuilt component set including accordion, calendar, carousel, chart, datagrid, datatable, dialog, drawer, dropdown, form controls, gallery, tabs, toast, tree, wizard, and more.
Declarative Dependencies
Holi supports declarative dependent updates between components.
- Source components publish change notifications through the central runtime.
- Subscriber components declare interest in one or more source components.
- The framework only resolves and delivers the notification.
- The subscriber decides how to react based on its own data source and behavior.
This means "local refresh" vs "remote/PPR reload" is not decided by the framework. It is decided by the subscriber component after it receives the dependency update.
Common attributes:
- Source side:
update,render,data-ppr-update - Subscriber side:
data-ppr-listen,data-ppr-source
Common subscriber hook contract:
handlePprUpdate(payload)refreshPpr(payload)refresh(payload)updateView(payload)
JSF-style target tokens such as @this, @parent, @form, @all, and explicit ids are supported by the dependency resolver.
Quick Start
npm install
npm run build
npm run serveMain artifacts:
dist/holi.jsdist/holi.cssdist/components.html(component templates)dist/layouts.html(layout templates)dist/holi.html(legacy compatibility bundle)dist/layouts/**(optional runtime-resolved layouts)- Example site output:
public/examples/**
CDN Quick Start
For application pages that consume Holi directly from a CDN, use the published npm package via jsDelivr:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/holi.css" />
<script src="https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/holi.js"></script>
<link rel="preload" as="fetch" href="https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/components.html" crossorigin="anonymous" />Direct file URLs:
https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/holi.jshttps://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/holi.csshttps://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/components.html
Pin an exact version in production so releases remain repeatable.
CDN Usage
Holi can be shipped directly from a free CDN after publishing the package to npm.
Recommended jsDelivr links for v0.1.5:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/holi.css" />
<script src="https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/holi.js"></script>
<link rel="preload" as="fetch" href="https://cdn.jsdelivr.net/npm/@saasira/[email protected]/dist/components.html" crossorigin="anonymous" />Fallback unpkg links:
<link rel="stylesheet" href="https://unpkg.com/@saasira/[email protected]/dist/holi.css" />
<script src="https://unpkg.com/@saasira/[email protected]/dist/holi.js"></script>
<link rel="preload" as="fetch" href="https://unpkg.com/@saasira/[email protected]/dist/components.html" crossorigin="anonymous" />Repository setup details for automated npm publishing are documented in docs/CDN.md.
Minimal Usage
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/dist/holi.css" />
<script src="/dist/holi.js"></script>
</head>
<body>
<tabs provider="demo" data-source="@{demo.simple}"></tabs>
<script>
const tabData = [{ label: "Overview", content: "Overview content" }];
window.contentProviders = window.contentProviders || {};
window.contentProviders.demo = class {
resolve(expr) {
return expr === "@{demo.simple}" ? tabData : [];
}
async getContent(item) {
return item?.content || "";
}
};
</script>
</body>
</html>Project Structure Contracts
- Component scripts:
src/scripts/components/<componentname>.js - Component styles:
src/styles/components/<componentname>.css - Component templates:
src/templates/components/<componentname>.html - Shared utilities:
src/scripts/utils/<utilityname>.js - Optional layout templates:
- library layouts:
src/templates/layouts/*.html - example/app layouts in examples:
src/examples/layouts/*.html
- library layouts:
- Example sources:
src/examples/pages/*.htmlsrc/examples/styles/*.csssrc/examples/scripts/*.js
- Built examples:
public/examples/**(referencingdist/holi.jsanddist/holi.css)
Page Layout Composition
Holi supports runtime page composition with a layout template plus named placeholders.
Supported structure:
<page layout="3x9" layouts-base="/examples/layouts/">
<block name="head">
<region name="meta">
<meta name="page-layout-demo" content="true" />
</region>
<region name="styles">
<style>.page-title { letter-spacing: 0.02em; }</style>
</region>
<region name="scripts">
<script>window.pageLayoutExampleLoaded = true;</script>
</region>
</block>
<block name="header">
<h1 class="page-title">Project Dashboard</h1>
</block>
<block name="main">
<region name="lhs"><div>Navigation</div></region>
<region name="middle"><div>Primary content</div></region>
<region name="rhs"><div>Context tools</div></region>
</block>
</page>Layout template example:
<template id="page-layout-3x9" data-layout="3x9">
<layout-head data-layout-head="true">
<meta name="example-layout" content="3x9" />
<slot name="meta"></slot>
<slot name="styles"></slot>
<slot name="scripts"></slot>
</layout-head>
<header><slot name="header"></slot></header>
<slot name="main">
<main>
<aside><slot name="lhs"></slot></aside>
<section><slot name="middle"></slot></section>
<aside><slot name="rhs"></slot></aside>
</main>
</slot>
</template>Rules:
- Top-level
block[name]fills a matching layout slot. - Direct
region[name]inside a block fills matching nested slots inside that block's assigned subtree. - Missing slots are empty by default.
- Set
inherit-missing="true"onpageto keep fallback slot content. - Use
renderer="browser"on source pages to show the built-in placeholder until runtime composition completes. - After browser or compiler rendering completes, both
rendererandrenderedshould be removed from the final page node. - Use
layout-srcfor an explicit layout file orlayouts-basefor a layout folder. - Page-level attributes
title,description,canonical, andlangare applied to the real document metadata. - Page-level
themeis applied to the real<body>element for theme scoping and switching. - Use
<layout-head data-layout-head="true">for shared head assets in layouts. - Use
<tail data-layout-tail="true">for deferred body-end assets in layouts. - Non-script nodes from
layout-headgo to the real HTML<head>. - Nodes from
tailare appended near the end of<body>. - Built-in region
metaalways targets the real HTML<head>. - Built-in regions
stylesandpage-stylesalways target the real HTML<head>, even when the layout does not declare those slots. - Built-in regions
scriptsandpage-scriptsalways target the end of<body>, even when the layout does not declare those slots. - Use
stylesfor external asset nodes such as<link rel="stylesheet" href="...">. - Use
scriptsfor external asset nodes such as<script src="..."></script>. - Use
page-stylesfor inline<style>tags. - Use
page-scriptsfor inline<script>tags.
Holi vs React / Angular / Vue
| Area | Holi | React | Angular | Vue |
|---|---|---|---|---|
| Primary model | HTML template + declarative attrs | Component functions + JSX | Framework with DI, modules, templates | SFC/templates + reactivity |
| Runtime style | Direct DOM + template clone/bind | Virtual DOM | Framework-managed change detection | Virtual DOM + compiler/runtime |
| Auto discovery | Yes (tag/component/role selectors) | No (explicit mount) | No (bootstrapped app) | No (explicit mount) |
| Progressive enhancement | First-class | Possible, not default | Usually app-shell centric | Possible, typically app-centric |
| Shadow DOM | Not used | Not required | Optional with Angular Elements | Not required |
| Secure expression engine | Built-in @{...} parser (no eval) | N/A (JS expressions in render code) | Template parser | Template parser |
| Best fit | Multi-page apps, server-rendered pages, declarative component islands | Large SPAs and highly interactive app UIs | Enterprise apps needing full framework conventions | Progressive SPAs and mixed-complexity apps |
Practical summary:
- Choose Holi when your HTML-first architecture, declarative enhancement, and low-framework runtime footprint are priorities.
- Choose React/Angular/Vue when you need the broader ecosystem around SPA routing/state tooling, compile-time optimizations, and framework-level DX conventions.
Development Scripts
npm run build: Build library and examples.npm run build:dev: Development build and examples.npm run build:examples: Build example pages intopublic/examples.npm run serve: Build examples and start webpack dev server.npm run smoke:examples: Run example smoke checks.npm run ci:smoke: Full build + smoke.
Notes
- Holi exports
window.HoliApp/window.Holi. - Auto init can be disabled with
window.HoliAutoInit = falsebefore loadingdist/holi.js. - Component templates are loaded from
dist/components.html(with runtime fallback paths). - Layout templates are loaded from
dist/layouts.html(with runtime fallback paths).
OfflineIndicator
OfflineIndicator provides a template-driven connectivity banner with automatic polling and queue count display.
Supported declaration styles:
<offline></offline><section component="offline"></section><section role="offline"></section>[data-offline]
Useful attributes:
scope="page|block|inline"position="top-left|top-right|bottom-left|bottom-right|top-center|bottom-center"host="#selector"forscope="block"ping-url="/api/ping"heartbeat-ms="2500"probe-timeout-ms="1800"duration="1200"max-attempts="4"
Instance/static helpers:
el.offlineIndicator.retryConnection()el.offlineIndicator.simulateOffline()el.offlineIndicator.simulateOnline()window.OfflineIndicator.queue(payload)window.OfflineIndicator.clearQueue()window.OfflineIndicator.create(options)
See offline example for page-scoped, block-scoped, and queue-sync scenarios.
