@usenagi/core 0.3.0 → 0.4.1
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/dist/addons/cue.es.js +1 -1
- package/dist/addons/scheduler.cjs.js +1 -1
- package/dist/addons/scheduler.es.js +26 -27
- package/dist/main.es.js +120 -118
- package/dist/main.umd.js +1 -1
- package/package.json +3 -17
- package/types/addons/cue/index.d.ts +1 -1
- package/types/addons/scheduler/_internal/pending.d.ts +11 -0
- package/types/addons/scheduler/_internal/schedule.d.ts +11 -0
- package/types/addons/scheduler/index.d.ts +10 -2
- package/types/core/_internal/addonRegistry.d.ts +10 -0
- package/types/core/_internal/component.d.ts +24 -0
- package/types/core/_internal/registry.d.ts +5 -0
- package/types/core/addon.d.ts +0 -7
- package/types/core/app.d.ts +2 -3
- package/types/core/component.d.ts +0 -22
- package/types/core/error.d.ts +2 -3
- package/types/core/runtime.d.ts +4 -3
- package/types/hooks/{useDomRef.d.ts → core/useDomRef.d.ts} +1 -1
- package/types/hooks/{useSlot.d.ts → core/useSlot.d.ts} +2 -3
- package/types/main.d.ts +7 -8
- package/types/types.d.ts +10 -8
- package/LICENSE +0 -21
- package/README.ja.md +0 -269
- package/README.md +0 -269
- package/types/addons/scheduler/addon.d.ts +0 -10
- package/types/addons/scheduler/pending.d.ts +0 -11
- package/types/addons/scheduler/scheduler.d.ts +0 -4
- package/types/addons/scheduler/task.d.ts +0 -2
- package/types/core/internal/registry.d.ts +0 -4
- package/types/hooks/domRefs.d.ts +0 -2
- package/types/utils/isAbortError.d.ts +0 -1
- /package/types/{hooks/createContext.d.ts → core/context.d.ts} +0 -0
- /package/types/{props.d.ts → core/props.d.ts} +0 -0
package/types/core/addon.d.ts
CHANGED
|
@@ -20,13 +20,6 @@ export type AddonContext = {
|
|
|
20
20
|
addMountMiddleware(middleware: MountMiddleware): void;
|
|
21
21
|
addUnmountMiddleware(middleware: UnmountMiddleware): void;
|
|
22
22
|
};
|
|
23
|
-
type AddonRegistry = AddonContext & {
|
|
24
|
-
composeComponent<S extends ComponentSetup>(setup: S): S;
|
|
25
|
-
composeMount(mountFn: MountFn, setup: ComponentSetup, opts: MountOptions): MountFn;
|
|
26
|
-
composeUnmount(unmountFn: UnmountFn): UnmountFn;
|
|
27
|
-
install(addon: Addon): void;
|
|
28
|
-
};
|
|
29
|
-
export declare function createAddonRegistry(): AddonRegistry;
|
|
30
23
|
/**
|
|
31
24
|
* Identity helper for type inference only — no runtime effect.
|
|
32
25
|
*/
|
package/types/core/app.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { ComponentSetup, RefElement } from "../types";
|
|
1
|
+
import type { ComponentContext, ComponentSetup, ExposedSetup, RefElement } from "../types";
|
|
2
2
|
import type { Addon, MountOptions } from "./addon";
|
|
3
|
-
import type { ComponentContext } from "./component";
|
|
4
3
|
type App = {
|
|
5
4
|
install(...addons: Addon[]): App;
|
|
6
|
-
component<S extends ComponentSetup>(component: S, opts?: MountOptions): (el: RefElement, props?: Record<string, any>) => ComponentContext<ReturnType<S["setup"]
|
|
5
|
+
component<S extends ComponentSetup>(component: S, opts?: MountOptions): (el: RefElement, props?: Record<string, any>) => ComponentContext<ExposedSetup<ReturnType<S["setup"]>>> | void;
|
|
7
6
|
unmount(targets: RefElement[]): void;
|
|
8
7
|
};
|
|
9
8
|
export declare function create(): App;
|
|
@@ -1,26 +1,4 @@
|
|
|
1
1
|
import type { ComponentProps, ComponentSetup, RefElement } from "../types";
|
|
2
|
-
export declare enum LifecycleHooks {
|
|
3
|
-
MOUNTED = "Mounted",
|
|
4
|
-
UNMOUNTED = "Unmounted"
|
|
5
|
-
}
|
|
6
|
-
export declare class ComponentContext<T = any> {
|
|
7
|
-
#private;
|
|
8
|
-
private [LifecycleHooks.MOUNTED];
|
|
9
|
-
private [LifecycleHooks.UNMOUNTED];
|
|
10
|
-
parent: ComponentContext<T> | null;
|
|
11
|
-
readonly uid: string;
|
|
12
|
-
readonly name: string;
|
|
13
|
-
current: ReturnType<ComponentSetup<T>["setup"]>;
|
|
14
|
-
props: Parameters<ComponentSetup<T>["setup"]>[1];
|
|
15
|
-
element: RefElement;
|
|
16
|
-
provides: Map<symbol, unknown>;
|
|
17
|
-
constructor(element: RefElement, name: string);
|
|
18
|
-
onMount: () => void;
|
|
19
|
-
onUnmount: () => void;
|
|
20
|
-
addChild: (child: ComponentContext) => void;
|
|
21
|
-
removeChild: (child: ComponentContext) => void;
|
|
22
|
-
get childElements(): RefElement[];
|
|
23
|
-
}
|
|
24
2
|
export declare function defineComponent<SetupResult extends Record<string, unknown> | void, Props extends Record<string, unknown>>(opts: {
|
|
25
3
|
name: string;
|
|
26
4
|
props: Props;
|
package/types/core/error.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RefElement } from "../types";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ComponentContextImpl } from "./_internal/component";
|
|
3
3
|
export type LifecycleErrorDetails = {
|
|
4
4
|
phase: "setup" | "mount" | "unmount" | "removeChild";
|
|
5
5
|
name: string;
|
|
@@ -11,10 +11,9 @@ export type LifecycleErrorDetails = {
|
|
|
11
11
|
props?: unknown;
|
|
12
12
|
cause: unknown;
|
|
13
13
|
};
|
|
14
|
-
export declare function traceComponentTree(context: ComponentContext): string;
|
|
15
14
|
export declare class LifecycleError extends Error {
|
|
16
15
|
readonly details: LifecycleErrorDetails;
|
|
17
16
|
constructor(details: LifecycleErrorDetails);
|
|
18
|
-
static create(phase: LifecycleErrorDetails["phase"], target:
|
|
17
|
+
static create(phase: LifecycleErrorDetails["phase"], target: ComponentContextImpl, cause: unknown, parent?: ComponentContextImpl | null | undefined, extra?: Partial<LifecycleErrorDetails>): LifecycleError;
|
|
19
18
|
}
|
|
20
19
|
export declare function isLifecycleError(error: unknown): error is LifecycleError;
|
package/types/core/runtime.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ComponentContextImpl } from "./_internal/component";
|
|
2
2
|
import type { ComponentSetup, RefElement } from "../types";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
declare function getCurrentComponent(hookName: string): ComponentContextImpl;
|
|
4
|
+
declare function createComponent<S extends ComponentSetup>(wrap: S, root: RefElement, props?: Record<string, any>): ComponentContextImpl<ReturnType<S["setup"]>>;
|
|
5
|
+
export {};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ComponentContext } from "
|
|
2
|
-
import type { ComponentSetup, RefElement } from "../types";
|
|
1
|
+
import type { ComponentContext, ComponentSetup, ExposedSetup, RefElement } from "../../types";
|
|
3
2
|
export declare function useSlot(): {
|
|
4
|
-
addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Partial<Parameters<Child["setup"]>[1]>): ComponentContext<ReturnType<Child["setup"]
|
|
3
|
+
addChild<Child extends ComponentSetup>(targetOrTargets: RefElement | RefElement[], child: Child, props?: Partial<Parameters<Child["setup"]>[1]>): ComponentContext<ExposedSetup<ReturnType<Child["setup"]>>>[];
|
|
5
4
|
removeChild(children: ComponentContext[]): void;
|
|
6
5
|
};
|
package/types/main.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
export { defineAddon } from "./core/addon";
|
|
2
2
|
export { create } from "./core/app";
|
|
3
3
|
export { defineComponent } from "./core/component";
|
|
4
|
+
export { createContext, withContext } from "./core/context";
|
|
4
5
|
export { isLifecycleError, LifecycleError } from "./core/error";
|
|
5
6
|
export { useMount, useUnmount } from "./core/lifecycle";
|
|
7
|
+
export { propTypes } from "./core/props";
|
|
6
8
|
export { readonly, signal, useComputed, useWatch } from "./core/reactivity";
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
+
export { useDomRef } from "./hooks/core/useDomRef";
|
|
10
|
+
export { useSlot } from "./hooks/core/useSlot";
|
|
9
11
|
export { useEvent } from "./hooks/useEvent";
|
|
10
12
|
export { useIntersectionWatch } from "./hooks/useIntersectionWatch";
|
|
11
13
|
export { useMediaQuery } from "./hooks/useMediaQuery";
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export type { Addon, AddonContext, ComponentMiddleware, MountFn, MountMiddleware, MountOptions, UnmountFn, UnmountMiddleware, } from "./core/addon";
|
|
15
|
-
export type { ComponentContext } from "./core/component";
|
|
14
|
+
export type { Addon, AddonContext, MountOptions } from "./core/addon";
|
|
15
|
+
export type { Provider } from "./core/context";
|
|
16
16
|
export type { LifecycleErrorDetails } from "./core/error";
|
|
17
17
|
export type { ReadonlySignal, Signal } from "./core/reactivity";
|
|
18
|
-
export type {
|
|
19
|
-
export type { ComponentSetup, Cue, IComponent, RefElement, SchedulePriority, Scheduler, } from "./types";
|
|
18
|
+
export type { Cleanup, ComponentContext, ComponentSetup, Cue, RefElement, SchedulePriority, } from "./types";
|
package/types/types.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
export type RefElement = HTMLElement | SVGElement;
|
|
2
2
|
export type ComponentProps<Props> = Readonly<Props>;
|
|
3
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
4
|
+
/** Normalize the return value of setup to the type stored in `current` (void / undefined → empty object). */
|
|
5
|
+
export type ExposedSetup<T> = IsAny<T> extends true ? Record<string, unknown> : [T] extends [void | undefined] ? Record<string, never> : T extends Record<string, unknown> ? T : Record<string, never>;
|
|
3
6
|
export type ComponentSetup<SetupResult = void | Record<string, unknown>, Props extends Record<string, unknown> = Record<string, unknown>> = {
|
|
4
7
|
name: string;
|
|
5
8
|
setup(el: RefElement, props: ComponentProps<Props>): SetupResult;
|
|
6
9
|
};
|
|
7
|
-
/**
|
|
8
|
-
export type
|
|
10
|
+
/** Mounted component instance exposed to app / hook callers. */
|
|
11
|
+
export type ComponentContext<Exposed extends Record<string, unknown> = Record<string, never>> = {
|
|
12
|
+
readonly current: Exposed;
|
|
13
|
+
readonly element: RefElement;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
};
|
|
9
16
|
export type Cleanup = () => void;
|
|
10
17
|
export type LifecycleHandler = () => void | Cleanup;
|
|
11
18
|
export type SchedulePriority = "user-blocking" | "user-visible" | "background";
|
|
12
|
-
export type Scheduler = {
|
|
13
|
-
schedule(task: () => void, options?: {
|
|
14
|
-
priority?: SchedulePriority;
|
|
15
|
-
signal?: AbortSignal;
|
|
16
|
-
}): void;
|
|
17
|
-
};
|
|
18
19
|
export type Cue = (el: RefElement, signal: AbortSignal) => Promise<void>;
|
|
20
|
+
export {};
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 hayakawasho
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.ja.md
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
[English](./README.md) | **日本語**
|
|
2
|
-
|
|
3
|
-
# nagi
|
|
4
|
-
|
|
5
|
-
**Composition-style ergonomics for vanilla DOM. Bring your own mounter.**
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/@usenagi/core)
|
|
8
|
-
[](https://bundlephobia.com/package/@usenagi/core)
|
|
9
|
-
[](./LICENSE)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Why nagi?
|
|
14
|
-
|
|
15
|
-
**既存 HTML に小さく足せる**
|
|
16
|
-
|
|
17
|
-
WordPress、CMS、Webflow、静的サイトなどに、仮想 DOM やテンプレートを持ち込まず、`setup()` / lifecycle / reactivity を追加できる。
|
|
18
|
-
|
|
19
|
-
**アニメーションと相性が良い**
|
|
20
|
-
|
|
21
|
-
GSAP、Lenis、IntersectionObserver などを `setup()` で初期化し、`useUnmount()` でクリーンアップできる。
|
|
22
|
-
|
|
23
|
-
**マウント戦略を縛らない**
|
|
24
|
-
|
|
25
|
-
`[data-component]` スキャン、manifest、lazy import、MutationObserver などは、利用側で自由に組み立てられる。
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 30-second example
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
// counter.ts
|
|
33
|
-
import { create, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
34
|
-
|
|
35
|
-
const { component } = create();
|
|
36
|
-
|
|
37
|
-
component({
|
|
38
|
-
name: "counter",
|
|
39
|
-
setup() {
|
|
40
|
-
const { refs } = useDomRef<{
|
|
41
|
-
count: HTMLSpanElement;
|
|
42
|
-
btn: HTMLButtonElement;
|
|
43
|
-
}>();
|
|
44
|
-
|
|
45
|
-
const n = signal(0);
|
|
46
|
-
useWatch(n, (v) => {
|
|
47
|
-
refs.count.textContent = String(v);
|
|
48
|
-
});
|
|
49
|
-
refs.btn.addEventListener("click", () => {
|
|
50
|
-
n.value++;
|
|
51
|
-
});
|
|
52
|
-
},
|
|
53
|
-
})(document.querySelector("#counter")!);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
```html
|
|
57
|
-
<div id="counter">
|
|
58
|
-
<span data-ref="count">0</span>
|
|
59
|
-
<button data-ref="btn">+</button>
|
|
60
|
-
</div>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Quick start
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
npm i @usenagi/core
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### First component
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
import { create, defineComponent, signal, useWatch, useDomRef } from "@usenagi/core";
|
|
75
|
-
|
|
76
|
-
const Greeting = defineComponent({
|
|
77
|
-
name: "greeting",
|
|
78
|
-
setup(el, props) {
|
|
79
|
-
const { refs } = useDomRef<{ message: HTMLParagraphElement }>();
|
|
80
|
-
const text = signal((props.name as string) ?? "world");
|
|
81
|
-
|
|
82
|
-
useWatch(text, (v) => {
|
|
83
|
-
refs.message.textContent = `Hello, ${v}!`;
|
|
84
|
-
});
|
|
85
|
-
refs.message.textContent = `Hello, ${text.value}!`;
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
create().component(Greeting)(document.querySelector("#app")!);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Scheduler + deferred mount
|
|
93
|
-
|
|
94
|
-
遅延マウントが必要な場合は、scheduler / cue addons を追加する。
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
import { create } from "@usenagi/core";
|
|
98
|
-
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
99
|
-
import { visible, idle } from "@usenagi/core/addons/cue";
|
|
100
|
-
|
|
101
|
-
const app = create().install(schedulerAddon());
|
|
102
|
-
|
|
103
|
-
// mount when the element enters the viewport
|
|
104
|
-
app.component(HeavyWidget, { when: visible() })(el);
|
|
105
|
-
|
|
106
|
-
// mount during browser idle time
|
|
107
|
-
app.component(Analytics, { when: idle() })(el);
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
`schedulerAddon()` を使うと、`when` は `setup()` の前に待機する条件、`priority` は `setup()` を含む mount task の実行タイミングを決める。
|
|
111
|
-
|
|
112
|
-
### BYO mounter recipe
|
|
113
|
-
|
|
114
|
-
`[data-component]` スキャン、manifest、cue を組み合わせた自動マウントの例。
|
|
115
|
-
→ [examples/recipes/byo-mounter](./examples/recipes/byo-mounter/main.ts)
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## API
|
|
120
|
-
|
|
121
|
-
### Reactivity
|
|
122
|
-
|
|
123
|
-
| API | 説明 |
|
|
124
|
-
| ---------------------- | ------------------------------------------------------ |
|
|
125
|
-
| `signal(value)` | `.value` を持つリアクティブな値コンテナを作成する |
|
|
126
|
-
| `readonly(signal)` | 書き込み可能な `signal` の読み取り専用ラッパー |
|
|
127
|
-
| `useComputed(fn)` | `signal` の依存を自動追跡する派生値 |
|
|
128
|
-
| `useWatch(target, cb)` | 値変更時に `cb` を呼ぶ。unmount 時に自動で購読解除する |
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
const width = signal(10);
|
|
132
|
-
const height = signal(5);
|
|
133
|
-
const area = useComputed(() => width.value * height.value); // auto-recomputed
|
|
134
|
-
|
|
135
|
-
useWatch(area, (v) => {
|
|
136
|
-
output.textContent = String(v);
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Lifecycle
|
|
141
|
-
|
|
142
|
-
| API | 説明 |
|
|
143
|
-
| ---------------- | ------------------------------------------- |
|
|
144
|
-
| `useMount(fn)` | コンポーネントのマウント完了後に1回実行する |
|
|
145
|
-
| `useUnmount(fn)` | unmount 時に実行する。クリーンアップに使う |
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import gsap from 'gsap';
|
|
149
|
-
|
|
150
|
-
setup(el) {
|
|
151
|
-
const tween = gsap.from(el, { opacity: 0, duration: 0.4 });
|
|
152
|
-
useUnmount(() => tween.kill());
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### DOM helpers
|
|
157
|
-
|
|
158
|
-
ルート要素には **`setup(el)`** を、**`[data-ref]`** の子要素には **`useDomRef()`** を使う。
|
|
159
|
-
|
|
160
|
-
| API | 説明 |
|
|
161
|
-
| ------------------------------ | ------------------------------------------------------- |
|
|
162
|
-
| `useDomRef<T>()` | `[data-ref]` 要素への型付きアクセス |
|
|
163
|
-
| `useEvent(el, event, handler)` | イベントリスナーを追加する。unmount 時に自動で除去する |
|
|
164
|
-
| `useSlot()` | 子コンポーネントをマウントする。親の unmount に連動する |
|
|
165
|
-
|
|
166
|
-
### Parent / child
|
|
167
|
-
|
|
168
|
-
`useSlot()` で子コンポーネントをマウントできる。親から子へは `props` または `createContext` / `withContext` で値を渡せる。`addChild()` が返す child context から、子の `setup()` の返り値も参照できる。
|
|
169
|
-
|
|
170
|
-
→ [examples/parent-child](./examples/parent-child/main.ts)
|
|
171
|
-
|
|
172
|
-
### Observers
|
|
173
|
-
|
|
174
|
-
| API | 説明 |
|
|
175
|
-
| --------------------------------- | ----------------------------------------------------------- |
|
|
176
|
-
| `useIntersectionWatch(cb, opts?)` | IntersectionObserver のラッパー。unmount 時に自動で切断する |
|
|
177
|
-
| `useMediaQuery(query)` | `matchMedia` の結果を `ReadonlySignal<boolean>` で返す |
|
|
178
|
-
|
|
179
|
-
### Addons
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { create, defineAddon } from "@usenagi/core";
|
|
183
|
-
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
184
|
-
|
|
185
|
-
const app = create().install(schedulerAddon(), myAddon());
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
| API | 説明 |
|
|
189
|
-
| --- | --- |
|
|
190
|
-
| `defineAddon({ name, install(ctx) })` | addon を定義する(`ctx` は `AddonContext`) |
|
|
191
|
-
| `app.install(...addons)` | app に addon を登録する(複数可) |
|
|
192
|
-
| `ctx.addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` | mount / unmount / ComponentSetup の middleware を追加する |
|
|
193
|
-
| `ctx.installedAddons` | この app に install 済みの addon 名 |
|
|
194
|
-
|
|
195
|
-
`addMountMiddleware` / `addUnmountMiddleware` / `addComponentMiddleware` は **後から install した addon ほど外側**に適用される(`install(a, b)` なら実行順は `b → a → コア`)。
|
|
196
|
-
|
|
197
|
-
遅延 mount には `schedulerAddon()` が必要。`when` や `priority` を使う場合も同様で、これらの mount option は scheduler addon が解釈する。addon の状態(scheduler / pending)は **各 app の `install` ごと**に作られる。
|
|
198
|
-
|
|
199
|
-
#### Scheduler + cue
|
|
200
|
-
|
|
201
|
-
```ts
|
|
202
|
-
import { schedulerAddon } from "@usenagi/core/addons/scheduler";
|
|
203
|
-
import { visible, idle, interaction, media } from "@usenagi/core/addons/cue";
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
| API | 説明 |
|
|
207
|
-
| --- | --- |
|
|
208
|
-
| `schedulerAddon(opts?)` | 遅延 mount 用 addon(内部で `createScheduler` を使用) |
|
|
209
|
-
| `createScheduler(opts?)` | カスタム Scheduler 実装用の low-level API |
|
|
210
|
-
| `visible(opts?)` | 要素が viewport に入ったときに解決する Cue |
|
|
211
|
-
| `idle(timeout?)` | `requestIdleCallback` で解決する Cue |
|
|
212
|
-
| `interaction(events?)` | 最初のユーザー操作で解決する Cue |
|
|
213
|
-
| `media(query)` | media query が一致したときに解決する Cue |
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Comparison
|
|
218
|
-
|
|
219
|
-
| | **nagi** | Alpine.js | Stimulus | petite-vue |
|
|
220
|
-
| -------------------------- | -------- | --------- | -------- | ---------- |
|
|
221
|
-
| Inline JS in HTML | ✗ | ◯ | ✗ | ◯ |
|
|
222
|
-
| Composition-style setup | ◯ | △ | ✗ | ◯ |
|
|
223
|
-
| BYO mounter | ◯ | △ | △ | △ |
|
|
224
|
-
| Async mount cue | ◯ | ✗ | ✗ | ✗ |
|
|
225
|
-
| Lifecycle cleanup | ◯ | △ | ◯ | △ |
|
|
226
|
-
| computed (derived signals) | ◯ | ◯ | ✗ | ◯ |
|
|
227
|
-
| Core gzip | ~2.5 kB | ~16 kB | ~8 kB | ~6 kB |
|
|
228
|
-
|
|
229
|
-
(◯ = 組み込み、△ = 利用側の実装・規約で対応可能、✗ = 主な機能ではない)
|
|
230
|
-
|
|
231
|
-
- **vs Alpine / petite-vue**: HTML に式を直接書かず、ロジックを `.ts` に集約する。
|
|
232
|
-
- **vs Stimulus**: Controller 規約はない。マウント戦略は利用側で自由に組み立てられる。
|
|
233
|
-
- **vs React / Vue**: 宣言的 UI フレームワークではなく、既存 DOM に lifecycle を足す薄いレイヤー。
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## When to use / When not to
|
|
238
|
-
|
|
239
|
-
**向いているケース:**
|
|
240
|
-
|
|
241
|
-
- React や Vue のランタイムを持ち込みにくいプロジェクト(CMS、Webflow、WordPress など)
|
|
242
|
-
- GSAP や Lenis を多用する、アニメーション主体のサイト
|
|
243
|
-
- ページの一部だけにインタラクティブな UI を追加したい場合
|
|
244
|
-
- `setup()`、lifecycle、reactivity による composition-style で書きたいが、仮想 DOM は不要な場合
|
|
245
|
-
|
|
246
|
-
**向いていないケース:**
|
|
247
|
-
|
|
248
|
-
- リスト描画や条件分岐を HTML テンプレートで書きたい場合(`v-for` や `v-if` 相当はない)
|
|
249
|
-
- 複雑なオブジェクトの深いリアクティビティが必要な場合(`reactive({})` は提供しない)
|
|
250
|
-
- SSR / hydration が必要な場合
|
|
251
|
-
- 状態管理、ルーティング、宣言的な view rendering をフレームワークにまとめて任せたい場合
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## Examples
|
|
256
|
-
|
|
257
|
-
| Example | 説明 |
|
|
258
|
-
| ----------------------------------------------------- | --------------------------------------------------- |
|
|
259
|
-
| [basic-counter](./examples/basic-counter/) | 最小の `signal` + `useWatch` 例 |
|
|
260
|
-
| [computed](./examples/computed/) | `useComputed` による派生値(width × height = area) |
|
|
261
|
-
| [parent-child](./examples/parent-child/) | `createContext` + `withContext` + `useSlot` |
|
|
262
|
-
| [lenis-scroll-scene](./examples/lenis-scroll-scene/) | Lenis + `useComputed` によるスクロール進捗連動 |
|
|
263
|
-
| [byo-mounter recipe](./examples/recipes/byo-mounter/) | `[data-component]` スキャン + manifest + cue |
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## License
|
|
268
|
-
|
|
269
|
-
MIT © [hayakawasho](https://github.com/hayakawasho)
|