@tschk/moonshine-solid 0.3.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 +28 -0
- package/package.json +42 -0
- package/src/framework.ts +124 -0
- package/src/h.ts +4 -0
- package/src/index.ts +305 -0
- package/src/solid.ts +25 -0
- package/src/store.ts +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# `@tschk/moonshine-solid`
|
|
2
|
+
|
|
3
|
+
Solid renderer and signal bridge for Moonshine. Solid keeps its own native
|
|
4
|
+
rendering model; Moonshine does not share a vnode with React.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { createSignal as solidSignal, For } from "@tschk/moonshine-solid/solid";
|
|
8
|
+
import { parseCrepus } from "@tschk/crepus-moonshine";
|
|
9
|
+
import { renderCrepusIrSolid } from "@tschk/moonshine-solid";
|
|
10
|
+
import { createMoonshineSignal } from "@tschk/moonshine-solid";
|
|
11
|
+
|
|
12
|
+
const view = renderCrepusIrSolid(parseCrepus(source), { rootTag: "main" });
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Subpath | Provides |
|
|
16
|
+
| --------- | ----------------------------------------- |
|
|
17
|
+
| `.` | Solid renderer, Crepus IR, signal bridges |
|
|
18
|
+
| `./solid` | `solid-js` re-exports |
|
|
19
|
+
| `./h` | `solid-js/h` factory |
|
|
20
|
+
| `./store` | Solid store helpers |
|
|
21
|
+
|
|
22
|
+
View IR types come from `@tschk/crepuscularity-wasm`, the same source of truth the React
|
|
23
|
+
renderer uses. Template class tokens (`style.classes`) are emitted verbatim as
|
|
24
|
+
`class`; View IR style hints are never converted to inline CSS.
|
|
25
|
+
|
|
26
|
+
Peer: `solid-js` >= 1.8.
|
|
27
|
+
|
|
28
|
+
See [Moonshine README](../../README.md) and [DESIGN.md](../../DESIGN.md).
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tschk/moonshine-solid",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Solid adapter — moonshine IR bridge + solid-js library re-exports",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./solid": "./src/solid.ts",
|
|
12
|
+
"./store": "./src/store.ts",
|
|
13
|
+
"./h": "./src/h.ts"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@tschk/crepuscularity-wasm": "^0.1.0",
|
|
17
|
+
"@tschk/moonshine": "^0.3.1",
|
|
18
|
+
"@tschk/moonshine-framework": "^0.3.1",
|
|
19
|
+
"@tschk/crepus-moonshine": "^0.3.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "7.0.2"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"solid-js": ">=1.8"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"moonshine",
|
|
33
|
+
"solid",
|
|
34
|
+
"adapter"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"format": "bunx prettier --check .",
|
|
38
|
+
"lint": "bunx oxlint .",
|
|
39
|
+
"typecheck": "bunx tsc --noEmit",
|
|
40
|
+
"test": "bun test"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/framework.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { RenderContext, Renderer } from "@tschk/moonshine-framework";
|
|
2
|
+
import type { JSX } from "solid-js";
|
|
3
|
+
import {
|
|
4
|
+
createComponent,
|
|
5
|
+
escape,
|
|
6
|
+
hydrate as solidHydrate,
|
|
7
|
+
isServer,
|
|
8
|
+
renderToStream,
|
|
9
|
+
renderToStringAsync,
|
|
10
|
+
ssr,
|
|
11
|
+
} from "solid-js/web";
|
|
12
|
+
|
|
13
|
+
type SsrNode = ReturnType<typeof ssr>;
|
|
14
|
+
|
|
15
|
+
type PageProps = {
|
|
16
|
+
params: Record<string, string>;
|
|
17
|
+
data: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function loadComponent(
|
|
21
|
+
file: string,
|
|
22
|
+
): Promise<(props: unknown) => SsrNode> {
|
|
23
|
+
const mod = await import(file);
|
|
24
|
+
return (mod.default ?? mod) as (props: unknown) => SsrNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function escapeAttr(value: string): string {
|
|
28
|
+
return (escape as (s: string, attr: boolean) => string)(value, true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function Shell(props: { mode: string; children: SsrNode }): SsrNode {
|
|
32
|
+
return ssr(
|
|
33
|
+
["<!DOCTYPE html><html><head></head>", "</html>"],
|
|
34
|
+
ssr(
|
|
35
|
+
['<body data-moonshine-mode="', '">', "</body>"],
|
|
36
|
+
escapeAttr(props.mode),
|
|
37
|
+
props.children,
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function buildBody(context: RenderContext): Promise<SsrNode> {
|
|
43
|
+
const Page = await loadComponent(context.route.file);
|
|
44
|
+
const pageProps: PageProps = { params: context.params, data: context.data };
|
|
45
|
+
let body = Page(pageProps);
|
|
46
|
+
if (context.route.layouts) {
|
|
47
|
+
for (let i = context.route.layouts.length - 1; i >= 0; i--) {
|
|
48
|
+
const Layout = await loadComponent(context.route.layouts[i]!);
|
|
49
|
+
body = Layout({ ...pageProps, children: body });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Shell({ mode: context.route.mode, children: body });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildSpaShell(context: RenderContext): SsrNode {
|
|
56
|
+
const clientTags = context.route.clientEntries
|
|
57
|
+
.map((href) => `<script type="module" src="${escapeAttr(href)}"></script>`)
|
|
58
|
+
.join("");
|
|
59
|
+
const body = ssr(
|
|
60
|
+
[
|
|
61
|
+
'<body data-moonshine-mode="',
|
|
62
|
+
'"><div id="moonshine-app" data-moonshine-app="',
|
|
63
|
+
'"></div>',
|
|
64
|
+
"</body>",
|
|
65
|
+
],
|
|
66
|
+
escapeAttr(context.route.mode),
|
|
67
|
+
escapeAttr(context.route.id),
|
|
68
|
+
clientTags,
|
|
69
|
+
);
|
|
70
|
+
return ssr(
|
|
71
|
+
["<!DOCTYPE html><html><head>", "</head>", "</html>"],
|
|
72
|
+
clientTags,
|
|
73
|
+
body,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function renderPageToString(context: RenderContext): Promise<string> {
|
|
78
|
+
const body = await buildBody(context);
|
|
79
|
+
return renderToStringAsync(() => body);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function renderPage(context: RenderContext): Promise<Response> {
|
|
83
|
+
const body = await buildBody(context);
|
|
84
|
+
const { readable, writable } = new TransformStream();
|
|
85
|
+
const stream = renderToStream(() => body);
|
|
86
|
+
stream.pipeTo(writable);
|
|
87
|
+
return new Response(readable, {
|
|
88
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const solidRenderer: Renderer = {
|
|
93
|
+
name: "solid",
|
|
94
|
+
async prerender(context) {
|
|
95
|
+
if (context.route.mode === "api") return JSON.stringify(context.data);
|
|
96
|
+
if (context.route.mode === "spa") {
|
|
97
|
+
return renderToStringAsync(() => buildSpaShell(context));
|
|
98
|
+
}
|
|
99
|
+
return renderPageToString(context);
|
|
100
|
+
},
|
|
101
|
+
async render(context) {
|
|
102
|
+
if (context.route.mode === "api") {
|
|
103
|
+
return new Response(JSON.stringify(context.data), {
|
|
104
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (context.route.mode === "spa") {
|
|
108
|
+
const html = await renderToStringAsync(() => buildSpaShell(context));
|
|
109
|
+
return new Response(html, {
|
|
110
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return renderPage(context);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export function hydrateSolid<T>(
|
|
118
|
+
Component: (props: T) => JSX.Element,
|
|
119
|
+
props: T,
|
|
120
|
+
container: Element,
|
|
121
|
+
): (() => void) | undefined {
|
|
122
|
+
if (isServer) return;
|
|
123
|
+
return solidHydrate(() => createComponent(Component, props), container);
|
|
124
|
+
}
|
package/src/h.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tschk/moonshine-solid
|
|
3
|
+
*
|
|
4
|
+
* Solid gets a separate adapter — no shared vnode with React (AGENTS.md).
|
|
5
|
+
* IR types come from `@tschk/crepuscularity-wasm`; host elements via solid-js/h.
|
|
6
|
+
*
|
|
7
|
+
* Prefer Solid signals in Solid apps. Bridge helpers exist only when sharing
|
|
8
|
+
* a moonshine signal across host boundaries.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { TabItem, ViewIr, ViewNode } from "@tschk/crepuscularity-wasm";
|
|
12
|
+
import {
|
|
13
|
+
createSignal as msCreateSignal,
|
|
14
|
+
type Signal as MsSignal,
|
|
15
|
+
} from "@tschk/moonshine";
|
|
16
|
+
import {
|
|
17
|
+
createEffect,
|
|
18
|
+
createSignal as solidCreateSignal,
|
|
19
|
+
type Accessor,
|
|
20
|
+
type JSX,
|
|
21
|
+
type Setter,
|
|
22
|
+
} from "solid-js";
|
|
23
|
+
import { createComponent, Dynamic } from "solid-js/web";
|
|
24
|
+
|
|
25
|
+
/** Host elements go through `Dynamic` so the same tree renders on server and client. */
|
|
26
|
+
function el(
|
|
27
|
+
type: string,
|
|
28
|
+
props: Record<string, unknown> | null,
|
|
29
|
+
...children: unknown[]
|
|
30
|
+
): JSX.Element {
|
|
31
|
+
const merged: Record<string, unknown> = { component: type };
|
|
32
|
+
for (const [name, value] of Object.entries(props ?? {})) {
|
|
33
|
+
if (value !== undefined) merged[name] = value;
|
|
34
|
+
}
|
|
35
|
+
if (children.length > 0) merged.children = children;
|
|
36
|
+
return createComponent(Dynamic, merged as never) as unknown as JSX.Element;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* One-way mirror: moonshine signal → Solid accessor.
|
|
41
|
+
* Prefer native `createSignal` from solid-js when staying in Solid.
|
|
42
|
+
*/
|
|
43
|
+
export function fromMoonshineSignal<T>(
|
|
44
|
+
signal: MsSignal<T>,
|
|
45
|
+
): [Accessor<T>, Setter<T>] {
|
|
46
|
+
const [get, set] = solidCreateSignal(signal());
|
|
47
|
+
createEffect(() => {
|
|
48
|
+
const unsub = signal.subscribe(() => set(() => signal()));
|
|
49
|
+
return unsub;
|
|
50
|
+
});
|
|
51
|
+
return [
|
|
52
|
+
get,
|
|
53
|
+
((v: T | ((prev: T) => T)) => {
|
|
54
|
+
if (typeof v === "function") {
|
|
55
|
+
signal.set(v as (prev: T) => T);
|
|
56
|
+
} else {
|
|
57
|
+
signal.set(v as T);
|
|
58
|
+
}
|
|
59
|
+
return signal();
|
|
60
|
+
}) as Setter<T>,
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Create a Solid pair backed by moonshine createSignal (cross-host share). */
|
|
65
|
+
export function createBridgedSignal<T>(initial: T): [Accessor<T>, Setter<T>] {
|
|
66
|
+
return fromMoonshineSignal(msCreateSignal(initial));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type CrepusNode = ViewNode;
|
|
70
|
+
export type CrepusIr = ViewIr;
|
|
71
|
+
|
|
72
|
+
export interface RenderCrepusOptions {
|
|
73
|
+
/** Wrapper element for the document root. */
|
|
74
|
+
rootTag?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Class tokens are carried verbatim from the template, so UnoCSS/Tailwind
|
|
79
|
+
* styles the output. The lowered `style` hints exist for native targets and are
|
|
80
|
+
* deliberately not converted to inline CSS here.
|
|
81
|
+
*/
|
|
82
|
+
function classOf(node: ViewNode): string | undefined {
|
|
83
|
+
const classes = (node as { style?: { classes?: string[] } }).style?.classes;
|
|
84
|
+
if (!classes || classes.length === 0) return undefined;
|
|
85
|
+
const joined = classes.filter(Boolean).join(" ");
|
|
86
|
+
return joined.length > 0 ? joined : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderChildren(children: ViewNode[]): JSX.Element[] {
|
|
90
|
+
return children.map((child) => renderCrepusNodeSolid(child));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function renderCrepusNodeSolid(node: ViewNode): JSX.Element {
|
|
94
|
+
const cls = classOf(node);
|
|
95
|
+
|
|
96
|
+
switch (node.kind) {
|
|
97
|
+
case "text":
|
|
98
|
+
return el("span", { class: cls }, node.content);
|
|
99
|
+
|
|
100
|
+
case "link":
|
|
101
|
+
return el(
|
|
102
|
+
"a",
|
|
103
|
+
{ class: cls, href: node.href, target: node.target, rel: node.rel },
|
|
104
|
+
...renderChildren(node.children),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
case "stack":
|
|
108
|
+
case "scroll":
|
|
109
|
+
return el("div", { class: cls }, ...renderChildren(node.children));
|
|
110
|
+
|
|
111
|
+
case "dropzone":
|
|
112
|
+
return el(
|
|
113
|
+
"div",
|
|
114
|
+
{ class: cls, "aria-label": node.label },
|
|
115
|
+
...renderChildren(node.children),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
case "list":
|
|
119
|
+
return el(
|
|
120
|
+
node.ordered ? "ol" : "ul",
|
|
121
|
+
{ class: cls },
|
|
122
|
+
...renderChildren(node.children),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
case "listItem":
|
|
126
|
+
return el("li", { class: cls }, ...renderChildren(node.children));
|
|
127
|
+
|
|
128
|
+
case "button":
|
|
129
|
+
return el("button", { class: cls, type: "button" }, node.label);
|
|
130
|
+
|
|
131
|
+
case "badge":
|
|
132
|
+
return el("span", { class: cls, "data-tone": node.tone }, node.label);
|
|
133
|
+
|
|
134
|
+
case "divider":
|
|
135
|
+
return el("hr", { class: cls });
|
|
136
|
+
|
|
137
|
+
case "spacer":
|
|
138
|
+
return el("div", { class: cls, "aria-hidden": true });
|
|
139
|
+
|
|
140
|
+
case "image":
|
|
141
|
+
return el("img", { class: cls, src: node.src, alt: node.alt ?? "" });
|
|
142
|
+
|
|
143
|
+
case "webView":
|
|
144
|
+
return el("iframe", { class: cls, src: node.src });
|
|
145
|
+
|
|
146
|
+
case "toggle":
|
|
147
|
+
return el(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
class: cls,
|
|
151
|
+
type: "button",
|
|
152
|
+
role: "switch",
|
|
153
|
+
"aria-checked": node.checked,
|
|
154
|
+
},
|
|
155
|
+
node.label,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
case "checkbox":
|
|
159
|
+
return el(
|
|
160
|
+
"label",
|
|
161
|
+
{ class: cls },
|
|
162
|
+
el("input", {
|
|
163
|
+
type: "checkbox",
|
|
164
|
+
checked: node.checked,
|
|
165
|
+
name: node.bind,
|
|
166
|
+
}),
|
|
167
|
+
node.label,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
case "slider":
|
|
171
|
+
return el("input", {
|
|
172
|
+
class: cls,
|
|
173
|
+
type: "range",
|
|
174
|
+
value: node.value,
|
|
175
|
+
min: node.min,
|
|
176
|
+
max: node.max,
|
|
177
|
+
step: node.step,
|
|
178
|
+
name: node.bind,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
case "progress":
|
|
182
|
+
return el("progress", { class: cls, value: node.value, max: node.max });
|
|
183
|
+
|
|
184
|
+
case "meter":
|
|
185
|
+
return el("meter", {
|
|
186
|
+
class: cls,
|
|
187
|
+
value: node.value,
|
|
188
|
+
min: node.min,
|
|
189
|
+
max: node.max,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
case "input":
|
|
193
|
+
return el(node.multiline ? "textarea" : "input", {
|
|
194
|
+
class: cls,
|
|
195
|
+
type: node.multiline ? undefined : node.secure ? "password" : "text",
|
|
196
|
+
placeholder: node.placeholder,
|
|
197
|
+
name: node.bind,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
case "picker":
|
|
201
|
+
return el(
|
|
202
|
+
"select",
|
|
203
|
+
{ class: cls, name: node.bind },
|
|
204
|
+
...node.options.map((option) =>
|
|
205
|
+
el("option", { value: option.value }, option.label),
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
case "filePicker":
|
|
210
|
+
return el(
|
|
211
|
+
"label",
|
|
212
|
+
{ class: cls },
|
|
213
|
+
el("input", {
|
|
214
|
+
type: "file",
|
|
215
|
+
accept: node.accept?.join(","),
|
|
216
|
+
multiple: node.multiple,
|
|
217
|
+
}),
|
|
218
|
+
node.label,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
case "slotRotate":
|
|
222
|
+
return el(
|
|
223
|
+
"span",
|
|
224
|
+
{
|
|
225
|
+
class: cls,
|
|
226
|
+
"data-crepus-slot-rotate": node.phrases.join("|"),
|
|
227
|
+
"data-interval-ms": node.intervalMs,
|
|
228
|
+
},
|
|
229
|
+
node.phrases[0] ?? "",
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
case "tabs":
|
|
233
|
+
return el(
|
|
234
|
+
"div",
|
|
235
|
+
{ class: cls },
|
|
236
|
+
el(
|
|
237
|
+
"div",
|
|
238
|
+
{ role: "tablist" },
|
|
239
|
+
...node.tabs.map((tab: TabItem) =>
|
|
240
|
+
el("button", { type: "button", role: "tab" }, tab.label),
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
...node.tabs.map((tab: TabItem) =>
|
|
244
|
+
el("div", { role: "tabpanel" }, ...renderChildren(tab.children)),
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Control flow the parser could not resolve statically. The chosen branch is
|
|
249
|
+
// rendered and the source expression kept as a data attribute.
|
|
250
|
+
case "if":
|
|
251
|
+
return el(
|
|
252
|
+
"div",
|
|
253
|
+
{ class: cls, "data-crepus-if": node.condition },
|
|
254
|
+
...renderChildren(node.thenChildren),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
case "forEach":
|
|
258
|
+
return el(
|
|
259
|
+
"div",
|
|
260
|
+
{
|
|
261
|
+
class: cls,
|
|
262
|
+
"data-crepus-for-each": node.bind,
|
|
263
|
+
"data-crepus-item": node.itemName,
|
|
264
|
+
},
|
|
265
|
+
...renderChildren(node.itemBody),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
default:
|
|
269
|
+
return null as unknown as JSX.Element;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Map View IR → Solid elements (separate from the React renderer).
|
|
275
|
+
* Same kind surface as `@tschk/crepus-moonshine`, one IR source of truth.
|
|
276
|
+
*/
|
|
277
|
+
export function renderCrepusIrSolid(
|
|
278
|
+
ir: ViewIr,
|
|
279
|
+
options: RenderCrepusOptions = {},
|
|
280
|
+
): JSX.Element {
|
|
281
|
+
const kids = (ir.root ?? []).map((node) => renderCrepusNodeSolid(node));
|
|
282
|
+
return el(
|
|
283
|
+
options.rootTag ?? "div",
|
|
284
|
+
{
|
|
285
|
+
"data-crepus-root": "true",
|
|
286
|
+
"data-crepus-target": "solid",
|
|
287
|
+
"data-crepus-ir-version": ir.version,
|
|
288
|
+
},
|
|
289
|
+
...kids,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export { msCreateSignal as createMoonshineSignal };
|
|
294
|
+
export type { MsSignal as MoonshineSignal };
|
|
295
|
+
|
|
296
|
+
// Core re-exports for Solid apps that share moonshine resources
|
|
297
|
+
export {
|
|
298
|
+
createResource,
|
|
299
|
+
createSignal as createMsSignal,
|
|
300
|
+
createMemo,
|
|
301
|
+
batch,
|
|
302
|
+
untrack,
|
|
303
|
+
} from "@tschk/moonshine";
|
|
304
|
+
export type { Resource, ResourceStatus } from "@tschk/moonshine";
|
|
305
|
+
export { hydrateSolid, solidRenderer } from "./framework";
|
package/src/solid.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* solid-js library re-exports for moonshine apps.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { createSignal, createEffect, For, Show } from "@tschk/moonshine-solid/solid";
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
export {
|
|
9
|
+
For,
|
|
10
|
+
Index,
|
|
11
|
+
Match,
|
|
12
|
+
Show,
|
|
13
|
+
Switch,
|
|
14
|
+
batch,
|
|
15
|
+
createEffect,
|
|
16
|
+
createMemo,
|
|
17
|
+
createResource,
|
|
18
|
+
createRoot,
|
|
19
|
+
createSignal,
|
|
20
|
+
on,
|
|
21
|
+
onCleanup,
|
|
22
|
+
onMount,
|
|
23
|
+
untrack,
|
|
24
|
+
} from "solid-js";
|
|
25
|
+
export type { Accessor, Resource, Setter } from "solid-js";
|
package/src/store.ts
ADDED