cajoa 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -43
- package/dist/index.cjs +16 -0
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +13 -0
- package/package.json +25 -4
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Cajoa
|
|
2
2
|
|
|
3
|
-
A small, beginner-friendly frontend framework built on **signals**.
|
|
3
|
+
A small, beginner-friendly frontend framework built on **signals** and **JSX**.
|
|
4
4
|
No virtual DOM, no hooks rules, no re-rendering components.
|
|
5
5
|
|
|
6
|
-
> ⚠️ **v0.
|
|
6
|
+
> ⚠️ **v0.3 (early stage).** The API may still change. Not production-ready yet.
|
|
7
7
|
|
|
8
8
|
## Why Cajoa?
|
|
9
9
|
|
|
10
10
|
- **Components run once.** A component is just a function that builds DOM. Only the parts that depend on a signal update.
|
|
11
|
+
- **Plain JSX.** Write `<button onClick={...}>`, no special syntax to learn.
|
|
11
12
|
- **Tiny and readable.** The whole core is small enough to read in one sitting.
|
|
12
13
|
- **TypeScript-first**, ESM + CommonJS, zero runtime dependencies.
|
|
13
14
|
|
|
@@ -17,52 +18,66 @@ No virtual DOM, no hooks rules, no re-rendering components.
|
|
|
17
18
|
npm install cajoa
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
## Setup (JSX)
|
|
22
|
+
|
|
23
|
+
In your `tsconfig.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"compilerOptions": {
|
|
28
|
+
"jsx": "react-jsx",
|
|
29
|
+
"jsxImportSource": "cajoa"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If you use Vite and your JSX is not transformed, also set it in `vite.config.ts`
|
|
35
|
+
(`oxc: { jsx: { runtime: "automatic", importSource: "cajoa" } }` on newer Vite,
|
|
36
|
+
or `esbuild: { jsx: "automatic", jsxImportSource: "cajoa" }` on older ones).
|
|
37
|
+
|
|
20
38
|
## Quick start
|
|
21
39
|
|
|
22
|
-
```
|
|
23
|
-
import { createSignal,
|
|
40
|
+
```tsx
|
|
41
|
+
import { createSignal, render } from "cajoa";
|
|
24
42
|
|
|
25
43
|
function Counter() {
|
|
26
44
|
const [count, setCount] = createSignal(0);
|
|
27
45
|
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
count,
|
|
33
|
-
" times",
|
|
46
|
+
return (
|
|
47
|
+
<button onClick={() => setCount((c) => c + 1)}>
|
|
48
|
+
Clicked {count} times
|
|
49
|
+
</button>
|
|
34
50
|
);
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
render(Counter, document.getElementById("app")!);
|
|
38
54
|
```
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
## Two rules to remember
|
|
57
|
+
|
|
58
|
+
Cajoa has no compiler, so JSX expressions run exactly once. That gives two rules:
|
|
59
|
+
|
|
60
|
+
1. **Pass the signal, don't call it.**
|
|
61
|
+
`{count}` is reactive. `{count()}` is read once and never updates.
|
|
62
|
+
For computed values, pass a function: `{() => count() * 2}`.
|
|
63
|
+
2. **`Show` children are a function.**
|
|
64
|
+
`<Show when={on}>{() => <p>Hello</p>}</Show>`
|
|
65
|
+
Otherwise the element is created even when it is hidden.
|
|
41
66
|
|
|
42
67
|
## Lists and conditions
|
|
43
68
|
|
|
44
|
-
```
|
|
45
|
-
import { createSignal,
|
|
69
|
+
```tsx
|
|
70
|
+
import { createSignal, Show, For } from "cajoa";
|
|
46
71
|
|
|
47
72
|
const [todos, setTodos] = createSignal([{ id: 1, text: "Learn Cajoa" }]);
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
h(
|
|
57
|
-
"ul",
|
|
58
|
-
null,
|
|
59
|
-
For({
|
|
60
|
-
each: todos,
|
|
61
|
-
children: (todo) => h("li", null, todo.text),
|
|
62
|
-
}),
|
|
63
|
-
),
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
74
|
+
<Show when={() => todos().length > 0} fallback={() => <p>Nothing to do.</p>}>
|
|
75
|
+
{() => (
|
|
76
|
+
<ul>
|
|
77
|
+
<For each={todos}>{(todo) => <li>{todo.text}</li>}</For>
|
|
78
|
+
</ul>
|
|
79
|
+
)}
|
|
80
|
+
</Show>;
|
|
66
81
|
```
|
|
67
82
|
|
|
68
83
|
## API
|
|
@@ -76,14 +91,19 @@ h(
|
|
|
76
91
|
| `createMemo(fn)` | Cached computed value. Only notifies when the result changes. |
|
|
77
92
|
| `batch(fn)` | Groups several `set` calls into one update. |
|
|
78
93
|
|
|
79
|
-
###
|
|
94
|
+
### Components
|
|
95
|
+
|
|
96
|
+
| Component | What it does |
|
|
97
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `<Show when fallback>` | Conditional rendering. `children` and `fallback` are functions. |
|
|
99
|
+
| `<For each key?>` | List rendering. Reuses DOM nodes for unchanged items. The child is a function that returns a `Node`. |
|
|
100
|
+
|
|
101
|
+
### Rendering
|
|
80
102
|
|
|
81
|
-
| Function
|
|
82
|
-
|
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `Show({ when, children, fallback })` | Conditional rendering. |
|
|
86
|
-
| `For({ each, children, key? })` | List rendering. Reuses DOM nodes for unchanged items. `children` must return a `Node`. |
|
|
103
|
+
| Function | What it does |
|
|
104
|
+
| ------------------------------ | ------------------------------------------------- |
|
|
105
|
+
| `render(component, container)` | Mounts a component. Returns a `dispose` function. |
|
|
106
|
+
| `h(tag, props, ...children)` | Low-level element creator. JSX compiles to this. |
|
|
87
107
|
|
|
88
108
|
### Lifecycle
|
|
89
109
|
|
|
@@ -109,7 +129,7 @@ h(
|
|
|
109
129
|
|
|
110
130
|
## Known limitations
|
|
111
131
|
|
|
112
|
-
- No
|
|
132
|
+
- No Fragments (`<>...</>`) yet. Wrap siblings in an element.
|
|
113
133
|
- No SSR.
|
|
114
134
|
- `createMemo` can show a brief inconsistent state when memos depend on each other.
|
|
115
135
|
- `set` calls are not batched automatically inside event handlers. Wrap them in `batch()`.
|
|
@@ -117,13 +137,14 @@ h(
|
|
|
117
137
|
## Roadmap
|
|
118
138
|
|
|
119
139
|
- [x] Signals and effects
|
|
120
|
-
- [x] DOM bindings
|
|
121
|
-
- [x] `Show`, `For`
|
|
122
|
-
- [x] Lifecycle
|
|
140
|
+
- [x] DOM bindings and `render`
|
|
141
|
+
- [x] `Show`, `For` (keyed)
|
|
142
|
+
- [x] Lifecycle and disposal
|
|
123
143
|
- [x] `createMemo`, `batch`
|
|
124
|
-
- [x] Keyed `For`
|
|
125
144
|
- [x] Context
|
|
126
|
-
- [
|
|
145
|
+
- [x] JSX support
|
|
146
|
+
- [ ] Fragments
|
|
147
|
+
- [ ] Vite plugin (removes the two rules above)
|
|
127
148
|
- [ ] Router
|
|
128
149
|
- [ ] SSR
|
|
129
150
|
|
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,9 @@ __export(index_exports, {
|
|
|
29
29
|
createRoot: () => createRoot,
|
|
30
30
|
createSignal: () => createSignal,
|
|
31
31
|
h: () => h,
|
|
32
|
+
jsx: () => jsx,
|
|
33
|
+
jsxDEV: () => jsxDEV,
|
|
34
|
+
jsxs: () => jsxs,
|
|
32
35
|
onCleanup: () => onCleanup,
|
|
33
36
|
onMount: () => onMount,
|
|
34
37
|
provide: () => provide,
|
|
@@ -293,6 +296,16 @@ function createMemo(fn) {
|
|
|
293
296
|
createEffect(() => setValue(() => fn()));
|
|
294
297
|
return value;
|
|
295
298
|
}
|
|
299
|
+
|
|
300
|
+
// src/jsx-runtime.ts
|
|
301
|
+
function jsx(type, props) {
|
|
302
|
+
if (typeof type === "function") return type(props ?? {});
|
|
303
|
+
const { children, ...rest } = props ?? {};
|
|
304
|
+
const kids = children === void 0 ? [] : [children];
|
|
305
|
+
return h(type, rest, ...kids);
|
|
306
|
+
}
|
|
307
|
+
var jsxs = jsx;
|
|
308
|
+
var jsxDEV = jsx;
|
|
296
309
|
// Annotate the CommonJS export names for ESM import in node:
|
|
297
310
|
0 && (module.exports = {
|
|
298
311
|
For,
|
|
@@ -304,6 +317,9 @@ function createMemo(fn) {
|
|
|
304
317
|
createRoot,
|
|
305
318
|
createSignal,
|
|
306
319
|
h,
|
|
320
|
+
jsx,
|
|
321
|
+
jsxDEV,
|
|
322
|
+
jsxs,
|
|
307
323
|
onCleanup,
|
|
308
324
|
onMount,
|
|
309
325
|
provide,
|
package/dist/index.d.cts
CHANGED
|
@@ -32,5 +32,18 @@ declare function For<T>(props: {
|
|
|
32
32
|
|
|
33
33
|
declare function createMemo<T>(fn: () => T): () => T;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
declare function jsx(type: any, props: Record<string, any> | null): any;
|
|
36
|
+
declare const jsxs: typeof jsx;
|
|
37
|
+
declare const jsxDEV: typeof jsx;
|
|
38
|
+
declare namespace JSX {
|
|
39
|
+
type Element = any;
|
|
40
|
+
interface IntrinsicElements {
|
|
41
|
+
[tag: string]: any;
|
|
42
|
+
}
|
|
43
|
+
interface ElementChildrenAttribute {
|
|
44
|
+
children: {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { For, JSX, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, useContext };
|
|
36
49
|
export type { Child, Context };
|
package/dist/index.d.ts
CHANGED
|
@@ -32,5 +32,18 @@ declare function For<T>(props: {
|
|
|
32
32
|
|
|
33
33
|
declare function createMemo<T>(fn: () => T): () => T;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
declare function jsx(type: any, props: Record<string, any> | null): any;
|
|
36
|
+
declare const jsxs: typeof jsx;
|
|
37
|
+
declare const jsxDEV: typeof jsx;
|
|
38
|
+
declare namespace JSX {
|
|
39
|
+
type Element = any;
|
|
40
|
+
interface IntrinsicElements {
|
|
41
|
+
[tag: string]: any;
|
|
42
|
+
}
|
|
43
|
+
interface ElementChildrenAttribute {
|
|
44
|
+
children: {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { For, JSX, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, useContext };
|
|
36
49
|
export type { Child, Context };
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,16 @@ function createMemo(fn) {
|
|
|
254
254
|
createEffect(() => setValue(() => fn()));
|
|
255
255
|
return value;
|
|
256
256
|
}
|
|
257
|
+
|
|
258
|
+
// src/jsx-runtime.ts
|
|
259
|
+
function jsx(type, props) {
|
|
260
|
+
if (typeof type === "function") return type(props ?? {});
|
|
261
|
+
const { children, ...rest } = props ?? {};
|
|
262
|
+
const kids = children === void 0 ? [] : [children];
|
|
263
|
+
return h(type, rest, ...kids);
|
|
264
|
+
}
|
|
265
|
+
var jsxs = jsx;
|
|
266
|
+
var jsxDEV = jsx;
|
|
257
267
|
export {
|
|
258
268
|
For,
|
|
259
269
|
Show,
|
|
@@ -264,6 +274,9 @@ export {
|
|
|
264
274
|
createRoot,
|
|
265
275
|
createSignal,
|
|
266
276
|
h,
|
|
277
|
+
jsx,
|
|
278
|
+
jsxDEV,
|
|
279
|
+
jsxs,
|
|
267
280
|
onCleanup,
|
|
268
281
|
onMount,
|
|
269
282
|
provide,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cajoa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A beginner-friendly, signals-based frontend framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -16,6 +16,26 @@
|
|
|
16
16
|
"types": "./dist/index.d.cts",
|
|
17
17
|
"default": "./dist/index.cjs"
|
|
18
18
|
}
|
|
19
|
+
},
|
|
20
|
+
"./jsx-runtime": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
27
|
+
"default": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"./jsx-dev-runtime": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
19
39
|
}
|
|
20
40
|
},
|
|
21
41
|
"files": [
|
|
@@ -31,7 +51,7 @@
|
|
|
31
51
|
},
|
|
32
52
|
"repository": {
|
|
33
53
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/
|
|
54
|
+
"url": "git+https://github.com/CajoaTS/Cajoa.git"
|
|
35
55
|
},
|
|
36
56
|
"keywords": [
|
|
37
57
|
"frontend",
|
|
@@ -44,10 +64,11 @@
|
|
|
44
64
|
"author": "cjmgrc",
|
|
45
65
|
"license": "Apache-2.0",
|
|
46
66
|
"bugs": {
|
|
47
|
-
"url": "https://github.com/
|
|
67
|
+
"url": "https://github.com/CajoaTS/Cajoa/issues"
|
|
48
68
|
},
|
|
49
|
-
"homepage": "https://github.com/
|
|
69
|
+
"homepage": "https://github.com/CajoaTS/Cajoa#readme",
|
|
50
70
|
"devDependencies": {
|
|
71
|
+
"@types/node": "^26.6.5",
|
|
51
72
|
"jsdom": "^29.1.1",
|
|
52
73
|
"tsup": "^9.0.0",
|
|
53
74
|
"typescript": "^5.9.3",
|