Skip to content

Commit

Permalink
🧱 Simplify kastro component model: stateless/statefull
Browse files Browse the repository at this point in the history
  • Loading branch information
KimlikDAO-bot committed Feb 8, 2025
1 parent 28f68df commit 44f4a08
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 162 deletions.
98 changes: 49 additions & 49 deletions kastro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,29 @@ import { LangCode } from "@kimlikdao/util/i18n";
import ArrowSvg from "./arrow.svg";
import Css from "./LandingPage.css";

/** @const {!HTMLButtonElement} */
const Button = dom.button(Css.ButtonId);
/** @const {!HTMLSpanElement} */
const Text = dom.span(Css.TextId);

/**
* @param {{ Lang: LangCode }} Lang
*/
const LandingPage = ({ Lang }) => (
<html lang={Lang}>
<Css />
<Button onClick={() => Text.innerText = "Clicked!"}>
<ArrowSvg />Click me!
</Button>
<Text>Hello World!</Text>
</html>
);
const LandingPage = ({ Lang }) => {
/** @const {!HTMLButtonElement} */
const Button = dom.button(Css.ButtonId);
/** @const {!HTMLSpanElement} */
const Text = dom.span(Css.TextId);

return (
<html lang={Lang}>
<Css />
<Button onClick={() => Text.innerText = "Clicked!"}>
<ArrowSvg />Click here!
</Button>
<Text>Hello World!</Text>
</html>
);
};

export default LandingPage;
```
When you import a `.css` file, you get a StyleSheet component like the `Css`
When you import a .css file, you get a StyleSheet component like the `Css`
component in the example above. Each selector in the css file becomes available
as a property on this component, with the selector name converted to PascalCase.
For example, `.blue-button` becomes `Css.BlueButton`.
Expand Down Expand Up @@ -77,12 +79,11 @@ and the following html will be generated (after de-minification):
<!DOCTYPE html>
<html lang="en">
<head><link rel="stylesheet" href="khmW2F9I.css" /></head>
<button id="A"><svg src="vlGA9oOP.svg"/>Click me!</button>
<button id="A"><svg src="vlGA9oOP.svg"/>Click here!</button>
<span id="B">Hello World!</span>
</html>
```
In particular, there is no runtime, no boilerplate or any other code that
would better be handled at compile time.
In particular, there is no runtime, no framework setup code or boilerplate.

## Not reactive
As you may have noticed in the example above, kastro is not a reactive
Expand All @@ -106,41 +107,29 @@ the component html and the object part is used to manage the DOM interactions.
When building for the client, the function part is stripped to essentially
a no-op and in particular, the entire jsx expression is removed.

There are 3 types of components:
There are 2 types of components, stateless and stateful. In stateless components
the function part binds the component to the dom; in stateful components, the
function part is used as a constructor of the component's instance.

1. **Singleton**: Only one instance can be present in the page. These bind to
the DOM when the containing module is imported and can keep an arbitrary
internal state (every variable you define in the module is available as a
property of the component object).
1. **Stateless**: A component that does not take an `instance` property is
deemed stateless. Their dom id is fixed at compile time either by an `id`
property passed by their parent component, or by hardcoding it if the
component appears at most once in a page (thus assigning a unique id by the
parent is unnecessary).

If a component does not take an `id` or `instance` property, then it is
determined as a singleton.
These components can keep an internal state, however if there are multiple
copies of the component in a page, they will share this state. This means
that for singleton components, we can freely keep internal state, however
for reusable components, either the entire state must be kept in the DOM
or passed into the methods of the component by the caller.

```jsx
const State = [1, 2, 3];
const SingletonComp = () => <div>Singleton</div>;
export default SingletonComp;
```
If your component is exposing additional methods, you can add them like so:
```jsx
const State = [1, 2, 3];
const SingletonComp = () => <div>Singleton</div>;
SingletonComp.push = (x) => State.push(x);
SingletonComp.pop = () => State.pop();

export default SingletonComp;
```

2. **Stateless**: A component that takes an `id` property (but no `instance`
property) is deemed stateless. Their dom `id` is fixed at compile time by
their parents, and in particular there can be any number of instances of
stateless components with unique ids assigned by their parents.

Kastro compiler will generate the `Component({ id: "idAssignedByparent" })`
Kastro compiler will generate the `Component({ id: "idAssignedByParent" })`
invocations from the initialization code of the parent component.

```jsx
/** @param {{ id: string }} props */
const StatelessComp = ({ id }) => {
/** @type {!HTMLDivElement} */
const Root = dom.div(id);
return (
<Root
Expand All @@ -157,7 +146,9 @@ determined as a singleton.
```
When transpiled by kastro (but before compilation by kdjs), the above jsx file will become
```javascript
/** @param {{ id: string }} props */
const StatelessComp = ({ id }) => {
/** @type {!HTMLDivElement} */
const Root = dom.div(id);
Root.onclick = () => Root.innerText = Root.innerText == "On" ? "Off" : "On";
return null;
Expand All @@ -169,13 +160,19 @@ determined as a singleton.
Page();
```

3. **Stateful**: A component which takes an `instance` property is deemed a
stateful component. These components have internal state and for each copy
of the component, a class instance is created.
2. **Stateful**: A component which takes an `instance` property is deemed a
stateful component. These components can keep an internal state and for each
copy of the component, a class instance is created.

Note the `instance` property is used by the client jsx transpiler and never
passed to the component itself.

```jsx
/** @param {{ id: string }} props */
const CheckBox = ({ id }) => {
/** @type {!HTMLDivElement} */
this.root = dom.div(id);
/** @type {boolean} */
this.on = true;
return <div id={id}>on</div>;
}
Expand All @@ -195,8 +192,11 @@ determined as a singleton.
When the above jsx file is transpiled for the client by kastro (but before compilation by kdjs),
it will become
```javascript
/** @param {{ id: string }} props */
const CheckBox = ({ id }) => {
/** @type {!HTMLDivElement} */
this.root = dom.div(id);
/** @type {boolean} */
this.on = true;
return null;
}
Expand Down
4 changes: 1 addition & 3 deletions kastro/compiler/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { capitalize, getDir } from "../../util/paths";
import { filterGlobalProps } from "../props";
import { Script } from "../script";
import { makeStyleSheets } from "../stylesheet";
import { initComponentProps } from "../transpiler/componentProps";
import { initGlobals } from "../transpiler/pageGlobals";
import HtmlMinifierConfig from "./config/htmlMinifierConfig";
import { initGlobals } from "./pageGlobals";

/**
* @param {string} targetName
Expand All @@ -22,7 +21,6 @@ const pageTarget = (targetName, props) => {

const { BuildMode, Lang } = props;
initGlobals(props);
initComponentProps(targetModuleName, { BuildMode, Lang });
const StyleSheets = makeStyleSheets();
return import(targetModulePath)
.then((jsx) => jsx.default({ BuildMode, Lang }).render())
Expand Down
2 changes: 1 addition & 1 deletion kastro/kastro.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import {
webpTarget
} from "./compiler/image";
import { pageTarget } from "./compiler/page";
import { getGlobals } from "./compiler/pageGlobals";
import { scriptTarget } from "./compiler/script";
import { styleSheetTarget } from "./compiler/styleSheet";
import { registerTargetFunction } from "./compiler/targetRegistry";
import { getGlobals } from "./transpiler/pageGlobals";
import { transpileCss, transpileJsx } from "./transpiler/transpiler";
import { CompressedMimes } from "./workers/mimes";

Expand Down
2 changes: 1 addition & 1 deletion kastro/script.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { tagYaz } from "../util/html";
import { splitFullExt } from "../util/paths";
import compiler from "./compiler/compiler";
import { getGlobals } from "./compiler/pageGlobals";
import { Props } from "./props";
import { getGlobals } from "./transpiler/pageGlobals";

/**
* @param {Props} props
Expand Down
20 changes: 0 additions & 20 deletions kastro/transpiler/componentProps.js

This file was deleted.

15 changes: 3 additions & 12 deletions kastro/transpiler/jsx-runtime.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { KapalıTag, tagYaz } from "../../util/html";
import { LangCode } from "../../util/i18n";
import { getGlobals } from "../compiler/pageGlobals";
import { storeComponentProps } from "./componentProps";
import { getGlobals } from "./pageGlobals";

/** @const {string} */
const Fragment = "";
Expand Down Expand Up @@ -76,16 +75,8 @@ const jsx = (name, props = {}) => {
resolveComponentProps(props, globals.Lang);

const nameType = typeof name;
if (nameType == "function") {
const componentProps = name({ ...props, ...globals });
if (!componentProps) return;
const render = componentProps.render;
componentProps.render = () => {
storeComponentProps(name.name, props);
return render();
}
return componentProps;
}
if (nameType == "function")
return name({ ...props, ...globals });

let { modifiesChildren, ...prop } = props;
resolveElementProps(prop);
Expand Down
Loading

0 comments on commit 44f4a08

Please sign in to comment.