mithril-lynx 0.0.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/list.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ // Ambient declaration for the ESM list.js (the file itself is not
2
+ // type-checked; this describes its runtime export shape for TS consumers).
3
+
4
+ export interface CreateListOptions {
5
+ itemCount: number;
6
+ /** Must return a fresh vnode for the cell's content; may be called more than once for the same index (recycling). */
7
+ renderItem(index: number): unknown;
8
+ /** Defaults to String(index). Set on every item's "item-key" attribute — required by native, not just an identity hint. */
9
+ itemKey?(index: number): string;
10
+ className?: string;
11
+ /** Defaults to "vertical". */
12
+ scrollOrientation?: "vertical" | "horizontal";
13
+ /** Defaults to "single". */
14
+ listType?: "single" | "flow";
15
+ /** Defaults to 1. */
16
+ spanCount?: number;
17
+ }
18
+
19
+ export interface ListHandle {
20
+ _handle: unknown;
21
+ nodeType: 1;
22
+ /** Call after mutating the underlying data so a subsequently-scrolled-into-view cell reflects the new count. */
23
+ setItemCount(nextCount: number): void;
24
+ }
25
+
26
+ /**
27
+ * Creates a native-recycled `<list>` element (project plan, Phase 8, Tier
28
+ * 2). Imperative escape hatch — call from oncreate(vnode) and attach the
29
+ * result yourself (parentNode.appendChild(list)).
30
+ */
31
+ export function createList(parentNode: { ownerDocument: unknown }, options: CreateListOptions): ListHandle;
package/list.js ADDED
@@ -0,0 +1,185 @@
1
+ // list.js
2
+ //
3
+ // List virtualization/recycling (project plan, Phase 8, Tier 2), main-thread
4
+ // only. __CreateList's componentAtIndex/enqueueComponent callbacks are a
5
+ // native-driven recycler: native owns scroll position and cell lifecycle,
6
+ // calling back into JS to fetch or recycle a cell's content by TYPE
7
+ // (matching RecyclerView/UICollectionView semantics), not a JS-side
8
+ // windowing calculation.
9
+ //
10
+ // The sign/recycle map design and the exact __FlushElementTree({triggerLayout,
11
+ // operationID, elementID, listID}) call shape are ported from
12
+ // @lynx-js/react's OWN shipped runtime/lib/snapshot/list/list.js — real,
13
+ // proven code, not guesswork (unlike gesture.js's callback-shape question,
14
+ // this part IS verified against a real implementation). What list.js narrows
15
+ // down for v1, deliberately:
16
+ // - No deferred list items (ReactLynx's `defer`/`isReady` promise dance —
17
+ // ties into a whole separate lifecycle-event protocol not otherwise
18
+ // needed here).
19
+ // - No componentAtIndexes batching — native's single-cell componentAtIndex
20
+ // callback only.
21
+ // - No independent per-item redraw after the initial bind/recycle — a list
22
+ // item's content is (re)computed fresh from renderItem(index) every time
23
+ // native calls componentAtIndex for it (on scroll-driven reuse), not
24
+ // whenever the app's own state changes. An app wanting a currently-bound
25
+ // visible item to reflect a data change needs its OWN mechanism to ask
26
+ // native to re-request that cell; list.js does not provide one in v1.
27
+ //
28
+ // This is an imperative escape hatch, like element.js/gesture.js — call it
29
+ // from oncreate(vnode) and attach the result yourself
30
+ // (parentNode.appendChild(list)), rather than mounting it through m().
31
+ // Tier 1 (no code here at all — just m("list", ...)/m("list-item", ...) as
32
+ // ordinary tags through the existing shim, relying on its already-tested
33
+ // keyed/LIS diff) covers the common case and should be preferred unless the
34
+ // app specifically needs native-driven recycling for very large lists.
35
+
36
+ import shim from "./src/lynx-mithril-shim.js";
37
+
38
+ // A render function dedicated to list-item content, independent of
39
+ // shim.render()/redraw()'s own single-root convenience-API state (which a
40
+ // main-thread-owned or data-channel-mode app may already be using for the
41
+ // rest of the page). Each item wrapper carries its own `.vnodes`, so reusing
42
+ // one render function sequentially across many items/lists is safe — Mithril
43
+ // diffs against whatever `.vnodes` is already on the target wrapper.
44
+ const renderItemContent = shim();
45
+
46
+ function typeKeyOf(vnode) {
47
+ return typeof vnode.tag === "string" ? vnode.tag : (vnode.tag && vnode.tag.name) || "default";
48
+ }
49
+
50
+ /**
51
+ * Creates a native-recycled `<list>` element, scoped to the same component
52
+ * as `parentNode` (anything with an `ownerDocument`, e.g. a real
53
+ * LynxNodeWrapper). `renderItem(index)` must return a fresh Mithril vnode
54
+ * for that cell's content every time it's called — it may be called more
55
+ * than once for the same index (recycling).
56
+ *
57
+ * `scrollOrientation`/`listType`/`spanCount` and each item's `item-key` are
58
+ * NOT optional in practice, even though native's __CreateList/componentAtIndex
59
+ * types don't require them: every real @lynx-js/react list example sets all
60
+ * of these unconditionally (verified by reading lynx-examples/examples/list's
61
+ * base/recyclable/async-rendering demos — see ../LIST_INVESTIGATION.md), and
62
+ * a real device never called componentAtIndex at all without them. Defaults
63
+ * here match what those examples use for the simple single-column case.
64
+ */
65
+ export function createList(parentNode, options) {
66
+ const {
67
+ itemCount,
68
+ renderItem,
69
+ itemKey,
70
+ className,
71
+ scrollOrientation = "vertical",
72
+ listType = "single",
73
+ spanCount = 1,
74
+ } = options;
75
+ let count = itemCount;
76
+ const pageId = parentNode.ownerDocument._pageId;
77
+ const keyOf = typeof itemKey === "function" ? itemKey : (index) => String(index);
78
+
79
+ // itemType -> Map<sign, ItemEntry>, the reuse pool.
80
+ const recycleMap = new Map();
81
+ // sign -> ItemEntry, currently-bound (visible) items.
82
+ const signMap = new Map();
83
+
84
+ function bindFreshItem(listHandle, listId, cellIndex, opId, vnode, typeKey) {
85
+ const itemWrapper = parentNode.ownerDocument.createElement("list-item");
86
+ __SetAttribute(itemWrapper._handle, "item-key", keyOf(cellIndex));
87
+ __AppendElement(listHandle, itemWrapper._handle);
88
+ renderItemContent(itemWrapper, vnode);
89
+
90
+ const sign = __GetElementUniqueID(itemWrapper._handle);
91
+ signMap.set(sign, { wrapper: itemWrapper, typeKey });
92
+ __FlushElementTree(itemWrapper._handle, { triggerLayout: true, operationID: opId, elementID: sign, listID: listId });
93
+ return sign;
94
+ }
95
+
96
+ function bindRecycledItem(listId, cellIndex, opId, vnode, pool) {
97
+ const [sign, entry] = pool.entries().next().value;
98
+ pool.delete(sign);
99
+ // The recycled wrapper is now bound to a DIFFERENT index — its
100
+ // item-key must be updated to match, the same way its content does.
101
+ __SetAttribute(entry.wrapper._handle, "item-key", keyOf(cellIndex));
102
+ renderItemContent(entry.wrapper, vnode);
103
+ signMap.set(sign, entry);
104
+ __FlushElementTree(entry.wrapper._handle, { triggerLayout: true, operationID: opId, elementID: sign, listID: listId });
105
+ return sign;
106
+ }
107
+
108
+ function componentAtIndex(listHandle, listId, cellIndex, opId) {
109
+ if (cellIndex < 0 || cellIndex >= count) {
110
+ throw new Error(`mithril-lynx list: cellIndex ${cellIndex} out of range (itemCount=${count})`);
111
+ }
112
+ const vnode = renderItem(cellIndex);
113
+ const typeKey = typeKeyOf(vnode);
114
+ const pool = recycleMap.get(typeKey);
115
+ if (pool && pool.size > 0) return bindRecycledItem(listId, cellIndex, opId, vnode, pool);
116
+ return bindFreshItem(listHandle, listId, cellIndex, opId, vnode, typeKey);
117
+ }
118
+
119
+ function enqueueComponent(_listHandle, _listId, sign) {
120
+ const entry = signMap.get(sign);
121
+ if (entry == null) return;
122
+ signMap.delete(sign);
123
+ if (!recycleMap.has(entry.typeKey)) recycleMap.set(entry.typeKey, new Map());
124
+ recycleMap.get(entry.typeKey).set(sign, entry);
125
+ }
126
+
127
+ // THE missing piece (see LIST_INVESTIGATION.md): __CreateList + attributes
128
+ // alone never triggers componentAtIndex — native only starts requesting
129
+ // cells once it's told, via a special "update-list-info" attribute, which
130
+ // positions currently exist. Ported from @lynx-js/react's OWN
131
+ // runtime/lib/snapshot/list/listUpdateInfo.js (ListUpdateInfoRecording.flush()),
132
+ // which sends this on every list-children change, alongside a matching
133
+ // __UpdateListCallbacks call. Each insertAction entry needs an "item-key"
134
+ // (dash-case — confirmed by a real device rejecting the first attempt,
135
+ // which omitted it, with "Error for illegal list item-key in parse
136
+ // insertAction"; the exact key name matches ReactLynx's own
137
+ // `__listItemPlatformInfo['item-key']`, a raw JSX-prop-shaped bag it
138
+ // spreads into each entry). `type` per entry is still UNCONFIRMED —
139
+ // ReactLynx's version uses its own component-type reference there
140
+ // (meaningful only to its own recycling, not to native, as far as this
141
+ // file's reading of the source can tell); a constant string is used
142
+ // here since this port's own type-based recycling (typeKeyOf(), above)
143
+ // is tracked independently, in this module, not through native's copy
144
+ // of `type`.
145
+ function sendListInfo(insertAction, removeAction, updateAction) {
146
+ __SetAttribute(listHandle, "update-list-info", { insertAction, removeAction, updateAction });
147
+ __UpdateListCallbacks(listHandle, componentAtIndex, enqueueComponent);
148
+ }
149
+
150
+ function insertEntry(position) {
151
+ return { position, type: "cell", "item-key": keyOf(position) };
152
+ }
153
+
154
+ const listHandle = __CreateList(pageId, componentAtIndex, enqueueComponent, {});
155
+ __SetAttribute(listHandle, "scroll-orientation", scrollOrientation);
156
+ __SetAttribute(listHandle, "list-type", listType);
157
+ __SetAttribute(listHandle, "span-count", String(spanCount));
158
+ if (className != null) __SetClasses(listHandle, className);
159
+
160
+ sendListInfo(
161
+ Array.from({ length: count }, (_, position) => insertEntry(position)),
162
+ [],
163
+ [],
164
+ );
165
+
166
+ return {
167
+ _handle: listHandle,
168
+ nodeType: 1,
169
+ /** Call after mutating the underlying data so a subsequently-scrolled-into-view cell reflects the new count. */
170
+ setItemCount(nextCount) {
171
+ if (nextCount === count) return;
172
+ if (nextCount > count) {
173
+ sendListInfo(
174
+ Array.from({ length: nextCount - count }, (_, i) => insertEntry(count + i)),
175
+ [],
176
+ [],
177
+ );
178
+ } else {
179
+ const removed = Array.from({ length: count - nextCount }, (_, i) => nextCount + i);
180
+ sendListInfo([], removed, []);
181
+ }
182
+ count = nextCount;
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,43 @@
1
+ // Ambient declaration for the ESM main-thread.js (the file itself is not
2
+ // type-checked; this describes its runtime export shape for TS consumers).
3
+
4
+ /** Returns the most recently received data (from __RenderPage/__UpdatePage or a background push). */
5
+ export function getData<T = unknown>(): T | undefined;
6
+
7
+ /** Sends a named action to the background thread's setBackgroundEventHandler. */
8
+ export function dispatchToBackground(handlerName: string, data?: unknown): void;
9
+
10
+ export interface SetupAppOptions<TInput = unknown, TData = TInput> {
11
+ /** Called exactly once, on the first __RenderPage, to build the root Mithril vnode. */
12
+ root(): unknown;
13
+ /** Optional transform applied to raw engine/background data before storing it. */
14
+ processData?(data: TInput): TData;
15
+ /**
16
+ * When true (default), pushes processed data to the background thread on
17
+ * every __RenderPage/__UpdatePage, and listens for background-pushed
18
+ * updates, redrawing via Mithril's own shim.redraw().
19
+ */
20
+ enableBackgroundSync?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Wires the Lynx engine's page lifecycle (__RenderPage/__UpdatePage/
25
+ * __DestroyLifetime) to a Mithril app: renders `root()` once, then calls
26
+ * the shim's redraw() on every subsequent data push. See the project plan,
27
+ * Phase 3 ("data-channel mode").
28
+ */
29
+ export function setupApp<TInput = unknown, TData = TInput>(
30
+ options: SetupAppOptions<TInput, TData>,
31
+ ): void;
32
+
33
+ /**
34
+ * Registers a handler background.js's runOnMainThread(key, ...args) can
35
+ * call by name. See the project plan, Phase 6.
36
+ */
37
+ export function registerHandler(key: string, fn: (...args: unknown[]) => unknown): void;
38
+
39
+ /**
40
+ * Calls a handler background.js registered via registerHandler(key, fn).
41
+ * Args and the resolved value must be JSON-serializable.
42
+ */
43
+ export function runOnBackground<T = unknown>(key: string, ...args: unknown[]): Promise<T>;
package/main-thread.js ADDED
@@ -0,0 +1,165 @@
1
+ // main-thread.js
2
+ //
3
+ // Cross-thread "data-channel mode" adapter for the main thread (see the
4
+ // project plan, Phase 3). Ports the engine-lifecycle wiring from
5
+ // lynx-examples/examples/vanilla/src/common/main-thread/setup.ts, adapted so
6
+ // the app supplies a Mithril root component instead of hand-written
7
+ // renderPage/updatePage functions: Mithril's own redraw/diff machinery
8
+ // (already ported in ../src/lynx-mithril-shim.js) does the update work.
9
+ //
10
+ // The app is responsible for reading live data inside its Mithril view()
11
+ // functions via getData() — exactly like any plain-JS Mithril store, no
12
+ // special re-invocation of a "mount" function is needed on every update.
13
+
14
+ import shim from "./src/lynx-mithril-shim.js";
15
+ import {
16
+ callBackgroundEventName,
17
+ callBackgroundResultEventName,
18
+ callMainThreadEventName,
19
+ callMainThreadResultEventName,
20
+ destroyLifetimeEventName,
21
+ dispatchEventToBackgroundEventName,
22
+ renderPageEventName,
23
+ updateDataFromBackgroundEventName,
24
+ updateDataFromMainThreadEventName,
25
+ updatePageEventName,
26
+ } from "./internal/constants.js";
27
+
28
+ let latestData;
29
+
30
+ // The native engine unconditionally invokes a global `processData(initData)`
31
+ // hook on every __RenderPage/__UpdatePage, regardless of framework. Install a
32
+ // pass-through default immediately so apps that don't need to transform
33
+ // incoming data (the common case) don't have to think about this at all.
34
+ Object.assign(globalThis, {
35
+ processData: (data) => data,
36
+ });
37
+
38
+ export function getData() {
39
+ return latestData;
40
+ }
41
+
42
+ export function dispatchToBackground(handlerName, data) {
43
+ lynx.getJSContext().dispatchEvent({
44
+ type: dispatchEventToBackgroundEventName,
45
+ data: { handlerName, data },
46
+ });
47
+ }
48
+
49
+ export function setupApp(options) {
50
+ const { root, processData, enableBackgroundSync = true } = options;
51
+ const engine = lynx.getEngine();
52
+ const background = enableBackgroundSync ? lynx.getJSContext() : undefined;
53
+ let rendered = false;
54
+
55
+ const applyData = (data) => {
56
+ latestData = typeof processData === "function" ? processData(data) : data;
57
+ if (enableBackgroundSync) {
58
+ lynx.getJSContext().dispatchEvent({
59
+ type: updateDataFromMainThreadEventName,
60
+ data: latestData,
61
+ });
62
+ }
63
+ return latestData;
64
+ };
65
+
66
+ const onRenderPage = (event) => {
67
+ const [data] = event.data;
68
+ applyData(data);
69
+ const page = __CreatePage("0", 0);
70
+ shim.renderToPage(page, root());
71
+ rendered = true;
72
+ };
73
+
74
+ const onUpdatePage = (event) => {
75
+ const [data] = event.data;
76
+ applyData(data);
77
+ if (rendered) shim.redraw();
78
+ };
79
+
80
+ const onDataFromBackground = (event) => {
81
+ latestData = { ...latestData, ...event.data };
82
+ if (rendered) shim.redraw();
83
+ };
84
+
85
+ const onDestroyLifetime = () => {
86
+ if (enableBackgroundSync) {
87
+ // The background thread has no lynx.getEngine() of its own, so relay
88
+ // the native lifecycle event across the channel explicitly — this is
89
+ // what lets background.js's own setupBackground() cleanup run.
90
+ background.dispatchEvent({ type: destroyLifetimeEventName, data: undefined });
91
+ background.removeEventListener(updateDataFromBackgroundEventName, onDataFromBackground);
92
+ }
93
+ engine.removeEventListener(renderPageEventName, onRenderPage);
94
+ engine.removeEventListener(updatePageEventName, onUpdatePage);
95
+ engine.removeEventListener(destroyLifetimeEventName, onDestroyLifetime);
96
+ };
97
+
98
+ engine.addEventListener(renderPageEventName, onRenderPage);
99
+ engine.addEventListener(updatePageEventName, onUpdatePage);
100
+ engine.addEventListener(destroyLifetimeEventName, onDestroyLifetime);
101
+ if (enableBackgroundSync) {
102
+ background.addEventListener(updateDataFromBackgroundEventName, onDataFromBackground);
103
+ }
104
+ }
105
+
106
+ // Cross-thread function registry (project plan, Phase 6 — worklet
107
+ // substitute). The common worklet use case (a gesture/tap handler running
108
+ // on the thread it's authored on) needs NONE of this — it's already just an
109
+ // ordinary function in main-thread.ts. This is only for the remaining case:
110
+ // background-owned code needs to trigger a main-thread action outside the
111
+ // normal render/data cycle. Equal *capability* to upstream ReactLynx's own
112
+ // runOnMainThread/runOnBackground (both are async serialized RPC under the
113
+ // hood there too) — worse *ergonomics* only, since there's no compiler to
114
+ // extract an inline closure; handlers must be named and pre-registered on
115
+ // the thread they run on. Listener setup is lazy (on first use), not
116
+ // module-top-level, so importing this module never assumes a particular
117
+ // thread is active yet.
118
+ const mainThreadHandlers = new Map();
119
+ const pendingBackgroundCalls = new Map();
120
+ let nextCallId = 1;
121
+ let crossThreadCallsReady = false;
122
+
123
+ function ensureCrossThreadCalls() {
124
+ if (crossThreadCallsReady) return;
125
+ crossThreadCallsReady = true;
126
+ const background = lynx.getJSContext();
127
+
128
+ background.addEventListener(callMainThreadEventName, (event) => {
129
+ const { callId, key, args } = event.data;
130
+ const fn = mainThreadHandlers.get(key);
131
+ let result;
132
+ let error;
133
+ try {
134
+ result = fn ? fn(...args) : undefined;
135
+ } catch (e) {
136
+ error = e instanceof Error ? e.message : String(e);
137
+ }
138
+ background.dispatchEvent({ type: callMainThreadResultEventName, data: { callId, result, error } });
139
+ });
140
+
141
+ background.addEventListener(callBackgroundResultEventName, (event) => {
142
+ const { callId, result, error } = event.data;
143
+ const pending = pendingBackgroundCalls.get(callId);
144
+ if (pending == null) return;
145
+ pendingBackgroundCalls.delete(callId);
146
+ if (error != null) pending.reject(new Error(error));
147
+ else pending.resolve(result);
148
+ });
149
+ }
150
+
151
+ /** Registers a handler background.runOnMainThread(key, ...) can call by name. */
152
+ export function registerHandler(key, fn) {
153
+ ensureCrossThreadCalls();
154
+ mainThreadHandlers.set(key, fn);
155
+ }
156
+
157
+ /** Calls a handler background.js registered via registerHandler(key, fn), by name. Args must be JSON-serializable. */
158
+ export function runOnBackground(key, ...args) {
159
+ ensureCrossThreadCalls();
160
+ return new Promise((resolve, reject) => {
161
+ const callId = nextCallId++;
162
+ pendingBackgroundCalls.set(callId, { resolve, reject });
163
+ lynx.getJSContext().dispatchEvent({ type: callBackgroundEventName, data: { callId, key, args } });
164
+ });
165
+ }
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "mithril-lynx",
3
+ "version": "0.0.1",
4
+ "description": "Mithril.js rendered through Lynx's Element PAPI — a contract-complete port of mithril/render/render.js@2.3.8 to the Lynx main thread, packaged as a reusable framework.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/carlos-sweb/mithril-lynx.git"
10
+ },
11
+ "homepage": "https://github.com/carlos-sweb/mithril-lynx#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/carlos-sweb/mithril-lynx/issues"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./src/lynx-mithril-shim.d.ts",
18
+ "default": "./src/lynx-mithril-shim.js"
19
+ },
20
+ "./plugin": {
21
+ "types": "./plugin.d.ts",
22
+ "default": "./plugin.js"
23
+ },
24
+ "./main-thread": {
25
+ "types": "./main-thread.d.ts",
26
+ "default": "./main-thread.js"
27
+ },
28
+ "./background": {
29
+ "types": "./background.d.ts",
30
+ "default": "./background.js"
31
+ },
32
+ "./renderer/main-thread": {
33
+ "types": "./renderer/main-thread.d.ts",
34
+ "default": "./renderer/main-thread.js"
35
+ },
36
+ "./renderer/background": {
37
+ "types": "./renderer/background.d.ts",
38
+ "default": "./renderer/background.js"
39
+ },
40
+ "./element": {
41
+ "types": "./element.d.ts",
42
+ "default": "./element.js"
43
+ },
44
+ "./gesture": {
45
+ "types": "./gesture.d.ts",
46
+ "default": "./gesture.js"
47
+ },
48
+ "./list": {
49
+ "types": "./list.d.ts",
50
+ "default": "./list.js"
51
+ },
52
+ "./testing": {
53
+ "types": "./testing.d.ts",
54
+ "default": "./testing.js"
55
+ }
56
+ },
57
+ "files": [
58
+ "src",
59
+ "internal",
60
+ "renderer",
61
+ "plugin.js",
62
+ "plugin.d.ts",
63
+ "main-thread.js",
64
+ "main-thread.d.ts",
65
+ "background.js",
66
+ "background.d.ts",
67
+ "element.js",
68
+ "element.d.ts",
69
+ "gesture.js",
70
+ "gesture.d.ts",
71
+ "list.js",
72
+ "list.d.ts",
73
+ "testing.js",
74
+ "testing.d.ts",
75
+ "CONTRACT.md"
76
+ ],
77
+ "engines": {
78
+ "node": "^20.19.0 || >=22.12.0"
79
+ },
80
+ "scripts": {
81
+ "test": "rstest run"
82
+ },
83
+ "dependencies": {
84
+ "@lynx-js/runtime-wrapper-webpack-plugin": "^0.2.4",
85
+ "@lynx-js/template-webpack-plugin": "^0.16.0"
86
+ },
87
+ "peerDependencies": {
88
+ "mithril": "2.3.8",
89
+ "@lynx-js/type-element-api": "0.0.9",
90
+ "@lynx-js/types": "4.1.0",
91
+ "@lynx-js/rspeedy": "^0.17.0"
92
+ },
93
+ "devDependencies": {
94
+ "mithril": "2.3.8",
95
+ "@lynx-js/testing-environment": "npm:@lynx-js/testing-environment-canary@0.3.2-canary-20260803-5a891c73",
96
+ "@rstest/core": "^0.11.3",
97
+ "jsdom": "^28.0.0",
98
+ "typescript": "~6.0.3"
99
+ }
100
+ }
package/plugin.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ // Ambient declaration for the ESM plugin.js (the file itself is not
2
+ // type-checked; this describes its runtime export shape for lynx.config.ts).
3
+
4
+ import type { RsbuildPlugin } from "@lynx-js/rspeedy";
5
+
6
+ export interface PluginMithrilLynxOptions {
7
+ /** Lynx engine target SDK version. Defaults to "3.5". */
8
+ targetSdkVersion?: string;
9
+ }
10
+
11
+ /**
12
+ * Dual-bundle build plugin: for each configured entry, compiles the entry
13
+ * file as the main-thread/Lepus bundle, and — if a sibling `background.ts`
14
+ * or `background.js` file exists next to it — compiles that as the
15
+ * background/JS-thread bundle. Pure multi-entry bundling, no code transform.
16
+ */
17
+ export function pluginMithrilLynx(options?: PluginMithrilLynxOptions): RsbuildPlugin;