@sigx/lynx 0.4.2 → 0.4.4
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 +102 -60
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,50 +1,108 @@
|
|
|
1
1
|
# @sigx/lynx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**[SignalX](https://github.com/signalxjs/core) for [Lynx](https://lynxjs.org/)** lets you build native iOS and Android apps with SignalX's signal/effect reactivity model on top of ByteDance's Lynx runtime — with cross-thread gestures and animations that run on the device's main UI thread.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**`@sigx/lynx`** is the package you import from in app code. It bundles [`@sigx/reactivity`](https://github.com/signalxjs/core/tree/main/packages/reactivity) (state), [`@sigx/runtime-core`](https://github.com/signalxjs/core/tree/main/packages/runtime-core) (components, lifecycle), and [`@sigx/lynx-runtime`](https://github.com/signalxjs/lynx/tree/main/packages/lynx-runtime) (the dual-thread renderer) behind one import path, so app code says `import { signal, component, useSharedValue } from '@sigx/lynx'` and nothing else.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
- **Native, not WebView.** Real `UIView` / `View` trees. Video maps to `AVPlayer` / `ExoPlayer`, maps to `MKMapView` / Google Maps, gestures hit the actual touch system — no DOM wrapper, no JS bridge in the hot path.
|
|
10
|
+
- **Zero-config native modules.** `pnpm add @sigx/lynx-camera` → `sigx prebuild` → done. The autolinker wires Podfile, Gradle, `Info.plist`, `AndroidManifest.xml`, and the native module registry from each package's `signalx-module.json`. You never edit a `Podfile` to add a dependency.
|
|
11
|
+
- **Main-thread gestures & animations.** Press, drag, swipe, scroll offsets, and spring + tween animations all run on Lepus (the platform's main thread). Your finger tracks at the display's refresh rate even when JS is busy.
|
|
12
|
+
- **`SharedValue` — cross-thread state for free.** Mutate from a `'main thread'` worklet; read reactively from a SignalX `effect` on the background thread. Powers gestures, scroll, animation, and any custom "fast state lives on MT" use case. Not available in react-lynx or vue-lynx as of 2026-04.
|
|
13
|
+
- **Type-first navigation.** `defineRoutes` plus module augmentation gives every navigator API (`useNav`, `useParams`, `useSearch`, `<Link>`) precise per-route inference. Native Stack / Tabs / Drawer / modals.
|
|
14
|
+
- **A real native-module catalog.** Camera, audio, video, maps, webview, biometric, secure storage, file system, location, push + local notifications, share sheet, clipboard, haptics, image picker, websocket, connectivity, device info, background tasks, appearance, safe area — all auto-linked.
|
|
15
|
+
- **Dev experience that doesn't fight you.** `sigx dev` runs rspeedy with HMR and streams device `console.*` straight to your terminal. `sigx run:ios` / `sigx run:android` go from scaffold to a running app in one command. `sigx doctor` verifies your toolchain. On-device dev menu, error overlay, perf HUD, and QR scanner are debug-only and dropped from release builds.
|
|
16
|
+
- **Build pipeline that disappears.** The plugin runs the SWC `'main thread'` worklet transform automatically — including across third-party packages that ship directives in their `dist/`, with no allowlist. Tailwind preset + DaisyUI components + build-time icon tree-shaking (only glyphs you actually render ship in the bundle).
|
|
17
|
+
- **Testable.** [`@sigx/lynx-testing`](https://github.com/signalxjs/lynx/tree/main/packages/lynx-testing) renders into an in-memory tree so component tests run under vitest like any other library — no Lynx runtime needed.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
Scaffold a new app:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm create @sigx@latest my-app
|
|
25
|
+
# pick: lynx (or lynx-tailwind)
|
|
26
|
+
cd my-app
|
|
27
|
+
pnpm install
|
|
28
|
+
pnpm dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then in another terminal:
|
|
6
32
|
|
|
7
33
|
```bash
|
|
8
|
-
|
|
34
|
+
pnpm run:ios # or run:android
|
|
9
35
|
```
|
|
10
36
|
|
|
37
|
+
That's it. The template wires the build plugin, the CLI, and a starter `App.tsx`.
|
|
38
|
+
|
|
39
|
+
### Minimal app
|
|
40
|
+
|
|
11
41
|
```tsx
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
// src/App.tsx
|
|
43
|
+
import { component, signal } from '@sigx/lynx';
|
|
44
|
+
|
|
45
|
+
const App = component(() => {
|
|
46
|
+
const count = signal(0);
|
|
47
|
+
return () => (
|
|
48
|
+
<view>
|
|
49
|
+
<text>count = {count.value}</text>
|
|
50
|
+
<view bindtap={() => { count.value++; }}>
|
|
51
|
+
<text>tap me</text>
|
|
52
|
+
</view>
|
|
53
|
+
</view>
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export default App;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// src/main.tsx
|
|
62
|
+
import { defineApp } from '@sigx/lynx';
|
|
63
|
+
import App from './App';
|
|
64
|
+
|
|
65
|
+
defineApp(<App />).mount(null);
|
|
21
66
|
```
|
|
22
67
|
|
|
23
|
-
|
|
68
|
+
### Build plugin
|
|
69
|
+
|
|
70
|
+
If you're integrating into an existing Lynx project rather than scaffolding, register the plugin in your rspeedy / rspack config:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// lynx.config.ts
|
|
74
|
+
import { defineConfig } from '@lynx-js/rspeedy';
|
|
75
|
+
import { pluginSigxLynx } from '@sigx/lynx-plugin';
|
|
76
|
+
|
|
77
|
+
export default defineConfig({
|
|
78
|
+
source: { entry: { main: './src/main.tsx' } },
|
|
79
|
+
plugins: [pluginSigxLynx()],
|
|
80
|
+
});
|
|
81
|
+
```
|
|
24
82
|
|
|
25
|
-
|
|
26
|
-
| ---------------------- | -------------------------- | ------------------------------------------------------------- |
|
|
27
|
-
| `signal`, `effect`, `computed`, `batch`, `untrack`, `watch`, `effectScope` | `@sigx/reactivity` | Reactive state and computations on the BG thread. |
|
|
28
|
-
| `component`, `defineApp`, `defineDirective`, `onMounted`, `onUnmounted`, `onUpdated`, `onCreated`, `provide`/`inject` | `@sigx/runtime-core` | Component model, lifecycle, dependency injection. |
|
|
29
|
-
| `useMainThreadRef`, `MainThreadRef` | `@sigx/lynx-runtime` | Refs whose `.current` value lives on the main UI thread. |
|
|
30
|
-
| `runOnMainThread`, `runOnBackground`, `transformToWorklet` | `@sigx/lynx-runtime` | Cross-thread function calls. |
|
|
31
|
-
| `useSharedValue`, `SharedValue`, `SharedValueState` | `@sigx/lynx-runtime` | **The cross-thread primitive** — MT-writable, BG-observable values (see below). |
|
|
32
|
-
| `useAnimatedStyle` | `@sigx/lynx-runtime` | Bind an element style to a `SharedValue` via a named mapper (linear or range-mapped), applied on MT every flush. |
|
|
33
|
-
| `OP`, `pushOp`, `scheduleFlush`, `takeOps`, `flushNow` | `@sigx/lynx-runtime` | Lower-level op-queue access for runtime authors. |
|
|
34
|
-
| `registerBgSink`, `unregisterBgSink`, `ingestAvPublishes` | `@sigx/lynx-runtime` | Lower-level SharedValue bridge primitives (the building blocks under `useSharedValue`). |
|
|
35
|
-
| `MainThread`, `Define`, `ViewAttributes`, etc. | `@sigx/lynx-runtime` | JSX type annotations. |
|
|
83
|
+
The plugin handles the BG / MT bundle split and the `'main thread'` worklet transform.
|
|
36
84
|
|
|
37
|
-
|
|
85
|
+
## What you import
|
|
86
|
+
|
|
87
|
+
| Surface | Use for |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `signal`, `effect`, `computed`, `batch`, `untrack`, `watch`, `effectScope` | Reactive state and computations (BG thread). |
|
|
90
|
+
| `component`, `defineApp`, `defineDirective`, `onMounted`, `onUnmounted`, `onUpdated`, `onCreated`, `provide` / `inject` | Component model, lifecycle, dependency injection. |
|
|
91
|
+
| `useMainThreadRef`, `MainThreadRef` | Refs whose `.current` value lives on the main UI thread. |
|
|
92
|
+
| `runOnMainThread`, `runOnBackground`, `transformToWorklet` | Cross-thread function calls. |
|
|
93
|
+
| `useSharedValue`, `SharedValue`, `SharedValueState` | The cross-thread primitive — MT-writable, BG-observable values. See below. |
|
|
94
|
+
| `useAnimatedStyle` | Bind an element style to a `SharedValue` via a named mapper (linear or range-mapped), applied on MT every flush. |
|
|
95
|
+
| `MainThread`, `Define`, `ViewAttributes`, … | JSX type annotations. |
|
|
38
96
|
|
|
39
97
|
## SharedValue — the cross-thread primitive
|
|
40
98
|
|
|
41
99
|
`useSharedValue<T>(initial)` returns a value you can **write from a main-thread worklet** and **read reactively from the background thread**.
|
|
42
100
|
|
|
43
|
-
It's not animation-specific. `SharedValue` is a general "fast state lives on the other thread" primitive
|
|
101
|
+
It's not animation-specific. `SharedValue` is a general "fast state lives on the other thread" primitive — animation, gestures, scroll, sensors, layout are all parallel customers of the same bridge.
|
|
44
102
|
|
|
45
103
|
```tsx
|
|
46
104
|
import { useSharedValue } from '@sigx/lynx';
|
|
47
|
-
import { Draggable } from '@sigx/gestures';
|
|
105
|
+
import { Draggable } from '@sigx/lynx-gestures';
|
|
48
106
|
|
|
49
107
|
const tx = useSharedValue(0);
|
|
50
108
|
|
|
@@ -52,61 +110,45 @@ const tx = useSharedValue(0);
|
|
|
52
110
|
<text>x = {tx.value}px</text> // BG-reactive, updates per drag frame
|
|
53
111
|
```
|
|
54
112
|
|
|
55
|
-
The MT side mutates `tx.current.value` from inside a `'main thread'` worklet (zero-latency). On every `__FlushElementTree` boundary
|
|
56
|
-
|
|
57
|
-
### Customers of the bridge
|
|
58
|
-
|
|
59
|
-
| Customer | What it provides | Built on |
|
|
60
|
-
| --- | --- | --- |
|
|
61
|
-
| Animation | `withSpring`, `withTiming`, `animate` | `@sigx/motion` |
|
|
62
|
-
| Gestures | `<Pressable>`, `<Draggable>`, `<Swipeable>` | `@sigx/gestures` |
|
|
63
|
-
| Scroll | `<ScrollView offsetY={sv} offsetX={sv}>` | `@sigx/gestures` |
|
|
64
|
-
| Style bindings | `useAnimatedStyle(elRef, sv, mapperName, params)` | `@sigx/lynx` |
|
|
113
|
+
The MT side mutates `tx.current.value` from inside a `'main thread'` worklet (zero-latency). On every `__FlushElementTree` boundary the runtime diffs registered values and dispatches a single batched event to BG, where each value lands in a SignalX `signal`. A BG `effect(() => sv.value)` re-runs reactively without injecting BG into the gesture hot path.
|
|
65
114
|
|
|
66
115
|
### Scroll-driven UI example
|
|
67
116
|
|
|
68
117
|
```tsx
|
|
69
118
|
import {
|
|
70
|
-
|
|
71
|
-
|
|
119
|
+
useSharedValue, useAnimatedStyle, useMainThreadRef,
|
|
120
|
+
type MainThread,
|
|
72
121
|
} from '@sigx/lynx';
|
|
73
|
-
import { ScrollView } from '@sigx/gestures';
|
|
122
|
+
import { ScrollView } from '@sigx/lynx-gestures';
|
|
74
123
|
|
|
75
124
|
const scrollY = useSharedValue(0);
|
|
76
125
|
const heroRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
77
126
|
|
|
78
127
|
// Parallax: as scroll goes 0 → 300, the hero translates 0 → -150 px.
|
|
79
128
|
useAnimatedStyle(heroRef, scrollY, 'translateY', {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
129
|
+
inputRange: [0, 300],
|
|
130
|
+
outputRange: [0, -150],
|
|
131
|
+
extrapolate: 'clamp',
|
|
83
132
|
});
|
|
84
133
|
|
|
85
134
|
<ScrollView offsetY={scrollY}>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
135
|
+
<view main-thread:ref={heroRef}><image src={hero} /></view>
|
|
136
|
+
<text>Body…</text>
|
|
137
|
+
<text>Scroll position (BG-reactive): {scrollY.value.toFixed(0)}px</text>
|
|
89
138
|
</ScrollView>
|
|
90
139
|
```
|
|
91
140
|
|
|
92
|
-
Scroll → `<ScrollView>`'s
|
|
93
|
-
|
|
94
|
-
### What this is not
|
|
95
|
-
|
|
96
|
-
- **Not bidirectional.** Writes from BG (`sv.value = 100`) are no-op'd with a dev warning. Authoritative state lives on MT; BG observes. A bidirectional bridge would be a larger design and isn't currently scoped.
|
|
97
|
-
|
|
98
|
-
### Differentiator
|
|
99
|
-
|
|
100
|
-
Neither vue-lynx nor react-lynx ships a BG-observable MT value. React-lynx's `MainThreadRef.current` *throws* on BG; framer-motion-style libraries store animation state in MT-only refs. The diff/publish bridge in `@sigx/lynx-runtime` is what makes `effect(() => sv.value)` work — a primitive unique to sigx-lynx as of 2026-04.
|
|
141
|
+
Scroll → `<ScrollView>`'s MT worklet writes `scrollY.current.value` → flush triggers `useAnimatedStyle`'s mapper and applies the transform → MT publishes the diff to BG → `<text>` updates reactively. End-to-end, never crosses to BG inside the scroll hot path. The user just passes a `SharedValue` — same shape as `<Draggable translateX={tx}>`.
|
|
101
142
|
|
|
102
|
-
###
|
|
143
|
+
### Caveats
|
|
103
144
|
|
|
104
|
-
|
|
145
|
+
- **Not bidirectional.** Writes from BG (`sv.value = 100`) are no-op'd with a dev warning. Authoritative state lives on MT; BG observes.
|
|
146
|
+
- **Mappers register on MT.** Custom mappers must be registered from a MT-side module via `registerMapper(name, fn)` — BG-side `useAnimatedStyle` only carries the *name*.
|
|
105
147
|
|
|
106
|
-
##
|
|
148
|
+
## The rest of the ecosystem
|
|
107
149
|
|
|
108
|
-
|
|
150
|
+
This package is the framework entry point. For the full list of native modules, UI packages, gestures, animation, navigation, icons, and dev tooling — see the [monorepo README](https://github.com/signalxjs/lynx#packages).
|
|
109
151
|
|
|
110
152
|
## License
|
|
111
153
|
|
|
112
|
-
MIT
|
|
154
|
+
MIT — © Andreas Ekdahl
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "sigx-lynx framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@sigx/reactivity": "^0.4.8",
|
|
42
42
|
"@sigx/runtime-core": "^0.4.8",
|
|
43
|
-
"@sigx/lynx-runtime": "^0.4.
|
|
43
|
+
"@sigx/lynx-runtime": "^0.4.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@typescript/native-preview": "7.0.0-dev.20260521.1",
|