docs: add README, dual license files, AGENTS.md; fix tsup DTS build
- Add README.md with full API reference and usage examples for npm publishing - Add LICENSE-MIT and LICENSE-APACHE for dual licensing (MIT OR Apache-2.0) - Update AGENTS.md with project instructions for AI agents - Fix tsconfig.json: remove declaration, declarationMap, outDir, and sourceMap which conflicted with tsup's own DTS generation (TS5055 overwrite errors)
This commit is contained in:
129
AGENTS.md
Normal file
129
AGENTS.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# AGENTS.md
|
||||
|
||||
Instructions for AI agents working with the @alkdev/ujsx codebase.
|
||||
|
||||
## Project Overview
|
||||
|
||||
`@alkdev/ujsx` is a runtime-agnostic reactive tree library. JSX serves as an intermediate representation — the same declarative tree can target different hosts (markdown, graph structures, DOM, workflow engines) through a `HostConfig` adapter. The core has zero platform-specific assumptions: no `onClick`, no `className`, no `style`.
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
```bash
|
||||
npm run build # tsup production build (ESM + CJS) → dist/
|
||||
npm run build:tsc # Type checking only (tsc --noEmit)
|
||||
npm run lint # Same as build:tsc — tsc --noEmit
|
||||
npm run test # vitest run
|
||||
npm run test:watch # vitest in watch mode
|
||||
npm run test:coverage # vitest run --coverage
|
||||
```
|
||||
|
||||
Always run `npm run lint` and `npm run test` after making changes.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Source Layout
|
||||
|
||||
```
|
||||
src/
|
||||
mod.ts # Barrel export (all public API)
|
||||
core/
|
||||
schema.ts # TypeBox Module (UJSX), UNode/UElement/URoot/UPrimitive types, type guards
|
||||
h.ts # Element factory: h(), createRoot(), createComponent(), Fragment, jsx/jsxs/jsxDEV
|
||||
reactive.ts # ReactiveRoot, reactiveComponent, reactiveElement; re-exports signal/computed/effect/batch
|
||||
context.ts # Context class (signal-backed), Density, Direction, RenderContext
|
||||
events.ts # EventEnvelope, PubSubLike, UjsxEventMap, createPubSubEmitter, proxyEventEmitter
|
||||
pointer.ts # ValuePointer<T>, selectNode(), setNode()
|
||||
jsx-runtime.ts # Re-exports jsx/jsxs/jsxDEV/Fragment for jsxImportSource
|
||||
host/
|
||||
config.ts # HostConfig<TTag,Instance,RootCtx>, Root, createRoot(), mount/reconcile/unmount pipeline
|
||||
fiber.ts # Fiber<I> type, Effect<I> type, disposeFiber(), HostLike
|
||||
reconcile.ts # reconcileProps, reconcileChildren, commitMutations, scheduleUpdate, flushUpdates, LIS
|
||||
transform/
|
||||
registry.ts # TransformRegistry, TransformRule, TransformContext, childCtx, matchesSchema, ctx
|
||||
```
|
||||
|
||||
### Core Data Model
|
||||
|
||||
The tree is the truth. All nodes are `UNode`:
|
||||
|
||||
```typescript
|
||||
type UNode = UPrimitive | UElement | URoot;
|
||||
// UPrimitive = string | number | boolean | null
|
||||
// UElement = { type: string; props: UniversalProps; children: UNode[]; key?: string }
|
||||
// URoot = { type: "root"; props: UniversalProps; children: UNode[] }
|
||||
```
|
||||
|
||||
- `key` on `UElement` is extracted from props by `h()` and promoted to the element level — it is never in `props`
|
||||
- `URoot` is a transparent container: never has a `key`, its children mount directly into the parent
|
||||
- Function components are transparent: called with `{ ...props, children }`, their output mounts in place
|
||||
|
||||
### Rendering Pipeline
|
||||
|
||||
1. `h()` / JSX creates `UElement` trees (pure data)
|
||||
2. `createHostRoot(host, container)` creates a `Root` with a `HostConfig`
|
||||
3. `root.render(node)` mounts the tree via `HostConfig` methods
|
||||
4. Re-renders use `reconcileChildren` (key-based, LIS-optimized) and `reconcileProps` (TypeBox `Value.Diff`/`Value.Equal`/`Value.Hash`)
|
||||
5. `wireSignalToFiber` binds Preact signals to fibers for automatic `scheduleUpdate` → `flushUpdates`
|
||||
|
||||
### Key Subsystems
|
||||
|
||||
- **Reactivity**: Built on `@preact/signals-core`. `Signal<UNode>` flows through `computed` → `effect` → reconciler. Do not introduce alternative reactive systems.
|
||||
- **HostConfig**: The sole integration point for platform-specific logic. Three type params: `TTag` (allowed tags), `Instance` (host instance type), `RootCtx` (root context type). Required methods: `createRootContext`, `createInstance`, `createTextInstance`, `appendChild`. Optional: `insertBefore`, `removeChild`, `prepareUpdate`, `commitUpdate`, `finalizeRoot`, `finalizeInstance`, `emit`.
|
||||
- **Transforms**: `TransformRegistry` with priority-sorted `TransformRule`s. Rules match by `direction` and `match()` predicate. The `next()` callback enables recursive transformation. Directions use Unicode arrows (e.g. `"ujsx→mdast"`).
|
||||
- **Pointers**: `ValuePointer<T>` wraps `signal<T>` with a path. `selectNode`/`setNode` provide immutable tree navigation and updates.
|
||||
|
||||
### TypeBox Usage
|
||||
|
||||
`UJSX` is a `Type.Module` from `@alkdev/typebox`. Schema keys: `UPrimitive`, `PropValue`, `UniversalProps`, `UElement`, `URoot`, `UNode`. Runtime validation via `Value.Check(UJSX.Import("UElement"), node)`. The reconciler uses `Value.Diff`, `Value.Equal`, and `Value.Hash` for prop diffing with function-prop stripping.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
- **No comments in code** unless explicitly requested
|
||||
- **ESM primary**: `"type": "module"`, CJS is a distribution compatibility layer
|
||||
- **No Node-specific APIs in core**: `src/core/` and `src/transform/` must not import `fs`, `path`, `child_process`, etc.
|
||||
- **Dual format via tsup**: Same source produces ESM and CJS; exports map always has matching `import` and `require` entries
|
||||
- **TypeBox is non-negotiable**: Not optional, not replaceable
|
||||
- **`@preact/signals-core` is the only reactive primitive**: No RxJS, Solid signals, or Vue reactivity
|
||||
- **Platform agnostic**: No DOM APIs, Three.js APIs, or any platform-specific API calls in core
|
||||
|
||||
## Sub-path Exports
|
||||
|
||||
Each sub-path maps to a single source file — no barrel re-exports within sub-paths:
|
||||
|
||||
| Import | Source |
|
||||
|--------|--------|
|
||||
| `@alkdev/ujsx` | `src/mod.ts` |
|
||||
| `@alkdev/ujsx/schema` | `src/core/schema.ts` |
|
||||
| `@alkdev/ujsx/h` | `src/core/h.ts` |
|
||||
| `@alkdev/ujsx/reactive` | `src/core/reactive.ts` |
|
||||
| `@alkdev/ujsx/context` | `src/core/context.ts` |
|
||||
| `@alkdev/ujsx/events` | `src/core/events.ts` |
|
||||
| `@alkdev/ujsx/pointer` | `src/core/pointer.ts` |
|
||||
| `@alkdev/ujsx/host` | `src/host/config.ts` |
|
||||
| `@alkdev/ujsx/transform` | `src/transform/registry.ts` |
|
||||
| `@alkdev/ujsx/jsx-runtime` | `src/core/jsx-runtime.ts` |
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are in `test/` using Vitest. Each test file maps to a specific feature area. Tests import directly from `src/` (not from `dist/`). When adding features, add or update corresponding test files.
|
||||
|
||||
## Architecture Documentation
|
||||
|
||||
Detailed architecture docs live in `docs/architecture/`:
|
||||
- `schema.md` — TypeBox Module, type definitions, type guards
|
||||
- `element-factory.md` — h(), createRoot(), createComponent(), Fragment
|
||||
- `reactive-layer.md` — ReactiveRoot, reactiveComponent, reactiveElement, signal integration
|
||||
- `host-config.md` — HostConfig interface, createRoot(), mount pipeline, reconciler gap
|
||||
- `reconciler.md` — Fiber tree, reconciliation algorithm, update scheduling, TypeBox optimizations
|
||||
- `lifecycle.md` — Mount, update, unmount/dispose lifecycle, signal cleanup
|
||||
- `transforms.md` — TransformRegistry, TransformRule, TransformContext, bi-directional transforms
|
||||
- `events.md` — EventEnvelope, PubSubLike, UjsxEventMap
|
||||
- `pointers.md` — ValuePointer, selectNode, setNode, tree navigation
|
||||
- `build-distribution.md` — Package structure, exports map, dependencies, runtime targets
|
||||
|
||||
Design decisions (ADRs) are in `docs/architecture/decisions/`:
|
||||
- 001: HTML-agnostic core
|
||||
- 002: TypeBox Module as type registry
|
||||
- 003: Preact signals-core for reactivity
|
||||
- 004: `key` as first-class field on UElement
|
||||
- 005: Signal-driven updates for props, reconciliation for structure
|
||||
Reference in New Issue
Block a user