Sigment Guide
Introduction
Sigment is a lightweight reactive framework designed for developers who want speed, simplicity,
and full control of the DOM without virtual DOM and transpilers.
Installation
Install via npm:
npm install sigment
Or create a new project:
npx create-sigment-app my-app
π Project Structure
Sigment keeps your project clean and organized. Hereβs the recommended structure:
// Root directory
/
βββ src/
β βββ assets/
β β βββ css/
β β βββ index.css
β βββ components/
β β βββ Hello.js
β βββ Main.js
βββ index.html
βββ jsconfig.json
βββ vite.config.js
βββ global.d.ts
βββ package.json
πΉ src/
Directory
assets/
β Contains static files like global CSS.components/
β Reusable components go here.Main.js
β Application entry point.
πΉ Root-Level Files
index.html
β The main HTML page.jsconfig.json
β Helps with module resolution and IntelliSense.vite.config.js
β Vite config for development/build setup.global.d.ts
β TypeScript declaration for global types like CSS modules.package.json
β Project metadata and dependencies.
Example index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
<title>sigment start</title>
<link rel="stylesheet" href="/src/assets/css/index.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/Main.js"></script>
</body>
</html>
π Main Entry Point
Your application starts from Main.js / ts
. It imports your components and mounts them into the DOM using Sigmentβs MyApp
and mount()
API.
import { MyApp, mount } from "sigment";
import Hello from "./components/Hello";
MyApp.cleanHtml(true);
function Main() {
mount("root", Hello());
}
Main();
export default Main;
π Whatβs Happening?
MyApp.cleanHtml(true)
β remove the<properties>
like id and other for clean html recommended to set true just in production.mount("root", Hello())
β Renders theHello
component into theroot
div.- The
Main()
function runs on startup and mounts the app.
π Hello component
Example of Hello component.
export default function Hello() {
return div("Hello, world!");
}
Hello component
β return html and render at the end at root.
Hello World
Create elements directly using tag functions:
document.body.appendChild(
div("Hello Sigment!")
);
Or
export default function App() {
return div("Hello, world!");
}
Main Entry
document.getElementById("root")?.appendChild(App());
Or
mount("root",App())
mount
sigment provide simple way to connect to dom you can use mount :
Main Entry
instead of:
document.getElementById("root")?.appendChild(App());
you can do
mount("root",App())
Nested Elements
You can nest elements directly by placing them as children inside a parent tag function like div
, ul
, etc.
Example β Nested Structure
function NestedExample() {
return div(
h2("Profile"),
p("User Info:"),
ul(
li("Name: Alice"),
li("Email: [email protected]")
)
);
}
The outer div
acts as a container. All child elements are nested inside it, maintaining clean structure.
Reactive Signals
Use createSignal
or just signal
to make reactive state:
const [count, setCount] = signal(0);
button({ onclick: () => setCount(count() + 1) }, count)
createEffect
createEffect
tracks reactive dependencies and runs the provided function when they change. It's useful for side effects like logging, animations, or syncing state.
Why is this useful?
createEffect
helps you respond to state changes without manually wiring up event listeners or re-computing values. It automatically re-runs when any reactive signal used inside changes.
import { signal, createEffect } from "sigment";
function Counter() {
const [count, setCount] = signal(0);
createEffect(() => {
console.log(`Count changed: ${count()}`);
});
return button({
onClick: () => setCount(count() + 1)
}, "Increment");
}
Every time you click the button, the signal count
updates, triggering the effect and logging the new value.
Global Signals
Create global signals with createGlobalSignal
or globalSignal
.
create new file for example globalState.js or ts and add globalSignal like in the example below.
import { createGlobalSignal } from "sigment";
createGlobalSignal('userName', '');
createGlobalSignal('userMail', '');
export const GlobalKeys = {
userName: 'userName',
userMail: 'userMail'
};
To read global from any place in code you can do:
const [userName] = useGlobalSignal(GlobalKeys.userName);
To write global from any place in code you can do:
const [userName, setUserName] = getGlobalSignal(GlobalKeys.userName);
setUserName("some name");
Custom Tags
Dynamically define new tags with addSigment
:
addSigment("card");
card("Content inside card");
// Renders: <s-card>Content inside card</s-card>
addSigment("myCard"); // before capital latter will add dash and the capital latter will convert to lower case
myCard("Custom element");
// Renders: <my-card>Custom element</my-card>
addSigment("card", "div");
card("Alias for div");
// Renders: <div>Alias for div</div>
Using with TypeScript
If you're using TypeScript, declare the types for your custom tags to enable type checking and IntelliSense.
It's best to put these declarations and registrations in a shared file like sigments.ts
.
import { addSigment } from "sigment";
type Child = string | HTMLElement;
type Props<T extends keyof HTMLElementTagNameMap> =
Partial<HTMLElementTagNameMap[T]> & Record<string, any>;
declare global {
function signin(props: Props<"div">, ...children: Child[]): HTMLElement;
function signin(...children: Child[]): HTMLElement;
function sectionheader(props: Props<"section">, ...children: Child[]): HTMLElement;
function sectionheader(...children: Child[]): HTMLElement;
}
addSigment("signin");
addSigment("sectionheader");
export {};
Note: You can use addSigment()
anywhere in your project, regardless of whether you're using TypeScript.
If you're using TypeScript, you only need to define the type declarations like shown above onceβtypically in a shared file like sigments.ts
βbut the addSigment()
call itself can be used anywhere in your codebase.
3 Ways to Bind
Using a Local Signal:
function LocalExample() {
const [name, setName] = signal("");
return div(
div("type your name:",
input({ type: "text", onInput: (e) => setName(e.target.value) })
),
// First way: as a separate child function
div('Hi i am first way to bind', () => name()),
// Second way: inline in template string
div(() => `Hi i am second way to bind, ${name()}`),
// Third way: interpolated placeholder
div(`Hi i am third way to bind: {{name}}`)
);
}
Fragment
Fragment(...children)
allows you to group multiple elements without introducing an actual DOM wrapper.
Example β Without Fragment
function Card() {
return div(
h2("Title"),
p("Some text")
);
}
Renders:
<div>
<h2>Title</h2>
<p>Some text</p>
</div>
Example β With Fragment
function Card() {
return fragment(
h2("Title"),
p("Some text")
);
}
Renders:
<h2>Title</h2>
<p>Some text</p>
Why is this useful?
- No extra DOM nodes β keeps your HTML clean
- Better performance β fewer elements mean less memory and faster rendering
- Zero styling impact β no unexpected box models or spacing from wrappers
- Logical grouping β lets you group elements structurally in code
Note: Fragments render their children in O(1)
time without creating an additional DOM node themselves. Only the children appear in the final DOM.
π¨ Styling in Sigment: Global CSS vs. CSS Modules
Sigment supports both global CSS files and scoped CSS modules β giving you flexibility in how you style your components.
β 1. Importing Global CSS
import './hello.css';
- Effect: All class names defined in
hello.css
are available globally across the app. - Usage: Just pass the class name as a string in the
class
attribute.
Example (hello.css):
.hi {
font-size: 20px;
color: darkblue;
}
In component:
p({ class: "hi" }, 'Hello from Sigment')
π― 2. Using CSS Modules
import styles from './hello.module.css';
- Effect: Class names are automatically scoped (e.g.,
fnt600__hello__abc123
). - Usage: Access them as properties:
styles.className
.
Example (hello.module.css):
.fnt600 {
font-weight: 600;
}
In component:
p({ class: styles.fnt600 }, 'Bold text')
π§ͺ 3. Combining Global and Module Styles
p({ class: "hi " + styles.fnt600 }, 'Mixed styles')
"hi"
β from global CSSstyles.fnt600
β scoped class from CSS Module
π‘ Final Example
import './hello.css'; // Global styles
import styles from './hello.module.css'; // Scoped module styles
function Hello() {
return div(
p({ class: "hi " + styles.fnt600 },
'Hello from Sigment,',
br(),
'Inspect this element,',
br(),
'and see how styles apply'
)
);
}
π¦ Summary for Developers
Feature | Global CSS | CSS Modules |
---|---|---|
File name | hello.css |
hello.module.css |
Import syntax | import './file.css' |
import styles from |
Class use | "hi" |
styles.hi |
Scope | Global (shared) | Local (scoped) |
Collision safe | β No | β Yes |
π§ Tips
- Use global CSS for layout, resets, or base styles.
- Use CSS modules for component-specific styling.
- Always combine multiple classes with a string or helper function (e.g.,
"a " + styles.b
orclsx()
).
Lifecycle Helpers
Lifecycle utilities like createEffect
, onPaint
, and paintFinish
allow you to run logic at different stages of a component's rendering flow.
These helpers are entirely optional, and you can choose to use them only when needed.
Lifecycle Flow
createEffect(fn)
β runs whenever signals insidefn
change.onPaint(fn)
β schedulesfn
to run after the next paint cycle.paintFinish(fn)
β runsfn
only once, after the first paint is complete.
Example β lifecycle demo
import { signal, onPaint, paintFinish, createEffect } from "sigment";
function MyComponent() {
const [count, setCount] = signal(0);
createEffect(() => {
console.log(`Count changed: ${count()}`);
});
onPaint(() => {
console.log("Runs after every browser paint.");
});
paintFinish(() => {
console.log("Runs only once, after the first paint.");
});
return div(
button({ onClick: () => setCount(count() + 1) }, () => `Clicked ${count()} times`)
);
}
This demonstrates how createEffect
, onPaint
, and paintFinish
can be used together to manage logic around reactive updates and browser paint timing.
Routing
Sigment supports defining routes as a map of route names to route configurations. Each route can have:
loader
: a function that dynamically imports the component for that route.urlParam
: a string pattern like'{id}/{pageid}'
to extract URL parameters.guard
: an optional function returningboolean
orPromise<boolean>
to control access.logic
: optional function to run extra logic when loading the route.cacheExpiration
: optional time in ms to cache the loaded component.
You can also specify a fallback
route which loads if no matching route is found.
Example (TypeScript)
import type { RoutesMap } from 'sigment';
export const Routes: RoutesMap = {
about: {
loader: () => import('./About'),
guard: () => true,
},
user: {
loader: () => import('./User'),
urlParam: '{id}/{pageid}',
guard: async (params) => {
return await checkPermission(params?.id);
}
},
dash: {
loader: () => import('./Dash'),
cacheExpiration: 60000,
guard: () => checkPermission('dashboard'),
},
fallback: 'login'
};
Usage
Use parsePath()
to get the current route and params, then loadRunFunc()
to load the component:
const container = div({class:"body"},'Loading...');
const { componentName, params } = parsePath(Routes);
const content = await loadRunFunc(Routes, componentName, params);
container.replaceChildren(content);
URL Parameters
When using urlParam
with a pattern like '{id}/{pageid}'
, the URL segments after the route name are parsed into an object.
For example, the URL /user/123/45
will set params = { id: '123', pageid: '45' }
which is passed to your component and guard.
Route Guards
Guards allow you to check permissions or conditions before loading a route. They can be async and should return true
or false
.
memoizeFunc
Caches the result of a function based on its arguments. Useful for preventing unnecessary recomputation or repeated fetch calls.
Signature:
(alias) function memoizeFunc<F extends (...args: any[]) => any>(
fn: F,
ttl?: number,
options?: MemoizeOptions
): F & {
clear(): void;
}
Import:
import { memoizeFunc } from "sigment";
Example β Memoize a computation:
const heavyCalc = memoizeFunc((n) => {
console.log("Calculating...");
return n * 10;
});
console.log(heavyCalc(5)); // logs: Calculating... β 50
console.log(heavyCalc(5)); // cached β 50
Example β With TTL (time to live):
const fetchData = memoizeFunc(fetchDataFromAPI, 5000); // cache lasts 5 seconds
To manually clear cache:
fetchData.clear();
fetchCache
A convenient wrapper around fetch
with automatic caching. Results are stored in memory and reused until the TTL (time to live) expires.
Signature:
(alias) function fetchCache<T = any>(
url: string,
ttl?: number,
options?: RequestInit
): Promise<T>
Import:
import { fetchCache } from "sigment";
Example β Basic Usage:
const data = await fetchCache("https://api.example.com/posts");
Example β With TTL (cache expiration in ms):
const data = await fetchCache("https://api.example.com/posts", 10000);
// Result is cached for 10 seconds
Example β With fetch options:
const result = await fetchCache("https://api.example.com/user", 5000, {
headers: {
Authorization: "Bearer token"
}
});
getVirtualElementById
(gve
)
getVirtualElementById(id)
(alias gve(id)
) retrieves a reference to a previously created virtual element by its id
attribute.
Useful for interacting with specific elements in your reactive UI without querying the real DOM directly.
Signature
function getVirtualElementById(id: string): HTMLElement;
function gve(id: string): HTMLElement;
Example β style element
import { signal, getVirtualElementById , paintFinish , createEffect } from "sigment";
function Counter() {
const [count, setCount] = signal(0);
createEffect(() => {
console.log(`Current count: ${count()}`);
});
paintFinish(() => {
console.log("function that runs only once, after the first paint");
const counterEl = getVirtualElementById("counter") // or shorthand gve("counter");
if (counterEl) {
counterEl.style.backgroundColor = "yellow";
// Add click event to show alert
counterEl.addEventListener("click", () => alert(`You clicked: ${count()}`));
}
});
return div(
button({ onClick: () => setCount(count() + 1) }, () => `increment me , ${count()}`),
div({ id: "counter" }, () => `counter is , ${count()}`)
);
}
Here, we call paintFinish
first to ensure the element with ID "counter"
is available div
.
π¦ Component Overriding with Stateful or Stateless
Sigment supports replacing components dynamically using gvec()
with control over state and optional lifecycle triggers. This allows:
- β
Defining components with static functions (e.g.,
Dashboard.writeToLog()
) - β Dynamically loading and overriding other components at runtime
- β
Preserving or resetting component state (via the
true
orfalse
second argument) - β Sending parameters or triggering target methods after override
π€ Concept
gvec({
"login": Login,
"dash": await loadFunc(runtime, "dash"),
"param": [7, "pp"]
}, true, {
writeToLog: ["Login component"]
});
Navigate("dash");
π Login Component Example
import { Navigate, getGlobalSignal, loadFunc, gvec, addSigment } from "sigment";
import { GlobalKeys } from "./global/globalState.js";
import loadAtRunTimeComponents from "./utils/loadAtRunTimeComponents.js";
import Dashboard from "./Dashboard.js";
function Login(): HTMLElement {
const [userName, setUserName] = getGlobalSignal(GlobalKeys.userName);
addSigment("signin");
async function loadDashboard() {
gvec({
"login": Login,
"dash": await loadFunc(loadAtRunTimeComponents, "dash"),
"param": [7, "pp"]
}, true, {
writeToLog: ["Login component"]
});
Navigate("dash");
}
return signin({ id: "login" },
div(
div("user name:",
input({
type: "text",
onInput: (e: InputEvent) =>
setUserName((e.target as HTMLInputElement).value)
})
),
div("password:",
input({ type: "password" })
),
div({ class: "username" }, 'User name is: ', () => userName()),
button({ onclick: () => loadDashboard() }, "to dash")
)
);
}
export default Login;
π Dashboard Component Example
import { useGlobalSignal, createSignal, div, input, h2, button } from "sigment";
import { GlobalKeys } from "./global/globalState.js";
import loadAtRunTimeComponents from "./utils/loadAtRunTimeComponents.js";
import { Navigate, gvec, loadFunc } from "sigment";
import Login from "./Login.js";
interface DashType {
(props: any): HTMLElement;
writeToLog: (from: any) => void;
}
function Dashboard(props: any): HTMLElement {
const [userName] = useGlobalSignal(GlobalKeys.userName);
const [name, setName] = createSignal("");
(Dashboard as DashType).writeToLog = function (from: any): void {
console.log("writeToLog is fired from", from);
};
const handleClick = async () => {
gvec({
"dash": Dashboard,
"login": await loadFunc(loadAtRunTimeComponents, "Login")
}, false);
Navigate("/");
};
return div({ id: "dash" },
h2(() => `Welcome, ${userName()}!`),
div("Change your name:"),
input({
type: "text",
value: name(),
onInput: (e: InputEvent) =>
setName((e.target as HTMLInputElement).value)
}),
div(() => `Local name is: ${name()}`),
button({ onclick: handleClick }, "to login")
);
}
export default Dashboard;
π Runtime Loader
const loadAtRunTimeComponents = {
"login": () => import("../Login.js"),
"dash": () => import("../Dash.js")
};
export default loadAtRunTimeComponents;
rpc
β Call Static Component Methods Remotely
The rpc
function allows you to dynamically call static methods attached to a component,
such as Dashboard.writeToLog
, from outside the component context.
This is useful for triggering logic or returning values from components programmatically.
Signature
function rpc(
component,
methods: Record<string, any[]>
): Record<string, any>;
Example 1 β call a void method
function Dashboard(props) {
// component internals
}
Dashboard.writeToLog = function(from) {
console.log("writeToLog is fired type is void", from);
};
rpc(Dashboard, {
writeToLog: ["some text"]
});
This will run Dashboard.writeToLog("some text")
.
π¦ You can call this from another component like this dont forget to import Dashboard
rpc(Dashboard, {
writeToLog: ["called from HomePage"]
});
Example 2 β call multiple methods with return values
Dashboard.writeToLogWithValue = function(val) {
console.log("return value", val);
return val;
};
const res = rpc(Dashboard, {
writeToLog: ["some text"],
writeToLogWithValue: ["i am with value"]
});
console.log(res);
Methods that return values will be included in the result. Methods returning
undefined
(like writeToLog
) will appear with undefined
in the response.
Note: Static methods like Dashboard.writeToLog
must be defined outside the componentβs render body. This ensures theyβre available at any time and do not depend on component mounting.