Skip to content

Commit

Permalink
πŸ“„ Add more kastro docs
Browse files Browse the repository at this point in the history
  • Loading branch information
KimlikDAO-bot committed Feb 18, 2025
1 parent c2ece6b commit caec874
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 35 deletions.
92 changes: 91 additions & 1 deletion kastro/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,93 @@
# Kastro: React-like components at zero runtime cost

Kastro is a revolutionary web framework that achieves extreme performance by moving computation to compile time while maintaining a familiar React-like developer experience.

## Key Features

- βœ… React-like JSX components with full type safety
- ⚑️ Aggressive compile-time optimization and static rendering
- πŸ—œοΈ Ultra-minimal client JavaScript bundle
- πŸ” Advanced type-driven optimizations through `kdjs` compiler
- 🌐 Integrated i18n, asset bundling and CSS modules

## Core Philosophy

Unlike traditional frameworks that handle component rendering at runtime, Kastro takes a radical approach:

1. All component structure and rendering logic is processed at compile time
2. The client bundle contains only the minimal code needed for DOM manipulation
3. Static HTML is generated at build time for optimal browser rendering
4. No virtual DOM or runtime component system

This means:
- Extremely small client bundles
- Fast initial page loads
- Optimal browser rendering performance
- No framework runtime overhead

## Quick Example

```jsx
import dom from "@kimlikdao/util/dom";
import { LangCode } from "@kimlikdao/util/i18n";
import Css from "./Button.css";

/**
* @param {{ Lang: LangCode }} props
*/
const Button = ({ Lang }) => {
/** @const {!HTMLButtonElement} */
const Root = dom.button(Css.Root);
/** @const {!HTMLSpanElement} */
const Text = dom.span(Css.Text);

return (
<Root onClick={() => Text.innerText = "Clicked!"}>
<Text>Hello World!</Text>
</Root>
);
};

export default Button;
```

When compiled, this generates minimal client JavaScript:

```javascript
const get = (a) => document.getElementById(a);
const b = get("B");
get("A").onclick = () => b.innerText = "Clicked!";
```

## Core Concepts

- [Components](./docs/components.md) - Learn about Kastro's stateless and stateful components
- [DOM Management](./docs/dom.md) - Understanding DOM references and manipulation
- [CSS Modules](./docs/css.md) - Working with scoped styles and optimizations
- [Asset Handling](./docs/assets.md) - Image, font and other asset optimizations
- [Internationalization](./docs/i18n.md) - Built-in i18n support
- [Type System](./docs/types.md) - Leveraging types for optimization
- [Compilation](./docs/compilation.md) - How Kastro optimizes at build time

## Design Principles

1. **Compile-Time Over Runtime**: Move as much work as possible to build time
2. **Direct DOM Manipulation**: Use browser's native DOM APIs instead of abstractions
3. **Type-Driven Optimization**: Leverage type information for aggressive optimization
4. **Zero Runtime Framework**: No framework code in the client bundle
5. **Developer Experience**: Maintain familiar React-like component model

## Getting Started

[Installation and Setup Guide](./docs/getting-started.md)

## Learn More

- [Component Patterns](./docs/patterns.md)
- [Performance Guide](./docs/performance.md)
- [Migration Guide](./docs/migration.md)
- [API Reference](./docs/api.md)

# Kastro: KimlikDAO UI framework

Kastro is a UI framework for building pico-optimized web applications by
Expand Down Expand Up @@ -26,7 +116,7 @@ browsers are highly optimized for constructing the DOM from static HTML,
compared to dynamically creating elements one by one via JavaScript like many
other frameworks do.

## Example
## Quick example
```jsx:LandingPage.jsx
import dom from "@kimlikdao/util/dom";
import { LangCode } from "@kimlikdao/util/i18n";
Expand Down
180 changes: 180 additions & 0 deletions kastro/docs/components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Components in Kastro

There are two types of components in Kastro:

1. Stateless Components
2. Stateful Components

There are also pseudo-components which behave somewhat like components in jsx expressions, which we will explain later.

## 1. Stateless Components

Components that don't take an `instance` property are called stateless components. Their DOM IDs are determined at compile time, either through an explicit `id` prop or obtained internally (either through Kastro automatic ID generators or hard-coded).

```jsx
/** @param {{ id: string }} props */
const Counter = ({ id }) => {
const Root = dom.div(id);
const Count = dom.span(`${id}.count`);

return (
<Root onClick={() => Count.innerText = "" + (+Count.innerText + 1)}>
Count: <Count>0</Count>
</Root>
);
}

/**
* Singletons can have methods to interact with the component.
* @param {string} id
*/
Counter.reset = (id) => dom.span(`${id}.count`).innerText = "0";

// Usage:
const Page = () => (
<html>
<Counter id="counter1" />
<Counter id="counter2" />
</html>
);
```

When transpiled by Kastro (but before compilation by kdjs), this becomes:

```javascript
/** @param {{ id: string }} props */
const Counter = ({ id }) => {
const Root = dom.div(id);
const Count = dom.span(`${id}.count`);
Root.onclick = () => Count.innerText = "" + (+Count.innerText + 1);
}

Counter.reset = (id) => dom.span(`${id}.count`).innerText = "0";

// Usage:
const Page = () => {
Counter({ id: "counter1" });
Counter({ id: "counter2" });
}
```

Stateless components can maintain internal state, but if multiple instances exist on a page, they will share that state. For singleton components (used once per page), internal state is fine--for such cases singleton components are the best choice. For reusable components, either:
- Keep all state in the DOM (approach taken in the example above)
- Pass state into component methods by the caller
- Consider using a stateful component instead

## 2. Stateful Components

Stateful components are function objects that take an `instance` property. For each instance of the component, a class instance is created and bound to the variable passed as the `instance` property.

```jsx
/**
* @constructor
* @param {{ id: string }} props
*/
const Counter = function({ id }) {
/** @type {!HTMLDivElement} */
this.root = dom.div(id);
/** @type {number} */
this.count = 0;

return (
<div id={id}>
Count: <span>{this.count}</span>
</div>
);
}

Counter.prototype.increment = function() {
this.root.lastElementChild.innerText = "" + ++this.count;
}

const Page = () => (
<html>
<Counter id="c1" instance={Page.counter1} />
<Counter id="c2" instance={Page.counter2} />
</html>
);

// Access instance methods
Page.counter1.increment();
```

When transpiled for the client:

```javascript
const Counter = function({ id }) {
/** @type {!HTMLDivElement} */
this.root = dom.div(id);
/** @type {number} */
this.count = 0;
}

Counter.prototype.increment = function() {
this.root.lastElementChild.innerText = "" + ++this.count;
}

const Page = () => {
Page.counter1 = new Counter({ id: "c1" });
Page.counter2 = new Counter({ id: "c2" });
}
```

## Pseudo Components

Pseudo components bridge the gap between plain JSX elements and full components. They are created using `dom` utility methods and provide direct DOM access while supporting component-like features.

```jsx
/** @const {!HTMLDivElement} */
const Root = dom.div(Css.Root);
/** @const {!HTMLButtonElement} */
const Button = dom.button(Css.Button);

const Page = () => (
<html>
<Root onClick={() => console.log("clicked")}>
<Button>Click me</Button>
{/* Direct children can have event handlers */}
<div onClick={() => console.log("inner div clicked")} />
</Root>
</html>
);
```

Key features of pseudo components:

1. Direct DOM access at runtime via `document.getElementById()`
2. Support for event handlers through JSX props
3. Support for special directives like `controlsDropdown={Dropdown}`
4. Event handlers on immediate children (but not deeper descendants)
5. Compile to efficient DOM lookups

At build time, `dom.()` methods return objects like `{ name: "div", id: "A" }`, while in client code they compile to `document.getElementById("A")` calls.

### Event Handler Scope

To encourage maintainable code, Kastro only allows event handlers on:
- The pseudo component itself
- Direct children of pseudo components

This limitation helps prevent deeply nested event handler chains that can be hard to track and maintain.

## Asset Components

Kastro provides specialized components for handling different types of assets:

```jsx
import MyImage from "./image.png";
import MyFont from "./font.ttf";
import MyWorker from "kastro:./worker.js"; // Import with kastro: prefix to get a worker

const Page = () => (
<html>
<MyImage width={100} height={100} />
<MyFont />
<MyWorker />
</html>
);
```

See [Asset Handling](./assets.md) for detailed documentation on asset components.
32 changes: 17 additions & 15 deletions kastro/stylesheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,22 @@ const makeStyleSheets = () => {
const dev = (BuildMode) => BuildMode == 0 ? "-dev" : "";
PageCss.clear();

const StyleSheets = ({ BuildMode, Lang, targetDir }) => Promise.all([
compiler.bundleTarget(`/build/shared-${Lang}${dev(BuildMode)}.css`, {
BuildMode,
childTargets: SharedCss.asTargets()
}),
compiler.bundleTarget(`${targetDir}/page-${Lang}${dev(BuildMode)}.css`, {
BuildMode,
childTargets: PageCss.asTargets()
})
]).then(([sharedBundleName, pageBundleName]) =>
tagYaz("link", { rel: "stylesheet", href: sharedBundleName }, true) +
tagYaz("link", { rel: "stylesheet", href: pageBundleName }, true)
);
const StyleSheets = ({ BuildMode, Lang, targetDir }) => {
PageCss.removeAll(SharedCss.entries());
return Promise.all([
compiler.bundleTarget(`/build/shared-${Lang}${dev(BuildMode)}.css`, {
BuildMode,
childTargets: SharedCss.asTargets()
}),
compiler.bundleTarget(`${targetDir}/page-${Lang}${dev(BuildMode)}.css`, {
BuildMode,
childTargets: PageCss.asTargets()
})
]).then(([sharedBundleName, pageBundleName]) =>
tagYaz("link", { rel: "stylesheet", href: sharedBundleName }, true) +
tagYaz("link", { rel: "stylesheet", href: pageBundleName }, true)
);
}

return StyleSheets;
}
Expand All @@ -75,8 +78,6 @@ const makeStyleSheets = () => {
* function({
* src: string,
* shared: boolean,
* SharedCss: StyleSheetCollection,
* PageCss: StyleSheetCollection,
* }): null
* }}
*/
Expand All @@ -90,6 +91,7 @@ const addStyleSheet = (shared, target) => (shared ? SharedCss : PageCss).add(tar
* @return {StyleSheet}
*/
const makeStyleSheet = (fileName, cssContent) => {
console.log("Making stylesheet for", fileName);
const { content, enumEntries } = minifyCss(cssContent, fileName);
const Css = new Proxy(Object.assign(
({ shared }) => {
Expand Down
8 changes: 7 additions & 1 deletion kastro/transpiler/domIdMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ class GlobalMapper {
/** @const {!Set<string>} */
minifiedIds = new Set();

constructor() {
this.preserve("mpa", "ndp");
this.preserve("mpa", "nsh");
this.preserve("mpa", "sel");
this.preserve("mpa", "dis");
}

/**
* @param {string} namespace
* @param {string} context
Expand All @@ -89,7 +96,6 @@ class GlobalMapper {
this.keyToIndex.set(namespace + key, index);
}
const minifiedId = indexToMinified(index);
if (minifiedId == "zC") console.log("\n\n\n\n\n\n", context, domId, index);
this.minifiedIds.add(namespace + minifiedId);
return minifiedId;
}
Expand Down
2 changes: 1 addition & 1 deletion kastro/transpiler/jsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const transpile = (isEntry, file, content, domIdMapper, globals) => {
? `{\n ${Object.entries(props).map(([k, v]) => `${k}: ${serialize(v)}`).join(",\n ")}\n }`
: "";
const call = `${tagName}(${callParams})`;
statements.push(instance ? `${instance} = new ${call}` : call);
statements.push(instance ? `/** @const {${tagName}} */\n ${instance} = new ${call}` : call);
}
if (keepImport && info)
info.state = SpecifierState.Keep;
Expand Down
Loading

0 comments on commit caec874

Please sign in to comment.