sygnal 5.2.0 → 5.2.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/README.md +1 -1
- package/dist/astro/client.cjs.js +29 -2
- package/dist/astro/client.mjs +29 -2
- package/dist/astro/server.cjs.js +18 -0
- package/dist/astro/server.mjs +18 -0
- package/dist/index.cjs.js +107 -2
- package/dist/index.d.ts +34 -0
- package/dist/index.esm.js +105 -3
- package/dist/sygnal.min.js +1 -1
- package/dist/vike/+config.cjs.js +5 -1
- package/dist/vike/+config.js +5 -1
- package/dist/vike/ClientOnly.cjs.js +34 -0
- package/dist/vike/ClientOnly.mjs +32 -0
- package/dist/vike/onRenderClient.cjs.js +292 -35
- package/dist/vike/onRenderClient.mjs +292 -35
- package/dist/vike/onRenderHtml.cjs.js +71 -34
- package/dist/vike/onRenderHtml.mjs +71 -34
- package/dist/vite/plugin.cjs.js +6 -4
- package/dist/vite/plugin.mjs +6 -4
- package/package.json +5 -1
- package/src/component.ts +29 -2
- package/src/extra/reducers.ts +64 -0
- package/src/extra/ssr.ts +19 -0
- package/src/index.d.ts +34 -0
- package/src/index.ts +1 -0
- package/src/vike/+config.ts +5 -1
- package/src/vike/ClientOnly.ts +10 -0
- package/src/vike/onRenderClient.ts +319 -36
- package/src/vike/onRenderHtml.ts +77 -33
- package/src/vike/types.ts +2 -0
- package/src/vite/plugin.ts +6 -4
package/dist/vite/plugin.cjs.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* export default defineConfig({ plugins: [sygnal()] })
|
|
11
11
|
*
|
|
12
12
|
* What it does:
|
|
13
|
-
* 1. Configures
|
|
13
|
+
* 1. Configures OXC for automatic JSX transform with sygnal as the import source
|
|
14
14
|
* 2. Detects files that call `run()` from sygnal and auto-injects HMR wiring
|
|
15
15
|
*
|
|
16
16
|
* The HMR transform finds the pattern:
|
|
@@ -34,9 +34,11 @@ function sygnal(options = {}) {
|
|
|
34
34
|
if (disableJsx)
|
|
35
35
|
return;
|
|
36
36
|
return {
|
|
37
|
-
|
|
38
|
-
jsx:
|
|
39
|
-
|
|
37
|
+
oxc: {
|
|
38
|
+
jsx: {
|
|
39
|
+
runtime: 'automatic',
|
|
40
|
+
importSource: 'sygnal',
|
|
41
|
+
},
|
|
40
42
|
},
|
|
41
43
|
};
|
|
42
44
|
},
|
package/dist/vite/plugin.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* export default defineConfig({ plugins: [sygnal()] })
|
|
9
9
|
*
|
|
10
10
|
* What it does:
|
|
11
|
-
* 1. Configures
|
|
11
|
+
* 1. Configures OXC for automatic JSX transform with sygnal as the import source
|
|
12
12
|
* 2. Detects files that call `run()` from sygnal and auto-injects HMR wiring
|
|
13
13
|
*
|
|
14
14
|
* The HMR transform finds the pattern:
|
|
@@ -32,9 +32,11 @@ function sygnal(options = {}) {
|
|
|
32
32
|
if (disableJsx)
|
|
33
33
|
return;
|
|
34
34
|
return {
|
|
35
|
-
|
|
36
|
-
jsx:
|
|
37
|
-
|
|
35
|
+
oxc: {
|
|
36
|
+
jsx: {
|
|
37
|
+
runtime: 'automatic',
|
|
38
|
+
importSource: 'sygnal',
|
|
39
|
+
},
|
|
38
40
|
},
|
|
39
41
|
};
|
|
40
42
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sygnal",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "An intuitive framework for building fast and small components or applications based on Cycle.js",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
"import": "./dist/vike/onRenderClient.mjs",
|
|
70
70
|
"require": "./dist/vike/onRenderClient.cjs.js"
|
|
71
71
|
},
|
|
72
|
+
"./vike/ClientOnly": {
|
|
73
|
+
"import": "./dist/vike/ClientOnly.mjs",
|
|
74
|
+
"require": "./dist/vike/ClientOnly.cjs.js"
|
|
75
|
+
},
|
|
72
76
|
"./types": {
|
|
73
77
|
"import": "./dist/index.d.ts",
|
|
74
78
|
"require": "./dist/index.d.ts"
|
package/src/component.ts
CHANGED
|
@@ -91,6 +91,7 @@ export interface ComponentOptions {
|
|
|
91
91
|
stateSourceName?: string;
|
|
92
92
|
requestSourceName?: string;
|
|
93
93
|
isolateOpts?: string | boolean | Record<string, any>;
|
|
94
|
+
isolatedState?: boolean;
|
|
94
95
|
onError?: (error: Error, info: { componentName: string }) => any;
|
|
95
96
|
debug?: boolean;
|
|
96
97
|
}
|
|
@@ -169,6 +170,7 @@ class Component {
|
|
|
169
170
|
sourceNames: string[];
|
|
170
171
|
_debug: boolean;
|
|
171
172
|
onError: ((error: Error, info: { componentName: string }) => any) | undefined;
|
|
173
|
+
isolatedState: boolean;
|
|
172
174
|
isSubComponent: boolean;
|
|
173
175
|
currentState: any;
|
|
174
176
|
currentProps: any;
|
|
@@ -202,7 +204,7 @@ class Component {
|
|
|
202
204
|
_readyChanged$: any;
|
|
203
205
|
_readyChangedListener: any;
|
|
204
206
|
|
|
205
|
-
constructor({name = 'NO NAME', sources, intent, model, hmrActions, context, response, view, peers = {}, components = {}, initialState, calculated, storeCalculatedInState = true, DOMSourceName = 'DOM', stateSourceName = 'STATE', requestSourceName = 'HTTP', onError, debug = false}: ComponentOptions) {
|
|
207
|
+
constructor({name = 'NO NAME', sources, intent, model, hmrActions, context, response, view, peers = {}, components = {}, initialState, calculated, storeCalculatedInState = true, DOMSourceName = 'DOM', stateSourceName = 'STATE', requestSourceName = 'HTTP', isolatedState = false, onError, debug = false}: ComponentOptions) {
|
|
206
208
|
if (!sources || !isObj(sources)) throw new Error(`[${name}] Missing or invalid sources`)
|
|
207
209
|
|
|
208
210
|
this._componentNumber = COMPONENT_COUNT++
|
|
@@ -225,6 +227,7 @@ class Component {
|
|
|
225
227
|
this.requestSourceName = requestSourceName
|
|
226
228
|
this.sourceNames = Object.keys(sources)
|
|
227
229
|
this.onError = onError
|
|
230
|
+
this.isolatedState = isolatedState
|
|
228
231
|
this._debug = debug
|
|
229
232
|
|
|
230
233
|
// Warn if calculated fields shadow base state keys
|
|
@@ -649,7 +652,7 @@ class Component {
|
|
|
649
652
|
const hmrState = ENVIRONMENT?.__SYGNAL_HMR_STATE
|
|
650
653
|
const effectiveInitialState = (typeof hmrState !== 'undefined') ? hmrState : this.initialState
|
|
651
654
|
const initial = { type: INITIALIZE_ACTION, data: effectiveInitialState }
|
|
652
|
-
if (this.isSubComponent && this.initialState) {
|
|
655
|
+
if (this.isSubComponent && this.initialState && !this.isolatedState) {
|
|
653
656
|
console.warn(`[${this.name}] Initial state provided to sub-component. This will overwrite any state provided by the parent component.`)
|
|
654
657
|
}
|
|
655
658
|
const hasInitialState = (typeof effectiveInitialState !== 'undefined')
|
|
@@ -851,6 +854,7 @@ class Component {
|
|
|
851
854
|
.map((vdom: any) => processLazy(vdom, this))
|
|
852
855
|
.map(processPortals)
|
|
853
856
|
.map(processTransitions)
|
|
857
|
+
.map(processClientOnly)
|
|
854
858
|
.compose(this.instantiateSubComponents.bind(this))
|
|
855
859
|
.filter((val: any) => val !== undefined)
|
|
856
860
|
.compose(this.renderVdom.bind(this))
|
|
@@ -1876,6 +1880,29 @@ function processTransitions(vnode: any): any {
|
|
|
1876
1880
|
return vnode
|
|
1877
1881
|
}
|
|
1878
1882
|
|
|
1883
|
+
function processClientOnly(vnode: any): any {
|
|
1884
|
+
if (!vnode || !vnode.sel) return vnode
|
|
1885
|
+
if (vnode.sel === 'clientonly') {
|
|
1886
|
+
// On the client, unwrap to children (render them normally)
|
|
1887
|
+
const children = vnode.children || []
|
|
1888
|
+
if (children.length === 0) return { sel: 'div', data: {}, children: [] }
|
|
1889
|
+
if (children.length === 1) return processClientOnly(children[0])
|
|
1890
|
+
// Multiple children: wrap in a div
|
|
1891
|
+
return {
|
|
1892
|
+
sel: 'div',
|
|
1893
|
+
data: {},
|
|
1894
|
+
children: children.map(processClientOnly),
|
|
1895
|
+
text: undefined,
|
|
1896
|
+
elm: undefined,
|
|
1897
|
+
key: undefined,
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (vnode.children && vnode.children.length > 0) {
|
|
1901
|
+
vnode.children = vnode.children.map(processClientOnly)
|
|
1902
|
+
}
|
|
1903
|
+
return vnode
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1879
1906
|
function applyTransitionHooks(vnode: any, name: string, duration?: number): any {
|
|
1880
1907
|
const existingInsert = vnode.data?.hook?.insert
|
|
1881
1908
|
const existingRemove = vnode.data?.hook?.remove
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reducer helpers for common state update patterns.
|
|
3
|
+
*
|
|
4
|
+
* These reduce boilerplate in model definitions by providing
|
|
5
|
+
* shorthand factories for the most frequent reducer shapes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── set() ──────────────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Create a reducer that merges a partial update into state.
|
|
11
|
+
*
|
|
12
|
+
* Static form — merge a fixed object:
|
|
13
|
+
* set({ isEditing: true })
|
|
14
|
+
*
|
|
15
|
+
* Dynamic form — function receives (state, data, next, props) and
|
|
16
|
+
* returns the partial update to merge:
|
|
17
|
+
* set((state, title) => ({ title }))
|
|
18
|
+
*/
|
|
19
|
+
export function set<S = any>(
|
|
20
|
+
partial: Partial<S> | ((state: S, data: any, next: Function, props: any) => Partial<S>)
|
|
21
|
+
): (state: S, data: any, next: Function, props: any) => S {
|
|
22
|
+
if (typeof partial === 'function') {
|
|
23
|
+
return (state, data, next, props) => ({
|
|
24
|
+
...state,
|
|
25
|
+
...partial(state, data, next, props),
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
return (state) => ({ ...state, ...partial })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── toggle() ───────────────────────────────────────────────────────
|
|
32
|
+
/**
|
|
33
|
+
* Create a reducer that toggles a boolean field on state.
|
|
34
|
+
*
|
|
35
|
+
* toggle('showModal')
|
|
36
|
+
* // equivalent to: (state) => ({ ...state, showModal: !state.showModal })
|
|
37
|
+
*/
|
|
38
|
+
export function toggle<S = any>(field: keyof S & string): (state: S) => S {
|
|
39
|
+
return (state) => ({ ...state, [field]: !state[field] })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── emit() ─────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Create a model entry that emits an EVENTS bus event.
|
|
45
|
+
*
|
|
46
|
+
* With static data:
|
|
47
|
+
* emit('DELETE_LANE', { laneId: 42 })
|
|
48
|
+
*
|
|
49
|
+
* With dynamic data derived from state:
|
|
50
|
+
* emit('DELETE_LANE', (state) => ({ laneId: state.id }))
|
|
51
|
+
*
|
|
52
|
+
* Fire-and-forget (no data):
|
|
53
|
+
* emit('REFRESH')
|
|
54
|
+
*/
|
|
55
|
+
export function emit(
|
|
56
|
+
type: string,
|
|
57
|
+
data?: any | ((state: any, actionData: any, next: Function, props: any) => any)
|
|
58
|
+
): { EVENTS: (state: any, actionData: any, next: Function, props: any) => { type: string; data: any } } {
|
|
59
|
+
return {
|
|
60
|
+
EVENTS: typeof data === 'function'
|
|
61
|
+
? (state, actionData, next, props) => ({ type, data: data(state, actionData, next, props) })
|
|
62
|
+
: () => ({ type, data }),
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/extra/ssr.ts
CHANGED
|
@@ -193,6 +193,25 @@ function processSSRTree(vnode: any, context: Record<string, any>, parentState?:
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// ClientOnly: render fallback during SSR, skip children (they need a browser)
|
|
197
|
+
if (sel === 'clientonly') {
|
|
198
|
+
const props = vnode.data?.props || {}
|
|
199
|
+
const fallback = props.fallback
|
|
200
|
+
if (fallback) {
|
|
201
|
+
// fallback can be a VNode or a string
|
|
202
|
+
return processSSRTree(fallback, context, parentState)
|
|
203
|
+
}
|
|
204
|
+
// No fallback — render an empty placeholder div
|
|
205
|
+
return {
|
|
206
|
+
sel: 'div',
|
|
207
|
+
data: {attrs: {'data-sygnal-clientonly': ''}},
|
|
208
|
+
children: [],
|
|
209
|
+
text: undefined,
|
|
210
|
+
elm: undefined,
|
|
211
|
+
key: undefined,
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
196
215
|
// Slot: unwrap to children
|
|
197
216
|
if (sel === 'slot') {
|
|
198
217
|
const children = vnode.children || []
|
package/src/index.d.ts
CHANGED
|
@@ -378,6 +378,40 @@ export function enableHMR<STATE = any, DRIVERS = {}>(
|
|
|
378
378
|
export function classes(...classes: ClassesType): string
|
|
379
379
|
export function exactState<STATE>(): <ACTUAL extends STATE>(state: ExactShape<STATE, ACTUAL>) => STATE
|
|
380
380
|
|
|
381
|
+
// ── Reducer helpers ────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Create a reducer that merges a partial update into state.
|
|
385
|
+
*
|
|
386
|
+
* Static form — merge a fixed object:
|
|
387
|
+
* `set({ isEditing: true })`
|
|
388
|
+
*
|
|
389
|
+
* Dynamic form — function receives (state, data, next, props) and
|
|
390
|
+
* returns the partial update to merge:
|
|
391
|
+
* `set((state, title) => ({ title }))`
|
|
392
|
+
*/
|
|
393
|
+
export function set<S = any>(
|
|
394
|
+
partial: Partial<S> | ((state: S, data: any, next: Function, props: any) => Partial<S>)
|
|
395
|
+
): (state: S, data: any, next: Function, props: any) => S
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Create a reducer that toggles a boolean field on state.
|
|
399
|
+
*
|
|
400
|
+
* `toggle('showModal')`
|
|
401
|
+
*/
|
|
402
|
+
export function toggle<S = any>(field: keyof S & string): (state: S) => S
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Create a model entry that emits an EVENTS bus event.
|
|
406
|
+
*
|
|
407
|
+
* `emit('DELETE_LANE', (state) => ({ laneId: state.id }))`
|
|
408
|
+
* `emit('REFRESH')`
|
|
409
|
+
*/
|
|
410
|
+
export function emit(
|
|
411
|
+
type: string,
|
|
412
|
+
data?: any | ((state: any, actionData: any, next: Function, props: any) => any)
|
|
413
|
+
): { EVENTS: (state: any, actionData: any, next: Function, props: any) => { type: string; data: any } }
|
|
414
|
+
|
|
381
415
|
/**
|
|
382
416
|
* Any object with an events() method (e.g., DOM.select('form')).
|
|
383
417
|
* Uses permissive signature to be compatible with MainDOMSource's overloaded events().
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { createElement } from './pragma/index'
|
|
|
21
21
|
export { createCommand } from './extra/command'
|
|
22
22
|
export { createRef, createRef$ } from './extra/ref'
|
|
23
23
|
export { renderComponent } from './extra/testing'
|
|
24
|
+
export { set, toggle, emit } from './extra/reducers'
|
|
24
25
|
export { renderToString } from './extra/ssr'
|
|
25
26
|
export { default as xs } from './extra/xstreamCompat'
|
|
26
27
|
export { getDevTools } from './extra/devtools'
|
package/src/vike/+config.ts
CHANGED
|
@@ -15,13 +15,17 @@ export default {
|
|
|
15
15
|
onRenderHtml: 'import:sygnal/vike/onRenderHtml:onRenderHtml',
|
|
16
16
|
onRenderClient: 'import:sygnal/vike/onRenderClient:onRenderClient',
|
|
17
17
|
|
|
18
|
-
passToClient: ['data', 'routeParams'],
|
|
18
|
+
passToClient: ['data', 'routeParams', 'urlPathname'],
|
|
19
19
|
|
|
20
20
|
meta: {
|
|
21
21
|
Layout: {
|
|
22
22
|
env: { server: true, client: true },
|
|
23
23
|
cumulative: true,
|
|
24
24
|
},
|
|
25
|
+
Wrapper: {
|
|
26
|
+
env: { server: true, client: true },
|
|
27
|
+
cumulative: true,
|
|
28
|
+
},
|
|
25
29
|
Head: {
|
|
26
30
|
env: { server: true },
|
|
27
31
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {h} from '../cycle/dom/snabbdom';
|
|
2
|
+
|
|
3
|
+
const ClientOnly = (props: any) => {
|
|
4
|
+
const {children, ...sanitizedProps} = props;
|
|
5
|
+
return h('clientonly', {props: sanitizedProps}, children);
|
|
6
|
+
};
|
|
7
|
+
(ClientOnly as any).label = 'clientonly';
|
|
8
|
+
(ClientOnly as any).preventInstantiation = true;
|
|
9
|
+
|
|
10
|
+
export {ClientOnly};
|