@lytjs/core-signal 6.0.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 ADDED
@@ -0,0 +1,182 @@
1
+ # @lytjs/core-signal
2
+
3
+ > LytJS 核心应用 API(Signal 渲染模式),适合细粒度响应式场景
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @lytjs/core-signal
9
+ ```
10
+
11
+ ## 与 @lytjs/core 的区别
12
+
13
+ | 特性 | @lytjs/core-signal | @lytjs/core |
14
+ | -------- | ------------------ | --------------------- |
15
+ | 渲染模式 | 仅 Signal | VNode + Signal 双模式 |
16
+ | 包体积 | 更小 | 完整功能 |
17
+ | 适用场景 | 细粒度响应式应用 | 需要双模式切换的应用 |
18
+ | 性能特点 | 细粒度更新 | 灵活选择 |
19
+
20
+ ## 核心 API
21
+
22
+ ### createApp
23
+
24
+ 创建 Signal 模式应用实例:
25
+
26
+ ```typescript
27
+ import { createApp, defineComponent, signal } from '@lytjs/core-signal';
28
+
29
+ const App = defineComponent({
30
+ template: `
31
+ <div>
32
+ <p>Count: {{ count() }}</p>
33
+ <button @click="increment">Increment</button>
34
+ </div>
35
+ `,
36
+ setup() {
37
+ const count = signal(0);
38
+
39
+ const increment = () => {
40
+ count.set(count() + 1);
41
+ };
42
+
43
+ return { count, increment };
44
+ },
45
+ });
46
+
47
+ createApp(App).mount('#app');
48
+ ```
49
+
50
+ ### Signal API
51
+
52
+ ```typescript
53
+ import { signal, computed, writableComputedSignal } from '@lytjs/core-signal';
54
+
55
+ // 创建 Signal
56
+ const count = signal(0);
57
+
58
+ // 读取值
59
+ console.log(count()); // 0
60
+
61
+ // 设置值
62
+ count.set(10);
63
+
64
+ // 通过 updater 更新
65
+ count.update((v) => v + 1);
66
+
67
+ // 计算 Signal
68
+ const doubled = computed(() => count() * 2);
69
+
70
+ // 可写计算 Signal
71
+ const fullName = writableComputedSignal(
72
+ () => `${firstName()} ${lastName()}`,
73
+ (val) => {
74
+ const [first, last] = val.split(' ');
75
+ firstName.set(first);
76
+ lastName.set(last);
77
+ },
78
+ );
79
+ ```
80
+
81
+ ### 批处理
82
+
83
+ ```typescript
84
+ import { signalBatch, signalUntrack } from '@lytjs/core-signal';
85
+
86
+ // 批量更新
87
+ signalBatch(() => {
88
+ a.set(10);
89
+ b.set(20);
90
+ // 只触发一次通知
91
+ });
92
+
93
+ // 取消追踪
94
+ const value = signalUntrack(() => a());
95
+ ```
96
+
97
+ ### 生命周期钩子
98
+
99
+ ```typescript
100
+ import {
101
+ onMounted,
102
+ onUnmounted,
103
+ onUpdated,
104
+ onBeforeMount,
105
+ onBeforeUnmount,
106
+ onBeforeUpdate,
107
+ onErrorCaptured,
108
+ } from '@lytjs/core-signal';
109
+
110
+ const App = defineComponent({
111
+ setup() {
112
+ onMounted(() => {
113
+ console.log('组件已挂载');
114
+ });
115
+
116
+ onUnmounted(() => {
117
+ console.log('组件已卸载');
118
+ });
119
+
120
+ return () => {
121
+ // Signal 渲染函数
122
+ };
123
+ },
124
+ });
125
+ ```
126
+
127
+ ## 响应式 API
128
+
129
+ 从 @lytjs/reactivity 重导出:
130
+
131
+ ```typescript
132
+ import {
133
+ // Signal
134
+ signal,
135
+ computed,
136
+ writableComputedSignal,
137
+ readonlySignal,
138
+ set,
139
+ update,
140
+ valueOf,
141
+ signalBatch,
142
+ signalUntrack,
143
+ // Ref(与 Signal 互操作)
144
+ ref,
145
+ reactive,
146
+ computed as computedRef,
147
+ watch,
148
+ watchEffect,
149
+ } from '@lytjs/core-signal';
150
+ ```
151
+
152
+ ## Signal vs Ref
153
+
154
+ | 特性 | Signal | Ref |
155
+ | -------- | ----------------- | ---------------- |
156
+ | 读取 | `count()` | `count.value` |
157
+ | 写入 | `count.set()` | `count.value = ` |
158
+ | 更新 | `count.update()` | 手动更新 |
159
+ | 批量 | `signalBatch()` | `batch()` |
160
+ | 取消追踪 | `signalUntrack()` | `untrack()` |
161
+ | 适用 | 细粒度响应 | 对象响应式 |
162
+
163
+ ## 类型定义
164
+
165
+ ```typescript
166
+ import type {
167
+ App,
168
+ AppConfig,
169
+ Component,
170
+ ComponentOptions,
171
+ ComponentPublicInstance,
172
+ Signal,
173
+ ComputedSignal,
174
+ WritableComputedSignal,
175
+ } from '@lytjs/core-signal';
176
+ ```
177
+
178
+ ## 相关包
179
+
180
+ - [@lytjs/core](../core) - 完整核心(支持双模式)
181
+ - [@lytjs/core-vnode](../core-vnode) - 仅 VNode 模式
182
+ - [@lytjs/reactivity](../reactivity) - 响应式系统实现
package/dist/index.cjs ADDED
@@ -0,0 +1,363 @@
1
+ 'use strict';
2
+
3
+ var renderer = require('@lytjs/renderer');
4
+ var commonError = require('@lytjs/common-error');
5
+ var component = require('@lytjs/component');
6
+ var commonScheduler = require('@lytjs/common-scheduler');
7
+ var reactivity = require('@lytjs/reactivity');
8
+ var compiler = require('@lytjs/compiler');
9
+ var domRuntime = require('@lytjs/dom-runtime');
10
+
11
+ // src/create-app.ts
12
+ function createApp(rootComponent, rootProps = null, _options) {
13
+ const installedPlugins = /* @__PURE__ */ new Set();
14
+ let _isUnmounted = false;
15
+ let _isMounted = false;
16
+ let signalRenderer = null;
17
+ let _container = null;
18
+ const context = {
19
+ provides: /* @__PURE__ */ Object.create(null),
20
+ config: {
21
+ errorHandler: void 0,
22
+ warnHandler: void 0,
23
+ performance: false,
24
+ globalProperties: {},
25
+ isCustomElement: void 0,
26
+ compilerOptions: void 0
27
+ }
28
+ };
29
+ const app = {
30
+ get config() {
31
+ return context.config;
32
+ },
33
+ use(plugin, ...options) {
34
+ if (installedPlugins.has(plugin)) return app;
35
+ try {
36
+ if (typeof plugin === "function") {
37
+ plugin(app, ...options);
38
+ } else {
39
+ plugin.install(app, ...options);
40
+ }
41
+ installedPlugins.add(plugin);
42
+ } catch (err) {
43
+ commonError.error(
44
+ `Plugin failed to install: ${typeof plugin === "function" ? plugin.name || "anonymous function" : plugin.install?.name || "plugin"}: ${err}`
45
+ );
46
+ throw err;
47
+ }
48
+ return app;
49
+ },
50
+ async mount(rootContainer) {
51
+ if (_isUnmounted) {
52
+ throw new Error(
53
+ `[LytJS] App has been unmounted and cannot be remounted. Create a new app instance instead.`
54
+ );
55
+ }
56
+ if (!rootComponent) {
57
+ return null;
58
+ }
59
+ if (_container) {
60
+ throw new Error(
61
+ `[LytJS] App is already mounted. Call app.unmount() first before mounting again.`
62
+ );
63
+ }
64
+ const container = typeof rootContainer === "string" ? document.querySelector(rootContainer) : rootContainer;
65
+ if (!container) {
66
+ throw new Error(
67
+ `[LytJS] Failed to mount app: cannot find element matching selector "${rootContainer}". Make sure the target element exists in the DOM before calling app.mount().`
68
+ );
69
+ }
70
+ _container = container;
71
+ try {
72
+ const componentOptions = rootComponent;
73
+ const template = componentOptions.template;
74
+ if (!template) {
75
+ throw new Error(
76
+ `[LytJS] Signal mode requires a template string in the root component. Provide a 'template' property in your component options.`
77
+ );
78
+ }
79
+ const ctx = { ...rootProps };
80
+ if (typeof componentOptions.data === "function") {
81
+ try {
82
+ const dataResult = componentOptions.data();
83
+ Object.assign(ctx, dataResult);
84
+ } catch (e) {
85
+ commonError.error(
86
+ `[LytJS] Failed to execute data() in Signal mode: ${e instanceof Error ? e.message : String(e)}`
87
+ );
88
+ throw e;
89
+ }
90
+ }
91
+ if (typeof componentOptions.setup === "function") {
92
+ try {
93
+ const setupResult = componentOptions.setup(rootProps ?? {}, {});
94
+ if (setupResult && typeof setupResult === "object") {
95
+ Object.assign(ctx, setupResult);
96
+ }
97
+ } catch (e) {
98
+ commonError.error(
99
+ `[LytJS] Failed to execute setup() in Signal mode: ${e instanceof Error ? e.message : String(e)}`
100
+ );
101
+ throw e;
102
+ }
103
+ }
104
+ const beforeMount = componentOptions.beforeMount;
105
+ if (typeof beforeMount === "function") {
106
+ beforeMount.call(ctx);
107
+ }
108
+ signalRenderer = await renderer.createSignalRenderer(template, ctx);
109
+ signalRenderer.render(container);
110
+ _isMounted = true;
111
+ const mounted = componentOptions.mounted;
112
+ if (typeof mounted === "function") {
113
+ mounted.call(ctx);
114
+ }
115
+ return new Proxy({}, {
116
+ get(_, key) {
117
+ if (key === "$el") return container;
118
+ if (key === "$options") return componentOptions;
119
+ return ctx[key];
120
+ },
121
+ set(_, key, value) {
122
+ ctx[key] = value;
123
+ return true;
124
+ }
125
+ });
126
+ } catch (err) {
127
+ if (context.config.errorHandler) {
128
+ context.config.errorHandler(err, null, "mount");
129
+ } else {
130
+ commonError.error(`[LytJS] Failed to mount app: ${err instanceof Error ? err.message : String(err)}`);
131
+ }
132
+ throw err;
133
+ }
134
+ },
135
+ unmount() {
136
+ const componentOptions = rootComponent;
137
+ const beforeUnmount = componentOptions.beforeUnmount;
138
+ if (typeof beforeUnmount === "function") {
139
+ beforeUnmount.call(componentOptions);
140
+ }
141
+ if (signalRenderer) {
142
+ signalRenderer.unmount();
143
+ signalRenderer = null;
144
+ }
145
+ const unmounted = componentOptions.unmounted;
146
+ if (typeof unmounted === "function") {
147
+ unmounted.call(componentOptions);
148
+ }
149
+ _isUnmounted = true;
150
+ _isMounted = false;
151
+ _container = null;
152
+ for (const plugin of installedPlugins) {
153
+ if (typeof plugin !== "function" && plugin != null && typeof plugin.cleanup === "function") {
154
+ try {
155
+ const cleanup = plugin.cleanup;
156
+ if (typeof cleanup === "function") cleanup();
157
+ } catch (err) {
158
+ commonError.error(
159
+ `Plugin cleanup failed: ${typeof plugin === "object" && plugin !== null && "name" in plugin ? plugin.name : "unknown"}: ${err}`
160
+ );
161
+ }
162
+ }
163
+ }
164
+ installedPlugins.clear();
165
+ },
166
+ provide(key, value) {
167
+ context.provides[key] = value;
168
+ return app;
169
+ },
170
+ inject(key, defaultValue) {
171
+ const provides = context.provides;
172
+ if (key in provides) {
173
+ return provides[key];
174
+ }
175
+ if (defaultValue !== void 0) {
176
+ return defaultValue;
177
+ }
178
+ return void 0;
179
+ },
180
+ component(_name, _component) {
181
+ return app;
182
+ },
183
+ directive(_name, _directive) {
184
+ return app;
185
+ },
186
+ mixin(_mixin) {
187
+ return app;
188
+ }
189
+ };
190
+ return app;
191
+ }
192
+ var defineComponent = component.defineComponent;
193
+
194
+ Object.defineProperty(exports, "onBeforeMount", {
195
+ enumerable: true,
196
+ get: function () { return component.onBeforeMount; }
197
+ });
198
+ Object.defineProperty(exports, "onBeforeUnmount", {
199
+ enumerable: true,
200
+ get: function () { return component.onBeforeUnmount; }
201
+ });
202
+ Object.defineProperty(exports, "onErrorCaptured", {
203
+ enumerable: true,
204
+ get: function () { return component.onErrorCaptured; }
205
+ });
206
+ Object.defineProperty(exports, "onMounted", {
207
+ enumerable: true,
208
+ get: function () { return component.onMounted; }
209
+ });
210
+ Object.defineProperty(exports, "onUnmounted", {
211
+ enumerable: true,
212
+ get: function () { return component.onUnmounted; }
213
+ });
214
+ Object.defineProperty(exports, "nextTick", {
215
+ enumerable: true,
216
+ get: function () { return commonScheduler.nextTick; }
217
+ });
218
+ Object.defineProperty(exports, "computed", {
219
+ enumerable: true,
220
+ get: function () { return reactivity.computed; }
221
+ });
222
+ Object.defineProperty(exports, "computedSignal", {
223
+ enumerable: true,
224
+ get: function () { return reactivity.computedSignal; }
225
+ });
226
+ Object.defineProperty(exports, "effect", {
227
+ enumerable: true,
228
+ get: function () { return reactivity.effect; }
229
+ });
230
+ Object.defineProperty(exports, "reactive", {
231
+ enumerable: true,
232
+ get: function () { return reactivity.reactive; }
233
+ });
234
+ Object.defineProperty(exports, "readonlySignal", {
235
+ enumerable: true,
236
+ get: function () { return reactivity.readonlySignal; }
237
+ });
238
+ Object.defineProperty(exports, "ref", {
239
+ enumerable: true,
240
+ get: function () { return reactivity.ref; }
241
+ });
242
+ Object.defineProperty(exports, "set", {
243
+ enumerable: true,
244
+ get: function () { return reactivity.set; }
245
+ });
246
+ Object.defineProperty(exports, "signal", {
247
+ enumerable: true,
248
+ get: function () { return reactivity.signal; }
249
+ });
250
+ Object.defineProperty(exports, "signalBatch", {
251
+ enumerable: true,
252
+ get: function () { return reactivity.signalBatch; }
253
+ });
254
+ Object.defineProperty(exports, "signalUntrack", {
255
+ enumerable: true,
256
+ get: function () { return reactivity.signalUntrack; }
257
+ });
258
+ Object.defineProperty(exports, "update", {
259
+ enumerable: true,
260
+ get: function () { return reactivity.update; }
261
+ });
262
+ Object.defineProperty(exports, "valueOf", {
263
+ enumerable: true,
264
+ get: function () { return reactivity.valueOf; }
265
+ });
266
+ Object.defineProperty(exports, "watch", {
267
+ enumerable: true,
268
+ get: function () { return reactivity.watch; }
269
+ });
270
+ Object.defineProperty(exports, "watchEffect", {
271
+ enumerable: true,
272
+ get: function () { return reactivity.watchEffect; }
273
+ });
274
+ Object.defineProperty(exports, "compile", {
275
+ enumerable: true,
276
+ get: function () { return compiler.compile; }
277
+ });
278
+ Object.defineProperty(exports, "addEventListener", {
279
+ enumerable: true,
280
+ get: function () { return domRuntime.addEventListener; }
281
+ });
282
+ Object.defineProperty(exports, "batchDOM", {
283
+ enumerable: true,
284
+ get: function () { return domRuntime.batchDOM; }
285
+ });
286
+ Object.defineProperty(exports, "bindEffect", {
287
+ enumerable: true,
288
+ get: function () { return domRuntime.bindEffect; }
289
+ });
290
+ Object.defineProperty(exports, "createCleanupScope", {
291
+ enumerable: true,
292
+ get: function () { return domRuntime.createCleanupScope; }
293
+ });
294
+ Object.defineProperty(exports, "createElement", {
295
+ enumerable: true,
296
+ get: function () { return domRuntime.createElement; }
297
+ });
298
+ Object.defineProperty(exports, "createEventHandler", {
299
+ enumerable: true,
300
+ get: function () { return domRuntime.createEventHandler; }
301
+ });
302
+ Object.defineProperty(exports, "createTemplate", {
303
+ enumerable: true,
304
+ get: function () { return domRuntime.createTemplate; }
305
+ });
306
+ Object.defineProperty(exports, "createTextNode", {
307
+ enumerable: true,
308
+ get: function () { return domRuntime.createTextNode; }
309
+ });
310
+ Object.defineProperty(exports, "insert", {
311
+ enumerable: true,
312
+ get: function () { return domRuntime.insert; }
313
+ });
314
+ Object.defineProperty(exports, "onCleanup", {
315
+ enumerable: true,
316
+ get: function () { return domRuntime.onCleanup; }
317
+ });
318
+ Object.defineProperty(exports, "reconcileArray", {
319
+ enumerable: true,
320
+ get: function () { return domRuntime.reconcileArray; }
321
+ });
322
+ Object.defineProperty(exports, "remove", {
323
+ enumerable: true,
324
+ get: function () { return domRuntime.remove; }
325
+ });
326
+ Object.defineProperty(exports, "removeAttribute", {
327
+ enumerable: true,
328
+ get: function () { return domRuntime.removeAttribute; }
329
+ });
330
+ Object.defineProperty(exports, "runCleanups", {
331
+ enumerable: true,
332
+ get: function () { return domRuntime.runCleanups; }
333
+ });
334
+ Object.defineProperty(exports, "setAttribute", {
335
+ enumerable: true,
336
+ get: function () { return domRuntime.setAttribute; }
337
+ });
338
+ Object.defineProperty(exports, "setClass", {
339
+ enumerable: true,
340
+ get: function () { return domRuntime.setClass; }
341
+ });
342
+ Object.defineProperty(exports, "setHTML", {
343
+ enumerable: true,
344
+ get: function () { return domRuntime.setHTML; }
345
+ });
346
+ Object.defineProperty(exports, "setProperty", {
347
+ enumerable: true,
348
+ get: function () { return domRuntime.setProperty; }
349
+ });
350
+ Object.defineProperty(exports, "setStyle", {
351
+ enumerable: true,
352
+ get: function () { return domRuntime.setStyle; }
353
+ });
354
+ Object.defineProperty(exports, "setText", {
355
+ enumerable: true,
356
+ get: function () { return domRuntime.setText; }
357
+ });
358
+ Object.defineProperty(exports, "toggleClass", {
359
+ enumerable: true,
360
+ get: function () { return domRuntime.toggleClass; }
361
+ });
362
+ exports.createApp = createApp;
363
+ exports.defineComponent = defineComponent;
@@ -0,0 +1,60 @@
1
+ import { BaseAppConfig, Directive, DebuggerEvent } from '@lytjs/shared-types';
2
+ export { DebuggerEvent, Directive, DirectiveArguments, DirectiveBinding } from '@lytjs/shared-types';
3
+ import { ComponentPublicInstance, ComponentOptions } from '@lytjs/component';
4
+ export { ComponentOptions, ComponentPublicInstance, onBeforeMount, onBeforeUnmount, onErrorCaptured, onMounted, onUnmounted } from '@lytjs/component';
5
+ export { nextTick } from '@lytjs/common-scheduler';
6
+ export { ComputedSignal, ReadonlySignal, Signal, WritableSignal, computed, computedSignal, effect, reactive, readonlySignal, ref, set, signal, signalBatch, signalUntrack, update, valueOf, watch, watchEffect } from '@lytjs/reactivity';
7
+ export { compile } from '@lytjs/compiler';
8
+ export { CleanupFn, ReconcileOptions, addEventListener, batchDOM, bindEffect, createCleanupScope, createElement, createEventHandler, createTemplate, createTextNode, insert, onCleanup, reconcileArray, remove, removeAttribute, runCleanups, setAttribute, setClass, setHTML, setProperty, setStyle, setText, toggleClass } from '@lytjs/dom-runtime';
9
+
10
+ /** 插件安装函数签名 */
11
+ type PluginInstallFunction<T = unknown> = (app: App, ...options: T[]) => void;
12
+ interface App<HostElement = Element> {
13
+ config: AppConfig;
14
+ use(plugin: Plugin | PluginInstallFunction, ...options: unknown[]): App;
15
+ mount(rootContainer: HostElement | string): Promise<ComponentPublicInstance | null>;
16
+ unmount(): void;
17
+ provide<T = unknown>(key: string | symbol, value: T): App;
18
+ inject<T = unknown>(key: string | symbol, defaultValue?: T): T;
19
+ component(name: string, component: Component): App;
20
+ directive(name: string, directive: Directive): App;
21
+ mixin(mixin: ComponentOptions): App;
22
+ errorHandler?: (err: unknown, instance: ComponentPublicInstance | null, info: string) => void;
23
+ warnHandler?: (msg: string, instance: ComponentPublicInstance | null, trace: string) => void;
24
+ }
25
+ interface AppConfig extends BaseAppConfig {
26
+ performance: boolean;
27
+ globalProperties: Record<string, unknown>;
28
+ isCustomElement?: (tag: string) => boolean;
29
+ compilerOptions?: Record<string, unknown>;
30
+ }
31
+ /** createApp 的配置选项(Signal 模式固定使用 signal 渲染,忽略 rendererMode) */
32
+ interface AppOptions {
33
+ /** Signal 模式下此选项被忽略,始终使用 Signal 渲染 */
34
+ rendererMode?: 'signal' | 'vapor';
35
+ }
36
+ interface Plugin {
37
+ install: PluginInstallFunction;
38
+ }
39
+ /**
40
+ * Signal 模式下的组件类型
41
+ * 必须包含 template 属性,可选包含 data、setup、生命周期钩子
42
+ */
43
+ type Component = ComponentOptions & {
44
+ template?: string;
45
+ };
46
+
47
+ type ErrorCapturedHook = (err: Error, instance: ComponentPublicInstance | null, info: string) => boolean | void;
48
+ type DebuggerHook = (event: DebuggerEvent) => void;
49
+
50
+ declare function createApp(rootComponent: Component, rootProps?: Record<string, unknown> | null, _options?: AppOptions): App;
51
+
52
+ /**
53
+ * 定义组件(re-export from @lytjs/component)
54
+ *
55
+ * Signal 模式下,组件应包含 template 属性。
56
+ * defineComponent 主要用于类型标注和 IDE 提示。
57
+ */
58
+ declare const defineComponent: (options: ComponentOptions) => ComponentOptions;
59
+
60
+ export { type App, type AppConfig, type AppOptions, type Component, type DebuggerHook, type ErrorCapturedHook, type Plugin, createApp, defineComponent };
@@ -0,0 +1,60 @@
1
+ import { BaseAppConfig, Directive, DebuggerEvent } from '@lytjs/shared-types';
2
+ export { DebuggerEvent, Directive, DirectiveArguments, DirectiveBinding } from '@lytjs/shared-types';
3
+ import { ComponentPublicInstance, ComponentOptions } from '@lytjs/component';
4
+ export { ComponentOptions, ComponentPublicInstance, onBeforeMount, onBeforeUnmount, onErrorCaptured, onMounted, onUnmounted } from '@lytjs/component';
5
+ export { nextTick } from '@lytjs/common-scheduler';
6
+ export { ComputedSignal, ReadonlySignal, Signal, WritableSignal, computed, computedSignal, effect, reactive, readonlySignal, ref, set, signal, signalBatch, signalUntrack, update, valueOf, watch, watchEffect } from '@lytjs/reactivity';
7
+ export { compile } from '@lytjs/compiler';
8
+ export { CleanupFn, ReconcileOptions, addEventListener, batchDOM, bindEffect, createCleanupScope, createElement, createEventHandler, createTemplate, createTextNode, insert, onCleanup, reconcileArray, remove, removeAttribute, runCleanups, setAttribute, setClass, setHTML, setProperty, setStyle, setText, toggleClass } from '@lytjs/dom-runtime';
9
+
10
+ /** 插件安装函数签名 */
11
+ type PluginInstallFunction<T = unknown> = (app: App, ...options: T[]) => void;
12
+ interface App<HostElement = Element> {
13
+ config: AppConfig;
14
+ use(plugin: Plugin | PluginInstallFunction, ...options: unknown[]): App;
15
+ mount(rootContainer: HostElement | string): Promise<ComponentPublicInstance | null>;
16
+ unmount(): void;
17
+ provide<T = unknown>(key: string | symbol, value: T): App;
18
+ inject<T = unknown>(key: string | symbol, defaultValue?: T): T;
19
+ component(name: string, component: Component): App;
20
+ directive(name: string, directive: Directive): App;
21
+ mixin(mixin: ComponentOptions): App;
22
+ errorHandler?: (err: unknown, instance: ComponentPublicInstance | null, info: string) => void;
23
+ warnHandler?: (msg: string, instance: ComponentPublicInstance | null, trace: string) => void;
24
+ }
25
+ interface AppConfig extends BaseAppConfig {
26
+ performance: boolean;
27
+ globalProperties: Record<string, unknown>;
28
+ isCustomElement?: (tag: string) => boolean;
29
+ compilerOptions?: Record<string, unknown>;
30
+ }
31
+ /** createApp 的配置选项(Signal 模式固定使用 signal 渲染,忽略 rendererMode) */
32
+ interface AppOptions {
33
+ /** Signal 模式下此选项被忽略,始终使用 Signal 渲染 */
34
+ rendererMode?: 'signal' | 'vapor';
35
+ }
36
+ interface Plugin {
37
+ install: PluginInstallFunction;
38
+ }
39
+ /**
40
+ * Signal 模式下的组件类型
41
+ * 必须包含 template 属性,可选包含 data、setup、生命周期钩子
42
+ */
43
+ type Component = ComponentOptions & {
44
+ template?: string;
45
+ };
46
+
47
+ type ErrorCapturedHook = (err: Error, instance: ComponentPublicInstance | null, info: string) => boolean | void;
48
+ type DebuggerHook = (event: DebuggerEvent) => void;
49
+
50
+ declare function createApp(rootComponent: Component, rootProps?: Record<string, unknown> | null, _options?: AppOptions): App;
51
+
52
+ /**
53
+ * 定义组件(re-export from @lytjs/component)
54
+ *
55
+ * Signal 模式下,组件应包含 template 属性。
56
+ * defineComponent 主要用于类型标注和 IDE 提示。
57
+ */
58
+ declare const defineComponent: (options: ComponentOptions) => ComponentOptions;
59
+
60
+ export { type App, type AppConfig, type AppOptions, type Component, type DebuggerHook, type ErrorCapturedHook, type Plugin, createApp, defineComponent };
package/dist/index.mjs ADDED
@@ -0,0 +1,193 @@
1
+ import { createSignalRenderer } from '@lytjs/renderer';
2
+ import { error } from '@lytjs/common-error';
3
+ import { defineComponent as defineComponent$1 } from '@lytjs/component';
4
+ export { onBeforeMount, onBeforeUnmount, onErrorCaptured, onMounted, onUnmounted } from '@lytjs/component';
5
+ export { nextTick } from '@lytjs/common-scheduler';
6
+ export { computed, computedSignal, effect, reactive, readonlySignal, ref, set, signal, signalBatch, signalUntrack, update, valueOf, watch, watchEffect } from '@lytjs/reactivity';
7
+ export { compile } from '@lytjs/compiler';
8
+ export { addEventListener, batchDOM, bindEffect, createCleanupScope, createElement, createEventHandler, createTemplate, createTextNode, insert, onCleanup, reconcileArray, remove, removeAttribute, runCleanups, setAttribute, setClass, setHTML, setProperty, setStyle, setText, toggleClass } from '@lytjs/dom-runtime';
9
+
10
+ // src/create-app.ts
11
+ function createApp(rootComponent, rootProps = null, _options) {
12
+ const installedPlugins = /* @__PURE__ */ new Set();
13
+ let _isUnmounted = false;
14
+ let _isMounted = false;
15
+ let signalRenderer = null;
16
+ let _container = null;
17
+ const context = {
18
+ provides: /* @__PURE__ */ Object.create(null),
19
+ config: {
20
+ errorHandler: void 0,
21
+ warnHandler: void 0,
22
+ performance: false,
23
+ globalProperties: {},
24
+ isCustomElement: void 0,
25
+ compilerOptions: void 0
26
+ }
27
+ };
28
+ const app = {
29
+ get config() {
30
+ return context.config;
31
+ },
32
+ use(plugin, ...options) {
33
+ if (installedPlugins.has(plugin)) return app;
34
+ try {
35
+ if (typeof plugin === "function") {
36
+ plugin(app, ...options);
37
+ } else {
38
+ plugin.install(app, ...options);
39
+ }
40
+ installedPlugins.add(plugin);
41
+ } catch (err) {
42
+ error(
43
+ `Plugin failed to install: ${typeof plugin === "function" ? plugin.name || "anonymous function" : plugin.install?.name || "plugin"}: ${err}`
44
+ );
45
+ throw err;
46
+ }
47
+ return app;
48
+ },
49
+ async mount(rootContainer) {
50
+ if (_isUnmounted) {
51
+ throw new Error(
52
+ `[LytJS] App has been unmounted and cannot be remounted. Create a new app instance instead.`
53
+ );
54
+ }
55
+ if (!rootComponent) {
56
+ return null;
57
+ }
58
+ if (_container) {
59
+ throw new Error(
60
+ `[LytJS] App is already mounted. Call app.unmount() first before mounting again.`
61
+ );
62
+ }
63
+ const container = typeof rootContainer === "string" ? document.querySelector(rootContainer) : rootContainer;
64
+ if (!container) {
65
+ throw new Error(
66
+ `[LytJS] Failed to mount app: cannot find element matching selector "${rootContainer}". Make sure the target element exists in the DOM before calling app.mount().`
67
+ );
68
+ }
69
+ _container = container;
70
+ try {
71
+ const componentOptions = rootComponent;
72
+ const template = componentOptions.template;
73
+ if (!template) {
74
+ throw new Error(
75
+ `[LytJS] Signal mode requires a template string in the root component. Provide a 'template' property in your component options.`
76
+ );
77
+ }
78
+ const ctx = { ...rootProps };
79
+ if (typeof componentOptions.data === "function") {
80
+ try {
81
+ const dataResult = componentOptions.data();
82
+ Object.assign(ctx, dataResult);
83
+ } catch (e) {
84
+ error(
85
+ `[LytJS] Failed to execute data() in Signal mode: ${e instanceof Error ? e.message : String(e)}`
86
+ );
87
+ throw e;
88
+ }
89
+ }
90
+ if (typeof componentOptions.setup === "function") {
91
+ try {
92
+ const setupResult = componentOptions.setup(rootProps ?? {}, {});
93
+ if (setupResult && typeof setupResult === "object") {
94
+ Object.assign(ctx, setupResult);
95
+ }
96
+ } catch (e) {
97
+ error(
98
+ `[LytJS] Failed to execute setup() in Signal mode: ${e instanceof Error ? e.message : String(e)}`
99
+ );
100
+ throw e;
101
+ }
102
+ }
103
+ const beforeMount = componentOptions.beforeMount;
104
+ if (typeof beforeMount === "function") {
105
+ beforeMount.call(ctx);
106
+ }
107
+ signalRenderer = await createSignalRenderer(template, ctx);
108
+ signalRenderer.render(container);
109
+ _isMounted = true;
110
+ const mounted = componentOptions.mounted;
111
+ if (typeof mounted === "function") {
112
+ mounted.call(ctx);
113
+ }
114
+ return new Proxy({}, {
115
+ get(_, key) {
116
+ if (key === "$el") return container;
117
+ if (key === "$options") return componentOptions;
118
+ return ctx[key];
119
+ },
120
+ set(_, key, value) {
121
+ ctx[key] = value;
122
+ return true;
123
+ }
124
+ });
125
+ } catch (err) {
126
+ if (context.config.errorHandler) {
127
+ context.config.errorHandler(err, null, "mount");
128
+ } else {
129
+ error(`[LytJS] Failed to mount app: ${err instanceof Error ? err.message : String(err)}`);
130
+ }
131
+ throw err;
132
+ }
133
+ },
134
+ unmount() {
135
+ const componentOptions = rootComponent;
136
+ const beforeUnmount = componentOptions.beforeUnmount;
137
+ if (typeof beforeUnmount === "function") {
138
+ beforeUnmount.call(componentOptions);
139
+ }
140
+ if (signalRenderer) {
141
+ signalRenderer.unmount();
142
+ signalRenderer = null;
143
+ }
144
+ const unmounted = componentOptions.unmounted;
145
+ if (typeof unmounted === "function") {
146
+ unmounted.call(componentOptions);
147
+ }
148
+ _isUnmounted = true;
149
+ _isMounted = false;
150
+ _container = null;
151
+ for (const plugin of installedPlugins) {
152
+ if (typeof plugin !== "function" && plugin != null && typeof plugin.cleanup === "function") {
153
+ try {
154
+ const cleanup = plugin.cleanup;
155
+ if (typeof cleanup === "function") cleanup();
156
+ } catch (err) {
157
+ error(
158
+ `Plugin cleanup failed: ${typeof plugin === "object" && plugin !== null && "name" in plugin ? plugin.name : "unknown"}: ${err}`
159
+ );
160
+ }
161
+ }
162
+ }
163
+ installedPlugins.clear();
164
+ },
165
+ provide(key, value) {
166
+ context.provides[key] = value;
167
+ return app;
168
+ },
169
+ inject(key, defaultValue) {
170
+ const provides = context.provides;
171
+ if (key in provides) {
172
+ return provides[key];
173
+ }
174
+ if (defaultValue !== void 0) {
175
+ return defaultValue;
176
+ }
177
+ return void 0;
178
+ },
179
+ component(_name, _component) {
180
+ return app;
181
+ },
182
+ directive(_name, _directive) {
183
+ return app;
184
+ },
185
+ mixin(_mixin) {
186
+ return app;
187
+ }
188
+ };
189
+ return app;
190
+ }
191
+ var defineComponent = defineComponent$1;
192
+
193
+ export { createApp, defineComponent };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@lytjs/core-signal",
3
+ "version": "6.0.0",
4
+ "description": "Lyt.js Core - Signal rendering mode only",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch"
23
+ },
24
+ "dependencies": {
25
+ "@lytjs/component": "^6.0.0",
26
+ "@lytjs/reactivity": "^6.0.0",
27
+ "@lytjs/compiler": "^6.0.0",
28
+ "@lytjs/renderer": "^6.0.0",
29
+ "@lytjs/dom-runtime": "^6.0.0",
30
+ "@lytjs/common-scheduler": "^6.0.0",
31
+ "@lytjs/common-error": "^6.0.0",
32
+ "@lytjs/shared-types": "^6.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.4.0"
37
+ },
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://gitee.com/lytjs/lytjs.git",
42
+ "directory": "packages/core-signal"
43
+ },
44
+ "keywords": [
45
+ "lytjs",
46
+ "core",
47
+ "signal",
48
+ "createApp",
49
+ "fine-grained",
50
+ "reactive"
51
+ ]
52
+ }