cajoa 0.1.0 → 0.2.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 CHANGED
@@ -3,12 +3,12 @@
3
3
  A small, beginner-friendly frontend framework built on **signals**.
4
4
  No virtual DOM, no hooks rules, no re-rendering components.
5
5
 
6
- > ⚠️ **v0.1 (early stage).** The API will change. Not production-ready yet.
6
+ > ⚠️ **v0.2 (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
- - **Tiny and readable.** The whole core is a few hundred lines you can read in one sitting.
11
+ - **Tiny and readable.** The whole core is small enough to read in one sitting.
12
12
  - **TypeScript-first**, ESM + CommonJS, zero runtime dependencies.
13
13
 
14
14
  ## Install
@@ -39,26 +39,80 @@ render(Counter, document.getElementById("app")!);
39
39
 
40
40
  Passing `count` (the function itself, not `count()`) is what makes that part of the DOM reactive.
41
41
 
42
+ ## Lists and conditions
43
+
44
+ ```ts
45
+ import { createSignal, h, Show, For } from "cajoa";
46
+
47
+ const [todos, setTodos] = createSignal([{ id: 1, text: "Learn Cajoa" }]);
48
+
49
+ h(
50
+ "div",
51
+ null,
52
+ Show({
53
+ when: () => todos().length > 0,
54
+ fallback: () => h("p", null, "Nothing to do."),
55
+ children: () =>
56
+ h(
57
+ "ul",
58
+ null,
59
+ For({
60
+ each: todos,
61
+ children: (todo) => h("li", null, todo.text),
62
+ }),
63
+ ),
64
+ }),
65
+ );
66
+ ```
67
+
42
68
  ## API
43
69
 
44
- | Function | What it does |
45
- | ------------------------------------ | ------------------------------------------------------------------------ |
46
- | `createSignal(value)` | Returns `[get, set]`. Reading `get()` inside an effect subscribes to it. |
47
- | `createEffect(fn)` | Runs `fn` now and again whenever a signal it read changes. |
48
- | `h(tag, props, ...children)` | Creates a real DOM element. Function props/children are reactive. |
49
- | `render(component, container)` | Mounts a component. Returns a `dispose` function. |
50
- | `Show({ when, children, fallback })` | Conditional rendering. |
51
- | `For({ each, children })` | List rendering. |
52
- | `onMount(fn)` | Runs `fn` after the component is rendered. |
53
- | `onCleanup(fn)` | Runs `fn` before an effect re-runs or when its owner is disposed. |
54
- | `createRoot(fn)` | Creates a top-level owner you can dispose manually. |
70
+ ### Reactivity
71
+
72
+ | Function | What it does |
73
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------- |
74
+ | `createSignal(value)` | Returns `[get, set]`. Reading `get()` inside an effect subscribes to it. `set` accepts a value or an updater function. |
75
+ | `createEffect(fn)` | Runs `fn` now and again whenever a signal it read changes. |
76
+ | `createMemo(fn)` | Cached computed value. Only notifies when the result changes. |
77
+ | `batch(fn)` | Groups several `set` calls into one update. |
78
+
79
+ ### DOM
80
+
81
+ | Function | What it does |
82
+ | ------------------------------------ | -------------------------------------------------------------------------------------- |
83
+ | `h(tag, props, ...children)` | Creates a real DOM element. Function props/children are reactive. |
84
+ | `render(component, container)` | Mounts a component. Returns a `dispose` function. |
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`. |
87
+
88
+ ### Lifecycle
89
+
90
+ | Function | What it does |
91
+ | ---------------- | ----------------------------------------------------------------- |
92
+ | `onMount(fn)` | Runs `fn` after the component is rendered. |
93
+ | `onCleanup(fn)` | Runs `fn` before an effect re-runs or when its owner is disposed. |
94
+ | `createRoot(fn)` | Creates a top-level owner you can dispose manually. |
95
+
96
+ ### Context
97
+
98
+ | Function | What it does |
99
+ | ------------------------- | ------------------------------------------------------ |
100
+ | `createContext(default)` | Creates a context with a default value. |
101
+ | `provide(ctx, value, fn)` | Runs `fn`; everything created inside can read `value`. |
102
+ | `useContext(ctx)` | Reads the nearest provided value (or the default). |
103
+
104
+ ## Good to know
105
+
106
+ - `For` uses the item itself as the key by default. If you pass `key`, items with the same key are **not** re-rendered when their data changes, so keys must be unique and the item should hold its own signals if it needs to update.
107
+ - `useContext` only works while a component is being created (or inside `onMount` / effects). Read it at the top of your component and use the value later in handlers.
108
+ - Updating an object in place will not trigger effects. Set a new object instead.
55
109
 
56
110
  ## Known limitations
57
111
 
58
- - `For` rebuilds the whole list on every change (keyed diffing is planned).
59
- - No `createMemo`, `batch`, or context yet.
60
112
  - No JSX support yet (`h()` only).
61
113
  - No SSR.
114
+ - `createMemo` can show a brief inconsistent state when memos depend on each other.
115
+ - `set` calls are not batched automatically inside event handlers. Wrap them in `batch()`.
62
116
 
63
117
  ## Roadmap
64
118
 
@@ -66,11 +120,12 @@ Passing `count` (the function itself, not `count()`) is what makes that part of
66
120
  - [x] DOM bindings (`h`, `render`)
67
121
  - [x] `Show`, `For`
68
122
  - [x] Lifecycle (`onMount`, `onCleanup`) and disposal
69
- - [ ] `createMemo`, `batch`
70
- - [ ] Keyed `For`
71
- - [ ] Context
123
+ - [x] `createMemo`, `batch`
124
+ - [x] Keyed `For`
125
+ - [x] Context
72
126
  - [ ] JSX support
73
127
  - [ ] Router
128
+ - [ ] SSR
74
129
 
75
130
  ## Development
76
131
 
package/dist/index.cjs CHANGED
@@ -22,13 +22,18 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  For: () => For,
24
24
  Show: () => Show,
25
+ batch: () => batch,
26
+ createContext: () => createContext,
25
27
  createEffect: () => createEffect,
28
+ createMemo: () => createMemo,
26
29
  createRoot: () => createRoot,
27
30
  createSignal: () => createSignal,
28
31
  h: () => h,
29
32
  onCleanup: () => onCleanup,
30
33
  onMount: () => onMount,
31
- render: () => render
34
+ provide: () => provide,
35
+ render: () => render,
36
+ useContext: () => useContext
32
37
  });
33
38
  module.exports = __toCommonJS(index_exports);
34
39
 
@@ -45,6 +50,22 @@ function cleanComputation(c) {
45
50
  c.deps.clear();
46
51
  clean(c);
47
52
  }
53
+ var batchDepth = 0;
54
+ var pending = /* @__PURE__ */ new Set();
55
+ function flush() {
56
+ const effects = [...pending];
57
+ pending.clear();
58
+ effects.forEach((effect) => effect.run());
59
+ }
60
+ function batch(fn) {
61
+ batchDepth++;
62
+ try {
63
+ return fn();
64
+ } finally {
65
+ batchDepth--;
66
+ if (batchDepth === 0) flush();
67
+ }
68
+ }
48
69
  function createSignal(value) {
49
70
  const subs = /* @__PURE__ */ new Set();
50
71
  const get = () => {
@@ -58,13 +79,18 @@ function createSignal(value) {
58
79
  const v = typeof next === "function" ? next(value) : next;
59
80
  if (Object.is(v, value)) return;
60
81
  value = v;
61
- [...subs].forEach((effect) => effect.run());
82
+ for (const effect of [...subs]) {
83
+ if (batchDepth > 0)
84
+ pending.add(effect);
85
+ else effect.run();
86
+ }
62
87
  };
63
88
  return [get, set];
64
89
  }
65
90
  function createEffect(fn) {
66
91
  const effect = {
67
92
  cleanups: [],
93
+ parent: currentOwner,
68
94
  deps: /* @__PURE__ */ new Set(),
69
95
  disposed: false,
70
96
  run() {
@@ -92,7 +118,7 @@ function onCleanup(fn) {
92
118
  currentOwner?.cleanups.push(fn);
93
119
  }
94
120
  function createRoot(fn) {
95
- const root = { cleanups: [] };
121
+ const root = { cleanups: [], parent: currentOwner };
96
122
  const prevOwner = currentOwner;
97
123
  const prevEffect = currentEffect;
98
124
  currentOwner = root;
@@ -119,6 +145,30 @@ function onMount(fn) {
119
145
  }
120
146
  });
121
147
  }
148
+ function createContext(defaultValue) {
149
+ return { id: /* @__PURE__ */ Symbol("cajoa-context"), defaultValue };
150
+ }
151
+ function provide(ctx, value, fn) {
152
+ const owner = {
153
+ cleanups: [],
154
+ parent: currentOwner,
155
+ context: /* @__PURE__ */ new Map([[ctx.id, value]])
156
+ };
157
+ onCleanup(() => clean(owner));
158
+ const prev = currentOwner;
159
+ currentOwner = owner;
160
+ try {
161
+ return fn();
162
+ } finally {
163
+ currentOwner = prev;
164
+ }
165
+ }
166
+ function useContext(ctx) {
167
+ for (let o = currentOwner; o; o = o.parent) {
168
+ if (o.context?.has(ctx.id)) return o.context.get(ctx.id);
169
+ }
170
+ return ctx.defaultValue;
171
+ }
122
172
 
123
173
  // src/dom/h.ts
124
174
  function h(tag, props, ...children) {
@@ -152,12 +202,12 @@ function toNodes(value) {
152
202
  return out;
153
203
  }
154
204
  function appendChildren(parent, children) {
155
- const pending = [...children].reverse();
156
- while (pending.length > 0) {
157
- const child = pending.pop();
205
+ const pending2 = [...children].reverse();
206
+ while (pending2.length > 0) {
207
+ const child = pending2.pop();
158
208
  if (Array.isArray(child)) {
159
209
  for (let i = child.length - 1; i >= 0; i--) {
160
- pending.push(child[i]);
210
+ pending2.push(child[i]);
161
211
  }
162
212
  continue;
163
213
  }
@@ -166,17 +216,29 @@ function appendChildren(parent, children) {
166
216
  const end = document.createComment("");
167
217
  parent.append(start, end);
168
218
  createEffect(() => {
169
- const nodes = toNodes(child());
170
- while (start.nextSibling && start.nextSibling !== end) {
171
- start.nextSibling.remove();
172
- }
173
- end.before(...nodes);
219
+ reconcile(start, end, toNodes(child()));
174
220
  });
175
221
  } else {
176
222
  parent.append(...toNodes(child));
177
223
  }
178
224
  }
179
225
  }
226
+ function reconcile(start, end, next) {
227
+ const keep = new Set(next);
228
+ let cur = start.nextSibling;
229
+ while (cur && cur !== end) {
230
+ const after = cur.nextSibling;
231
+ if (!keep.has(cur)) cur.remove();
232
+ cur = after;
233
+ }
234
+ const parent = end.parentNode;
235
+ let ref = start.nextSibling;
236
+ for (const node of next) {
237
+ if (node === ref)
238
+ ref = ref.nextSibling;
239
+ else parent.insertBefore(node, ref);
240
+ }
241
+ }
180
242
 
181
243
  // src/dom/render.ts
182
244
  function render(component, container) {
@@ -196,17 +258,55 @@ function Show(props) {
196
258
 
197
259
  // src/components/for.ts
198
260
  function For(props) {
199
- return () => props.each().map((item, i) => props.children(item, i));
261
+ let cache = /* @__PURE__ */ new Map();
262
+ onCleanup(() => {
263
+ cache.forEach((e) => e.dispose());
264
+ cache.clear();
265
+ });
266
+ return () => {
267
+ const items = props.each();
268
+ const next = /* @__PURE__ */ new Map();
269
+ const nodes = [];
270
+ items.forEach((item, i) => {
271
+ const k = props.key ? props.key(item, i) : item;
272
+ let entry = cache.get(k);
273
+ if (!entry) {
274
+ entry = createRoot((dispose) => ({
275
+ node: props.children(item, i),
276
+ dispose
277
+ }));
278
+ }
279
+ next.set(k, entry);
280
+ nodes.push(entry.node);
281
+ });
282
+ cache.forEach((entry, k) => {
283
+ if (!next.has(k)) entry.dispose();
284
+ });
285
+ cache = next;
286
+ return nodes;
287
+ };
288
+ }
289
+
290
+ // src/reactivity/memo.ts
291
+ function createMemo(fn) {
292
+ const [value, setValue] = createSignal(void 0);
293
+ createEffect(() => setValue(() => fn()));
294
+ return value;
200
295
  }
201
296
  // Annotate the CommonJS export names for ESM import in node:
202
297
  0 && (module.exports = {
203
298
  For,
204
299
  Show,
300
+ batch,
301
+ createContext,
205
302
  createEffect,
303
+ createMemo,
206
304
  createRoot,
207
305
  createSignal,
208
306
  h,
209
307
  onCleanup,
210
308
  onMount,
211
- render
309
+ provide,
310
+ render,
311
+ useContext
212
312
  });
package/dist/index.d.cts CHANGED
@@ -1,9 +1,17 @@
1
1
  type Cleanup = () => void;
2
+ declare function batch<T>(fn: () => T): T;
2
3
  declare function createSignal<T>(value: T): readonly [() => T, (next: T | ((prev: T) => T)) => void];
3
4
  declare function createEffect(fn: () => void): void;
4
5
  declare function onCleanup(fn: Cleanup): void;
5
6
  declare function createRoot<T>(fn: (dispose: () => void) => T): T;
6
7
  declare function onMount(fn: () => void): void;
8
+ interface Context<T> {
9
+ readonly id: symbol;
10
+ readonly defaultValue: T;
11
+ }
12
+ declare function createContext<T>(defaultValue: T): Context<T>;
13
+ declare function provide<T, R>(ctx: Context<T>, value: T, fn: () => R): R;
14
+ declare function useContext<T>(ctx: Context<T>): T;
7
15
 
8
16
  type Child = string | number | boolean | null | undefined | Node | (() => unknown) | Child[];
9
17
  declare function h(tag: string, props: Record<string, any> | null, ...children: Child[]): HTMLElement;
@@ -18,8 +26,11 @@ declare function Show<T>(props: {
18
26
 
19
27
  declare function For<T>(props: {
20
28
  each: () => T[];
21
- children: (item: T, index: number) => Child;
22
- }): () => Child[];
29
+ children: (item: T, index: number) => Node;
30
+ key?: (item: T, index: number) => unknown;
31
+ }): () => Node[];
23
32
 
24
- export { For, Show, createEffect, createRoot, createSignal, h, onCleanup, onMount, render };
25
- export type { Child };
33
+ declare function createMemo<T>(fn: () => T): () => T;
34
+
35
+ export { For, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, onCleanup, onMount, provide, render, useContext };
36
+ export type { Child, Context };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  type Cleanup = () => void;
2
+ declare function batch<T>(fn: () => T): T;
2
3
  declare function createSignal<T>(value: T): readonly [() => T, (next: T | ((prev: T) => T)) => void];
3
4
  declare function createEffect(fn: () => void): void;
4
5
  declare function onCleanup(fn: Cleanup): void;
5
6
  declare function createRoot<T>(fn: (dispose: () => void) => T): T;
6
7
  declare function onMount(fn: () => void): void;
8
+ interface Context<T> {
9
+ readonly id: symbol;
10
+ readonly defaultValue: T;
11
+ }
12
+ declare function createContext<T>(defaultValue: T): Context<T>;
13
+ declare function provide<T, R>(ctx: Context<T>, value: T, fn: () => R): R;
14
+ declare function useContext<T>(ctx: Context<T>): T;
7
15
 
8
16
  type Child = string | number | boolean | null | undefined | Node | (() => unknown) | Child[];
9
17
  declare function h(tag: string, props: Record<string, any> | null, ...children: Child[]): HTMLElement;
@@ -18,8 +26,11 @@ declare function Show<T>(props: {
18
26
 
19
27
  declare function For<T>(props: {
20
28
  each: () => T[];
21
- children: (item: T, index: number) => Child;
22
- }): () => Child[];
29
+ children: (item: T, index: number) => Node;
30
+ key?: (item: T, index: number) => unknown;
31
+ }): () => Node[];
23
32
 
24
- export { For, Show, createEffect, createRoot, createSignal, h, onCleanup, onMount, render };
25
- export type { Child };
33
+ declare function createMemo<T>(fn: () => T): () => T;
34
+
35
+ export { For, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, onCleanup, onMount, provide, render, useContext };
36
+ export type { Child, Context };
package/dist/index.js CHANGED
@@ -11,6 +11,22 @@ function cleanComputation(c) {
11
11
  c.deps.clear();
12
12
  clean(c);
13
13
  }
14
+ var batchDepth = 0;
15
+ var pending = /* @__PURE__ */ new Set();
16
+ function flush() {
17
+ const effects = [...pending];
18
+ pending.clear();
19
+ effects.forEach((effect) => effect.run());
20
+ }
21
+ function batch(fn) {
22
+ batchDepth++;
23
+ try {
24
+ return fn();
25
+ } finally {
26
+ batchDepth--;
27
+ if (batchDepth === 0) flush();
28
+ }
29
+ }
14
30
  function createSignal(value) {
15
31
  const subs = /* @__PURE__ */ new Set();
16
32
  const get = () => {
@@ -24,13 +40,18 @@ function createSignal(value) {
24
40
  const v = typeof next === "function" ? next(value) : next;
25
41
  if (Object.is(v, value)) return;
26
42
  value = v;
27
- [...subs].forEach((effect) => effect.run());
43
+ for (const effect of [...subs]) {
44
+ if (batchDepth > 0)
45
+ pending.add(effect);
46
+ else effect.run();
47
+ }
28
48
  };
29
49
  return [get, set];
30
50
  }
31
51
  function createEffect(fn) {
32
52
  const effect = {
33
53
  cleanups: [],
54
+ parent: currentOwner,
34
55
  deps: /* @__PURE__ */ new Set(),
35
56
  disposed: false,
36
57
  run() {
@@ -58,7 +79,7 @@ function onCleanup(fn) {
58
79
  currentOwner?.cleanups.push(fn);
59
80
  }
60
81
  function createRoot(fn) {
61
- const root = { cleanups: [] };
82
+ const root = { cleanups: [], parent: currentOwner };
62
83
  const prevOwner = currentOwner;
63
84
  const prevEffect = currentEffect;
64
85
  currentOwner = root;
@@ -85,6 +106,30 @@ function onMount(fn) {
85
106
  }
86
107
  });
87
108
  }
109
+ function createContext(defaultValue) {
110
+ return { id: /* @__PURE__ */ Symbol("cajoa-context"), defaultValue };
111
+ }
112
+ function provide(ctx, value, fn) {
113
+ const owner = {
114
+ cleanups: [],
115
+ parent: currentOwner,
116
+ context: /* @__PURE__ */ new Map([[ctx.id, value]])
117
+ };
118
+ onCleanup(() => clean(owner));
119
+ const prev = currentOwner;
120
+ currentOwner = owner;
121
+ try {
122
+ return fn();
123
+ } finally {
124
+ currentOwner = prev;
125
+ }
126
+ }
127
+ function useContext(ctx) {
128
+ for (let o = currentOwner; o; o = o.parent) {
129
+ if (o.context?.has(ctx.id)) return o.context.get(ctx.id);
130
+ }
131
+ return ctx.defaultValue;
132
+ }
88
133
 
89
134
  // src/dom/h.ts
90
135
  function h(tag, props, ...children) {
@@ -118,12 +163,12 @@ function toNodes(value) {
118
163
  return out;
119
164
  }
120
165
  function appendChildren(parent, children) {
121
- const pending = [...children].reverse();
122
- while (pending.length > 0) {
123
- const child = pending.pop();
166
+ const pending2 = [...children].reverse();
167
+ while (pending2.length > 0) {
168
+ const child = pending2.pop();
124
169
  if (Array.isArray(child)) {
125
170
  for (let i = child.length - 1; i >= 0; i--) {
126
- pending.push(child[i]);
171
+ pending2.push(child[i]);
127
172
  }
128
173
  continue;
129
174
  }
@@ -132,17 +177,29 @@ function appendChildren(parent, children) {
132
177
  const end = document.createComment("");
133
178
  parent.append(start, end);
134
179
  createEffect(() => {
135
- const nodes = toNodes(child());
136
- while (start.nextSibling && start.nextSibling !== end) {
137
- start.nextSibling.remove();
138
- }
139
- end.before(...nodes);
180
+ reconcile(start, end, toNodes(child()));
140
181
  });
141
182
  } else {
142
183
  parent.append(...toNodes(child));
143
184
  }
144
185
  }
145
186
  }
187
+ function reconcile(start, end, next) {
188
+ const keep = new Set(next);
189
+ let cur = start.nextSibling;
190
+ while (cur && cur !== end) {
191
+ const after = cur.nextSibling;
192
+ if (!keep.has(cur)) cur.remove();
193
+ cur = after;
194
+ }
195
+ const parent = end.parentNode;
196
+ let ref = start.nextSibling;
197
+ for (const node of next) {
198
+ if (node === ref)
199
+ ref = ref.nextSibling;
200
+ else parent.insertBefore(node, ref);
201
+ }
202
+ }
146
203
 
147
204
  // src/dom/render.ts
148
205
  function render(component, container) {
@@ -162,16 +219,54 @@ function Show(props) {
162
219
 
163
220
  // src/components/for.ts
164
221
  function For(props) {
165
- return () => props.each().map((item, i) => props.children(item, i));
222
+ let cache = /* @__PURE__ */ new Map();
223
+ onCleanup(() => {
224
+ cache.forEach((e) => e.dispose());
225
+ cache.clear();
226
+ });
227
+ return () => {
228
+ const items = props.each();
229
+ const next = /* @__PURE__ */ new Map();
230
+ const nodes = [];
231
+ items.forEach((item, i) => {
232
+ const k = props.key ? props.key(item, i) : item;
233
+ let entry = cache.get(k);
234
+ if (!entry) {
235
+ entry = createRoot((dispose) => ({
236
+ node: props.children(item, i),
237
+ dispose
238
+ }));
239
+ }
240
+ next.set(k, entry);
241
+ nodes.push(entry.node);
242
+ });
243
+ cache.forEach((entry, k) => {
244
+ if (!next.has(k)) entry.dispose();
245
+ });
246
+ cache = next;
247
+ return nodes;
248
+ };
249
+ }
250
+
251
+ // src/reactivity/memo.ts
252
+ function createMemo(fn) {
253
+ const [value, setValue] = createSignal(void 0);
254
+ createEffect(() => setValue(() => fn()));
255
+ return value;
166
256
  }
167
257
  export {
168
258
  For,
169
259
  Show,
260
+ batch,
261
+ createContext,
170
262
  createEffect,
263
+ createMemo,
171
264
  createRoot,
172
265
  createSignal,
173
266
  h,
174
267
  onCleanup,
175
268
  onMount,
176
- render
269
+ provide,
270
+ render,
271
+ useContext
177
272
  };
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
- {
2
- "name": "cajoa",
3
- "version": "0.1.0",
4
- "description": "A beginner-friendly, signals-based frontend framework.",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "import": {
12
- "types": "./dist/index.d.ts",
13
- "default": "./dist/index.js"
14
- },
15
- "require": {
16
- "types": "./dist/index.d.cts",
17
- "default": "./dist/index.cjs"
18
- }
19
- }
20
- },
21
- "files": [
22
- "dist"
23
- ],
24
- "sideEffects": false,
25
- "scripts": {
26
- "typecheck": "tsc --noEmit",
27
- "dev": "vite --open /playground/",
28
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
29
- "test": "vitest",
30
- "prepublishOnly": "vitest run && npm run build"
31
- },
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/cjmgrc/Cajoa.git"
35
- },
36
- "keywords": [
37
- "frontend",
38
- "framework",
39
- "signals",
40
- "reactive",
41
- "ui",
42
- "cajoa"
43
- ],
44
- "author": "cjmgrc",
45
- "license": "Apache-2.0",
46
- "bugs": {
47
- "url": "https://github.com/cjmgrc/Cajoa/issues"
48
- },
49
- "homepage": "https://github.com/cjmgrc/Cajoa#readme",
50
- "devDependencies": {
51
- "jsdom": "^29.1.1",
52
- "tsup": "^9.0.0",
53
- "typescript": "^5.9.3",
54
- "vite": "^8.3.4",
55
- "vitest": "^5.0.5"
56
- }
57
- }
1
+ {
2
+ "name": "cajoa",
3
+ "version": "0.2.0",
4
+ "description": "A beginner-friendly, signals-based frontend framework.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "scripts": {
26
+ "typecheck": "tsc --noEmit",
27
+ "dev": "vite --open /playground/",
28
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
29
+ "test": "vitest",
30
+ "prepublishOnly": "vitest run && npm run build"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/cjmgrc/Cajoa.git"
35
+ },
36
+ "keywords": [
37
+ "frontend",
38
+ "framework",
39
+ "signals",
40
+ "reactive",
41
+ "ui",
42
+ "cajoa"
43
+ ],
44
+ "author": "cjmgrc",
45
+ "license": "Apache-2.0",
46
+ "bugs": {
47
+ "url": "https://github.com/cjmgrc/Cajoa/issues"
48
+ },
49
+ "homepage": "https://github.com/cjmgrc/Cajoa#readme",
50
+ "devDependencies": {
51
+ "jsdom": "^29.1.1",
52
+ "tsup": "^9.0.0",
53
+ "typescript": "^5.9.3",
54
+ "vite": "^8.3.4",
55
+ "vitest": "^5.0.5"
56
+ }
57
+ }