@nifrajs/web-svelte 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/Await.svelte +27 -0
- package/dist/Await.svelte.d.ts +21 -0
- package/dist/Chain.svelte +35 -0
- package/dist/Content.svelte +13 -0
- package/dist/Content.svelte.d.ts +17 -0
- package/dist/I18nProvider.svelte +20 -0
- package/dist/I18nProvider.svelte.d.ts +15 -0
- package/dist/Image.svelte +27 -0
- package/dist/Image.svelte.d.ts +29 -0
- package/dist/Router.svelte +27 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +30 -0
- package/dist/client.js.map +1 -0
- package/dist/error.d.ts +13 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +5 -0
- package/dist/error.js.map +1 -0
- package/dist/fetcher.d.ts +36 -0
- package/dist/fetcher.d.ts.map +1 -0
- package/dist/fetcher.js +55 -0
- package/dist/fetcher.js.map +1 -0
- package/dist/i18n.d.ts +11 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +15 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mdx.d.ts +5 -0
- package/dist/mdx.d.ts.map +1 -0
- package/dist/mdx.js +74 -0
- package/dist/mdx.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +58 -0
- package/dist/plugin.js.map +1 -0
- package/dist/query.d.ts +26 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +55 -0
- package/dist/query.js.map +1 -0
- package/package.json +111 -0
- package/src/Await.svelte +27 -0
- package/src/Await.svelte.d.ts +21 -0
- package/src/Chain.svelte +35 -0
- package/src/Content.svelte +13 -0
- package/src/Content.svelte.d.ts +17 -0
- package/src/I18nProvider.svelte +20 -0
- package/src/I18nProvider.svelte.d.ts +15 -0
- package/src/Image.svelte +27 -0
- package/src/Image.svelte.d.ts +29 -0
- package/src/Router.svelte +27 -0
- package/src/client.ts +33 -0
- package/src/error.ts +15 -0
- package/src/fetcher.ts +66 -0
- package/src/i18n.ts +23 -0
- package/src/index.ts +51 -0
- package/src/mdx.ts +72 -0
- package/src/plugin.ts +60 -0
- package/src/query.ts +66 -0
- package/src/svelte-shim.d.ts +9 -0
package/dist/mdx.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { compile } from "svelte/compiler";
|
|
10
|
+
/**
|
|
11
|
+
* `@nifrajs/web-svelte/mdx` — compile `.mdx` routes/files to **Svelte** components. Svelte has no JSX
|
|
12
|
+
* runtime, so (unlike the JSX-family `mdxBunPlugin`) MDX-for-Svelte goes through `mdsvex` — Svelte's
|
|
13
|
+
* own Markdown compiler — which produces a `.svelte` module, then the standard `svelte/compiler` runs
|
|
14
|
+
* (`generate: "server"` for SSR, `"client"` for the browser). Pass it to `buildClient`/`buildServer`'s
|
|
15
|
+
* `plugins` alongside `svelteBunPlugin` (one handles `.svelte`, this one `.mdx`). `mdsvex` is an
|
|
16
|
+
* optional peer, lazy-loaded at build time.
|
|
17
|
+
*
|
|
18
|
+
* `<style>` blocks inside MDX are scoped + bundled exactly like `svelteBunPlugin`'s (own CSS namespace
|
|
19
|
+
* so the two plugins don't collide).
|
|
20
|
+
*/
|
|
21
|
+
const MDSVEX_MODULE = "mdsvex"; // non-literal so consumers don't need mdsvex's types to typecheck
|
|
22
|
+
const STYLE_SUFFIX = "?svelte-mdx-css";
|
|
23
|
+
const STYLE_NS = "nifra-svelte-mdx-css";
|
|
24
|
+
/** Build a `Bun.build` plugin that loads `.mdx` files as Svelte components via mdsvex. `generate`:
|
|
25
|
+
* `"ssr"` for the server build, `"dom"` for the client (matches `svelteBunPlugin`). */
|
|
26
|
+
export function svelteMdxBunPlugin(generate) {
|
|
27
|
+
const cssByPath = new Map();
|
|
28
|
+
return {
|
|
29
|
+
name: `nifra-svelte-mdx-${generate}`,
|
|
30
|
+
async setup(build) {
|
|
31
|
+
let mdsvexCompile;
|
|
32
|
+
try {
|
|
33
|
+
mdsvexCompile = (await import(__rewriteRelativeImportExtension(MDSVEX_MODULE))).compile;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error("@nifrajs/web-svelte/mdx: compiling `.mdx` for Svelte needs the `mdsvex` package — install it (`bun add mdsvex`).");
|
|
37
|
+
}
|
|
38
|
+
build.onLoad({ filter: /\.mdx(\?|$)/ }, async (args) => {
|
|
39
|
+
const path = args.path.split("?")[0] ?? args.path;
|
|
40
|
+
// 1. Markdown/MDX → Svelte source (mdsvex). `extensions` must include `.mdx` or mdsvex skips the
|
|
41
|
+
// file (returns undefined — it only processes its configured extensions, default `.svx`).
|
|
42
|
+
const pre = await mdsvexCompile(await Bun.file(path).text(), {
|
|
43
|
+
filename: path,
|
|
44
|
+
extensions: [".mdx", ".svx", ".md"],
|
|
45
|
+
});
|
|
46
|
+
if (!pre)
|
|
47
|
+
throw new Error(`@nifrajs/web-svelte/mdx: mdsvex produced no output for ${path}`);
|
|
48
|
+
// 2. Svelte source → JS, exactly like `svelteBunPlugin`.
|
|
49
|
+
const { js, css } = compile(pre.code, {
|
|
50
|
+
generate: generate === "ssr" ? "server" : "client",
|
|
51
|
+
filename: path,
|
|
52
|
+
css: "external",
|
|
53
|
+
});
|
|
54
|
+
if (generate === "dom" && css?.code) {
|
|
55
|
+
cssByPath.set(path, css.code);
|
|
56
|
+
return {
|
|
57
|
+
contents: `${js.code}\nimport ${JSON.stringify(path + STYLE_SUFFIX)}\n`,
|
|
58
|
+
loader: "js",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { contents: js.code, loader: "js" };
|
|
62
|
+
});
|
|
63
|
+
build.onResolve({ filter: /\?svelte-mdx-css$/ }, (args) => ({
|
|
64
|
+
path: args.path,
|
|
65
|
+
namespace: STYLE_NS,
|
|
66
|
+
}));
|
|
67
|
+
build.onLoad({ filter: /.*/, namespace: STYLE_NS }, (args) => ({
|
|
68
|
+
contents: cssByPath.get(args.path.slice(0, -STYLE_SUFFIX.length)) ?? "",
|
|
69
|
+
loader: "css",
|
|
70
|
+
}));
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=mdx.js.map
|
package/dist/mdx.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdx.js","sourceRoot":"","sources":["../src/mdx.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAsBpC,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAqClE"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { compile } from "svelte/compiler";
|
|
2
|
+
/**
|
|
3
|
+
* `@nifrajs/web-svelte/plugin` — the `.svelte` compiler Bun-plugin, in its OWN module (no `.svelte`
|
|
4
|
+
* imports). This matters for the SSR preload: `bun --preload` must register the plugin BEFORE any
|
|
5
|
+
* `.svelte` file is loaded. Importing it from the package root (`./index`) would eagerly load
|
|
6
|
+
* `Chain.svelte` (the adapter's fold) before the plugin is registered — so it lives here instead.
|
|
7
|
+
*
|
|
8
|
+
* `generate: "server"` for SSR, `"client"` for the browser (Svelte 5's client output is hydratable).
|
|
9
|
+
* Preload it for SSR (`bun --preload`) and pass it to `buildClient({ plugins: [...] })` for the client
|
|
10
|
+
* bundle. The compiler runs at build time, on `.svelte` files only.
|
|
11
|
+
*
|
|
12
|
+
* `<style>` blocks: Svelte scopes selectors (`.foo.svelte-<hash>`) and bakes the matching classes into
|
|
13
|
+
* the markup (both client + server output), so SSR HTML is already scoped. The **client** build emits
|
|
14
|
+
* the scoped stylesheet as a virtual `?svelte-css` module that `Bun.build`'s CSS bundler folds into the
|
|
15
|
+
* app stylesheet (served as a `<link>`); the SSR build drops it (the stylesheet ships from the client
|
|
16
|
+
* build — no double-emit). With `nifra dev`, `@sveltejs/vite-plugin-svelte` injects the CSS instead.
|
|
17
|
+
*/
|
|
18
|
+
const STYLE_SUFFIX = "?svelte-css";
|
|
19
|
+
const STYLE_NS = "nifra-svelte-css";
|
|
20
|
+
export function svelteBunPlugin(generate) {
|
|
21
|
+
// Compiled scoped CSS per `.svelte` file (client build only) — read back by the virtual-module loader.
|
|
22
|
+
const cssByPath = new Map();
|
|
23
|
+
return {
|
|
24
|
+
name: `nifra-svelte-${generate}`,
|
|
25
|
+
setup(build) {
|
|
26
|
+
// Match `.svelte`, tolerating a `?query` suffix (dev servers append one to bust Bun's import
|
|
27
|
+
// cache); strip it before reading the file off disk.
|
|
28
|
+
build.onLoad({ filter: /\.svelte(\?|$)/ }, async (args) => {
|
|
29
|
+
const path = args.path.split("?")[0] ?? args.path;
|
|
30
|
+
const source = await Bun.file(path).text();
|
|
31
|
+
const { js, css } = compile(source, {
|
|
32
|
+
generate: generate === "ssr" ? "server" : "client",
|
|
33
|
+
filename: path,
|
|
34
|
+
css: "external", // emit the scoped stylesheet separately (not injected into the JS)
|
|
35
|
+
});
|
|
36
|
+
if (generate === "dom" && css?.code) {
|
|
37
|
+
cssByPath.set(path, css.code);
|
|
38
|
+
// Import the virtual style module so the bundler pulls the scoped CSS into the app stylesheet.
|
|
39
|
+
return {
|
|
40
|
+
contents: `${js.code}\nimport ${JSON.stringify(path + STYLE_SUFFIX)}\n`,
|
|
41
|
+
loader: "js",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { contents: js.code, loader: "js" };
|
|
45
|
+
});
|
|
46
|
+
// Virtual CSS module: `<file>.svelte?svelte-css` → the compiled scoped stylesheet (css loader).
|
|
47
|
+
build.onResolve({ filter: /\?svelte-css$/ }, (args) => ({
|
|
48
|
+
path: args.path,
|
|
49
|
+
namespace: STYLE_NS,
|
|
50
|
+
}));
|
|
51
|
+
build.onLoad({ filter: /.*/, namespace: STYLE_NS }, (args) => ({
|
|
52
|
+
contents: cssByPath.get(args.path.slice(0, -STYLE_SUFFIX.length)) ?? "",
|
|
53
|
+
loader: "css",
|
|
54
|
+
}));
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEzC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,YAAY,GAAG,aAAa,CAAA;AAClC,MAAM,QAAQ,GAAG,kBAAkB,CAAA;AAEnC,MAAM,UAAU,eAAe,CAAC,QAAuB;IACrD,uGAAuG;IACvG,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC3C,OAAO;QACL,IAAI,EAAE,gBAAgB,QAAQ,EAAE;QAChC,KAAK,CAAC,KAAK;YACT,6FAA6F;YAC7F,qDAAqD;YACrD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAA;gBACjD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;gBAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE;oBAClC,QAAQ,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;oBAClD,QAAQ,EAAE,IAAI;oBACd,GAAG,EAAE,UAAU,EAAE,mEAAmE;iBACrF,CAAC,CAAA;gBACF,IAAI,QAAQ,KAAK,KAAK,IAAI,GAAG,EAAE,IAAI,EAAE,CAAC;oBACpC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;oBAC7B,+FAA+F;oBAC/F,OAAO;wBACL,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,YAAY,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI;wBACvE,MAAM,EAAE,IAAI;qBACb,CAAA;gBACH,CAAC;gBACD,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;YAC5C,CAAC,CAAC,CAAA;YACF,gGAAgG;YAChG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,SAAS,EAAE,QAAQ;aACpB,CAAC,CAAC,CAAA;YACH,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC7D,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;gBACvE,MAAM,EAAE,KAAK;aACd,CAAC,CAAC,CAAA;QACL,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type QueryClient, type QueryState } from "@nifrajs/web";
|
|
2
|
+
/**
|
|
3
|
+
* `@nifrajs/web-svelte/query` — Svelte bindings for the keyed query-cache, as **Svelte stores** (plain
|
|
4
|
+
* `.ts`). `useQuery(key, fn)` returns a `Readable<QueryState<T>>` augmented with `refetch` — read it
|
|
5
|
+
* reactively with `$query`. The store fetches on mount (the start notifier fires on the first
|
|
6
|
+
* `$`-subscription) and subscribes to the cache entry; concurrent `useQuery`s with the same key share
|
|
7
|
+
* one entry + one in-flight fetch (dedup). `useQueryClient` exposes `invalidateQueries`.
|
|
8
|
+
*
|
|
9
|
+
* The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
|
|
10
|
+
* there is none, so `useQuery` holds the idle/pending state (queries are client-first; loaders are the
|
|
11
|
+
* SSR data source). The first client render uses the same idle snapshot, so no mismatch.
|
|
12
|
+
*/
|
|
13
|
+
import { type Readable } from "svelte/store";
|
|
14
|
+
/** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
|
|
15
|
+
export declare function useQueryClient(): Pick<QueryClient, "invalidateQueries">;
|
|
16
|
+
/** A query store: a `Readable<QueryState<T>>` (read via `$`) plus `refetch`. */
|
|
17
|
+
export type QueryStore<T> = Readable<QueryState<T>> & {
|
|
18
|
+
/** Force a refetch (ignores `staleTime`). */
|
|
19
|
+
readonly refetch: () => Promise<T>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Subscribe to the keyed query for `key`, fetched via `fn`. Returns a store of `{ status, data, error,
|
|
23
|
+
* isFetching, updatedAt }` augmented with `refetch`. Fetches on mount (first `$`-subscription); SSR-idle.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useQuery<T>(key: unknown, fn: () => Promise<T>): QueryStore<T>;
|
|
26
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AACrB;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAY,MAAM,cAAc,CAAA;AAStD,wGAAwG;AACxG,wBAAgB,cAAc,IAAI,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAEvE;AAWD,gFAAgF;AAChF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG;IACpD,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;CACnC,CAAA;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAe7E"}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createQueryClient, } from "@nifrajs/web";
|
|
2
|
+
/**
|
|
3
|
+
* `@nifrajs/web-svelte/query` — Svelte bindings for the keyed query-cache, as **Svelte stores** (plain
|
|
4
|
+
* `.ts`). `useQuery(key, fn)` returns a `Readable<QueryState<T>>` augmented with `refetch` — read it
|
|
5
|
+
* reactively with `$query`. The store fetches on mount (the start notifier fires on the first
|
|
6
|
+
* `$`-subscription) and subscribes to the cache entry; concurrent `useQuery`s with the same key share
|
|
7
|
+
* one entry + one in-flight fetch (dedup). `useQueryClient` exposes `invalidateQueries`.
|
|
8
|
+
*
|
|
9
|
+
* The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
|
|
10
|
+
* there is none, so `useQuery` holds the idle/pending state (queries are client-first; loaders are the
|
|
11
|
+
* SSR data source). The first client render uses the same idle snapshot, so no mismatch.
|
|
12
|
+
*/
|
|
13
|
+
import { readable } from "svelte/store";
|
|
14
|
+
let client;
|
|
15
|
+
function getClient() {
|
|
16
|
+
if (typeof window === "undefined")
|
|
17
|
+
return undefined; // SSR → no client (queries render idle)
|
|
18
|
+
if (client === undefined)
|
|
19
|
+
client = createQueryClient({ now: () => Date.now() });
|
|
20
|
+
return client;
|
|
21
|
+
}
|
|
22
|
+
/** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
|
|
23
|
+
export function useQueryClient() {
|
|
24
|
+
return getClient() ?? { invalidateQueries: () => { } };
|
|
25
|
+
}
|
|
26
|
+
// Stable idle snapshot for the server / pre-fetch render.
|
|
27
|
+
const IDLE = Object.freeze({
|
|
28
|
+
status: "pending",
|
|
29
|
+
data: undefined,
|
|
30
|
+
error: undefined,
|
|
31
|
+
isFetching: false,
|
|
32
|
+
updatedAt: Number.NEGATIVE_INFINITY,
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to the keyed query for `key`, fetched via `fn`. Returns a store of `{ status, data, error,
|
|
36
|
+
* isFetching, updatedAt }` augmented with `refetch`. Fetches on mount (first `$`-subscription); SSR-idle.
|
|
37
|
+
*/
|
|
38
|
+
export function useQuery(key, fn) {
|
|
39
|
+
const handle = getClient()?.query(key, fn);
|
|
40
|
+
const store = readable(handle ? handle.snapshot() : IDLE, (set) => {
|
|
41
|
+
if (handle === undefined)
|
|
42
|
+
return;
|
|
43
|
+
set(handle.snapshot());
|
|
44
|
+
const unsubscribe = handle.subscribe(() => set(handle.snapshot()));
|
|
45
|
+
handle.fetch().catch(() => { }); // fetch on mount (the first subscriber attached)
|
|
46
|
+
return unsubscribe;
|
|
47
|
+
});
|
|
48
|
+
const refetch = handle
|
|
49
|
+
? handle.refetch
|
|
50
|
+
: (async () => {
|
|
51
|
+
throw new Error("[nifra/web-svelte] useQuery.refetch called with no query client (server?)");
|
|
52
|
+
});
|
|
53
|
+
return Object.assign(store, { refetch });
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAIlB,MAAM,cAAc,CAAA;AACrB;;;;;;;;;;GAUG;AACH,OAAO,EAAiB,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEtD,IAAI,MAA+B,CAAA;AACnC,SAAS,SAAS;IAChB,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA,CAAC,wCAAwC;IAC5F,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,GAAG,iBAAiB,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC/E,OAAO,MAAM,CAAA;AACf,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,cAAc;IAC5B,OAAO,SAAS,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAA;AACvD,CAAC;AAED,0DAA0D;AAC1D,MAAM,IAAI,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,UAAU,EAAE,KAAK;IACjB,SAAS,EAAE,MAAM,CAAC,iBAAiB;CACpC,CAAC,CAAA;AAQF;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAI,GAAY,EAAE,EAAoB;IAC5D,MAAM,MAAM,GAA+B,SAAS,EAAE,EAAE,KAAK,CAAI,GAAG,EAAE,EAAE,CAAC,CAAA;IACzE,MAAM,KAAK,GAAG,QAAQ,CAAgB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/E,IAAI,MAAM,KAAK,SAAS;YAAE,OAAM;QAChC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QACtB,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;QAClE,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA,CAAC,iDAAiD;QAChF,OAAO,WAAW,CAAA;IACpB,CAAC,CAAC,CAAA;IACF,MAAM,OAAO,GAAG,MAAM;QACpB,CAAC,CAAC,MAAM,CAAC,OAAO;QAChB,CAAC,CAAE,CAAC,KAAK,IAAI,EAAE;YACX,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAA;QAC9F,CAAC,CAAsB,CAAA;IAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;AAC1C,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nifrajs/web-svelte",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Svelte 5 render adapter for @nifrajs/web — SSR + hydration + the .svelte compiler Bun-plugin.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"typescript",
|
|
8
|
+
"nifra",
|
|
9
|
+
"web-framework",
|
|
10
|
+
"full-stack",
|
|
11
|
+
"edge",
|
|
12
|
+
"ssr"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"bun": "./src/index.ts",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./client": {
|
|
25
|
+
"bun": "./src/client.ts",
|
|
26
|
+
"types": "./dist/client.d.ts",
|
|
27
|
+
"default": "./dist/client.js"
|
|
28
|
+
},
|
|
29
|
+
"./plugin": {
|
|
30
|
+
"bun": "./src/plugin.ts",
|
|
31
|
+
"types": "./dist/plugin.d.ts",
|
|
32
|
+
"default": "./dist/plugin.js"
|
|
33
|
+
},
|
|
34
|
+
"./await": {
|
|
35
|
+
"types": "./dist/Await.svelte.d.ts",
|
|
36
|
+
"bun": "./src/Await.svelte",
|
|
37
|
+
"svelte": "./dist/Await.svelte",
|
|
38
|
+
"default": "./dist/Await.svelte"
|
|
39
|
+
},
|
|
40
|
+
"./fetcher": {
|
|
41
|
+
"bun": "./src/fetcher.ts",
|
|
42
|
+
"types": "./dist/fetcher.d.ts",
|
|
43
|
+
"default": "./dist/fetcher.js"
|
|
44
|
+
},
|
|
45
|
+
"./query": {
|
|
46
|
+
"bun": "./src/query.ts",
|
|
47
|
+
"types": "./dist/query.d.ts",
|
|
48
|
+
"default": "./dist/query.js"
|
|
49
|
+
},
|
|
50
|
+
"./image": {
|
|
51
|
+
"types": "./dist/Image.svelte.d.ts",
|
|
52
|
+
"bun": "./src/Image.svelte",
|
|
53
|
+
"svelte": "./dist/Image.svelte",
|
|
54
|
+
"default": "./dist/Image.svelte"
|
|
55
|
+
},
|
|
56
|
+
"./content": {
|
|
57
|
+
"types": "./dist/Content.svelte.d.ts",
|
|
58
|
+
"bun": "./src/Content.svelte",
|
|
59
|
+
"svelte": "./dist/Content.svelte",
|
|
60
|
+
"default": "./dist/Content.svelte"
|
|
61
|
+
},
|
|
62
|
+
"./i18n": {
|
|
63
|
+
"bun": "./src/i18n.ts",
|
|
64
|
+
"types": "./dist/i18n.d.ts",
|
|
65
|
+
"default": "./dist/i18n.js"
|
|
66
|
+
},
|
|
67
|
+
"./mdx": {
|
|
68
|
+
"bun": "./src/mdx.ts",
|
|
69
|
+
"types": "./dist/mdx.d.ts",
|
|
70
|
+
"default": "./dist/mdx.js"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"files": [
|
|
74
|
+
"dist",
|
|
75
|
+
"src"
|
|
76
|
+
],
|
|
77
|
+
"sideEffects": false,
|
|
78
|
+
"scripts": {
|
|
79
|
+
"build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist && cp src/*.svelte dist/ && cp src/*.svelte.d.ts dist/"
|
|
80
|
+
},
|
|
81
|
+
"license": "MIT",
|
|
82
|
+
"publishConfig": {
|
|
83
|
+
"access": "public"
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"@nifrajs/i18n": "workspace:*",
|
|
87
|
+
"@nifrajs/image": "workspace:*",
|
|
88
|
+
"@nifrajs/web": "workspace:*"
|
|
89
|
+
},
|
|
90
|
+
"peerDependencies": {
|
|
91
|
+
"mdsvex": "^0.12",
|
|
92
|
+
"svelte": "^5.3.0"
|
|
93
|
+
},
|
|
94
|
+
"peerDependenciesMeta": {
|
|
95
|
+
"mdsvex": {
|
|
96
|
+
"optional": true
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"devDependencies": {
|
|
100
|
+
"svelte": "^5.3.0"
|
|
101
|
+
},
|
|
102
|
+
"repository": {
|
|
103
|
+
"type": "git",
|
|
104
|
+
"url": "git+https://github.com/nifrajs/nifra.git",
|
|
105
|
+
"directory": "packages/web-svelte"
|
|
106
|
+
},
|
|
107
|
+
"bugs": {
|
|
108
|
+
"url": "https://github.com/nifrajs/nifra/issues"
|
|
109
|
+
},
|
|
110
|
+
"homepage": "https://github.com/nifrajs/nifra#readme"
|
|
111
|
+
}
|
package/src/Await.svelte
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Await.svelte — the `<Await>` primitive for deferred loader/action data (`defer()`), built on Svelte's
|
|
3
|
+
native `{#await}` block + scoped-snippet props: `children(value)` (resolved), `pending`, `error(err)`.
|
|
4
|
+
|
|
5
|
+
Svelte SSR renders the PENDING branch of `{#await}` (it does not block on the promise), so the server
|
|
6
|
+
emits the fallback and the client resolves it (hydration / soft-nav) — matching nifra's non-blocking
|
|
7
|
+
`defer()` (like the Vue adapter; contrast React/Preact, which stream the resolved content into the SSR
|
|
8
|
+
HTML). An already-resolved `resolve` (a plain value, or a client nav that awaited it) renders directly.
|
|
9
|
+
Plain-JS script (no TS) so it needs no svelte TS preprocessor.
|
|
10
|
+
-->
|
|
11
|
+
<script>
|
|
12
|
+
let { resolve, children, pending, error } = $props()
|
|
13
|
+
const isDeferred = (v) =>
|
|
14
|
+
v != null && typeof v === "object" && v.__velo_deferred === true
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#if isDeferred(resolve)}
|
|
18
|
+
{#await resolve.promise}
|
|
19
|
+
{@render pending?.()}
|
|
20
|
+
{:then value}
|
|
21
|
+
{@render children?.(value)}
|
|
22
|
+
{:catch err}
|
|
23
|
+
{@render error?.(err)}
|
|
24
|
+
{/await}
|
|
25
|
+
{:else}
|
|
26
|
+
{@render children?.(resolve)}
|
|
27
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Deferred } from "@nifrajs/web"
|
|
2
|
+
import type { Component, Snippet } from "svelte"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hand-written types for `Await.svelte` (svelte-package would generate this from the component). The
|
|
6
|
+
* internal build resolves `*.svelte` via the ambient `svelte-shim.d.ts`; this file is what *consumers*
|
|
7
|
+
* resolve through the `./await` export's `types` condition.
|
|
8
|
+
*/
|
|
9
|
+
export interface AwaitProps {
|
|
10
|
+
/** A `Deferred<T>` from a loader/action's `defer(...)`, or an already-resolved value. */
|
|
11
|
+
resolve: Deferred<unknown> | unknown
|
|
12
|
+
/** Rendered with the resolved value (the default snippet). */
|
|
13
|
+
children?: Snippet<[unknown]>
|
|
14
|
+
/** Rendered while the deferred is pending (the SSR + pre-resolve state). */
|
|
15
|
+
pending?: Snippet
|
|
16
|
+
/** Rendered if the deferred rejects. */
|
|
17
|
+
error?: Snippet<[unknown]>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare const Await: Component<AwaitProps>
|
|
21
|
+
export default Await
|
package/src/Chain.svelte
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Chain.svelte — folds a nifra layout chain (outermost layout → page) into nested Svelte components:
|
|
3
|
+
the page (innermost, chain[last]) receives `props` (the loader data); each layout wraps the rest via
|
|
4
|
+
its `children` snippet ({@render children()} in the layout). Recurses on itself. This is the Svelte
|
|
5
|
+
analogue of the React/Vue `compose` — Svelte components are compiled (not callable), so the fold is a
|
|
6
|
+
component, not a function returning a node.
|
|
7
|
+
-->
|
|
8
|
+
<script>
|
|
9
|
+
import Self from "./Chain.svelte"
|
|
10
|
+
let { chain, props } = $props()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{#if chain.length <= 1}
|
|
14
|
+
{#if chain[0]}
|
|
15
|
+
{@const Page = chain[0]}
|
|
16
|
+
<Page {...props} />
|
|
17
|
+
{/if}
|
|
18
|
+
{:else if chain[0] && chain[0].__veloErrorBoundary}
|
|
19
|
+
<!-- nifra `_error` boundary marker (from errorBoundary()): wrap the rest in <svelte:boundary>; the
|
|
20
|
+
`failed` snippet renders the route's _error component with the serialized error. -->
|
|
21
|
+
{@const Fallback = chain[0].__veloErrorBoundary}
|
|
22
|
+
<svelte:boundary>
|
|
23
|
+
<Self chain={chain.slice(1)} {props} />
|
|
24
|
+
{#snippet failed(error)}
|
|
25
|
+
<Fallback data={{ name: error?.name ?? "Error", message: String(error?.message ?? error) }} />
|
|
26
|
+
{/snippet}
|
|
27
|
+
</svelte:boundary>
|
|
28
|
+
{:else}
|
|
29
|
+
{@const Layout = chain[0]}
|
|
30
|
+
<Layout>
|
|
31
|
+
{#snippet children()}
|
|
32
|
+
<Self chain={chain.slice(1)} {props} />
|
|
33
|
+
{/snippet}
|
|
34
|
+
</Layout>
|
|
35
|
+
{/if}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Content.svelte — render trusted pre-rendered HTML (e.g. a @nifrajs/content collection entry's `.html`)
|
|
3
|
+
into the DOM via Svelte's {@html}. Pairs with content collections: <Content html={entry.html} />.
|
|
4
|
+
`as` picks the wrapper element; extra attributes in `...rest` (class/style/id/data-*) pass through.
|
|
5
|
+
|
|
6
|
+
Security: {@html} injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
|
|
7
|
+
build/server time) — never pass user-supplied HTML without sanitizing it first. Plain-JS script.
|
|
8
|
+
-->
|
|
9
|
+
<script>
|
|
10
|
+
let { html, as = "div", ...rest } = $props()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<svelte:element this={as} {...rest}>{@html html}</svelte:element>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Component } from "svelte"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hand-written types for `Content.svelte` (svelte-package would generate this). Consumers resolve this
|
|
5
|
+
* through the `./content` export's `types` condition.
|
|
6
|
+
*/
|
|
7
|
+
export interface ContentProps {
|
|
8
|
+
/** Trusted, pre-rendered HTML (e.g. `entry.html`). */
|
|
9
|
+
html: string
|
|
10
|
+
/** Wrapper element (default `"div"`). */
|
|
11
|
+
as?: string
|
|
12
|
+
/** DOM passthrough (`class`, `style`, `id`, `data-*`, …). */
|
|
13
|
+
[key: string]: unknown
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare const Content: Component<ContentProps>
|
|
17
|
+
export default Content
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
I18nProvider.svelte — provides a nifra i18n `Formatter` (built from `locale` + `messages`) to the
|
|
3
|
+
subtree via Svelte context; consumers read it with `useT()` from `@nifrajs/web-svelte/i18n`. The
|
|
4
|
+
formatter is `$derived`, so switching locale rebuilds it. `locale` + `messages` are serializable, so
|
|
5
|
+
SSR renders the negotiated catalog and the client rebuilds the same formatter on hydrate (no
|
|
6
|
+
mismatch). Plain-JS script. The context key is a string (kept in sync with i18n.ts's `useT`) so this
|
|
7
|
+
component needs no `.ts` import.
|
|
8
|
+
-->
|
|
9
|
+
<script>
|
|
10
|
+
import { createFormatter } from "@nifrajs/i18n"
|
|
11
|
+
import { setContext } from "svelte"
|
|
12
|
+
|
|
13
|
+
let { locale, messages, children } = $props()
|
|
14
|
+
|
|
15
|
+
const formatter = $derived(createFormatter(locale, messages))
|
|
16
|
+
// A getter (not the value) so consumers read the current `$derived` formatter reactively.
|
|
17
|
+
setContext("@nifrajs/web-svelte:i18n", () => formatter)
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
{@render children?.()}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Messages } from "@nifrajs/i18n"
|
|
2
|
+
import type { Component, Snippet } from "svelte"
|
|
3
|
+
|
|
4
|
+
/** Hand-written types for `I18nProvider.svelte` (consumers resolve these via the `./i18n` re-export). */
|
|
5
|
+
export interface I18nProviderProps {
|
|
6
|
+
/** The active locale (e.g. `"en"`, `"fr-CA"`). */
|
|
7
|
+
locale: string
|
|
8
|
+
/** The ICU message catalog for `locale`. */
|
|
9
|
+
messages: Messages
|
|
10
|
+
/** The subtree that reads the formatter via `useT()`. */
|
|
11
|
+
children?: Snippet
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare const I18nProvider: Component<I18nProviderProps>
|
|
15
|
+
export default I18nProvider
|
package/src/Image.svelte
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Image.svelte — a CLS-safe responsive <Image> for nifra + Svelte. A thin wrapper over `resolveImage` +
|
|
3
|
+
`toHtmlAttrs` from @nifrajs/image: it computes the responsive <img> attributes (src/srcset/sizes/width/
|
|
4
|
+
height/loading/decoding/fetchpriority — lowercase HTML names) and renders an <img>. Any extra
|
|
5
|
+
attributes (class/style/id/data-*/handlers in `...rest`) pass through. `width`+`height` are required
|
|
6
|
+
(reserve layout space — no CLS); `priority` marks the LCP image. Resizing is delegated to the
|
|
7
|
+
`loader` (an image CDN); nifra bundles no codec. Plain-JS script (no TS preprocessor needed).
|
|
8
|
+
-->
|
|
9
|
+
<script>
|
|
10
|
+
import { resolveImage, toHtmlAttrs } from "@nifrajs/image"
|
|
11
|
+
|
|
12
|
+
let { src, width, height, alt, sizes, widths, quality, loading, priority, loader, ...rest } = $props()
|
|
13
|
+
|
|
14
|
+
// `$derived` keeps the attributes in sync if props change. Build ImageProps omitting unset optionals;
|
|
15
|
+
// resolveImage validates width/height > 0 (the CLS contract) and builds the responsive srcSet.
|
|
16
|
+
const attrs = $derived.by(() => {
|
|
17
|
+
const input = { src, width, height, alt }
|
|
18
|
+
if (sizes !== undefined) input.sizes = sizes
|
|
19
|
+
if (widths !== undefined) input.widths = widths
|
|
20
|
+
if (quality !== undefined) input.quality = quality
|
|
21
|
+
if (loading !== undefined) input.loading = loading
|
|
22
|
+
if (priority !== undefined) input.priority = priority
|
|
23
|
+
return toHtmlAttrs(resolveImage(input, loader))
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<img {...rest} {...attrs} />
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ImageLoader, ImageProps } from "@nifrajs/image"
|
|
2
|
+
import type { Component } from "svelte"
|
|
3
|
+
import type { HTMLImgAttributes } from "svelte/elements"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hand-written types for `Image.svelte` (svelte-package would generate this). The internal build
|
|
7
|
+
* resolves `*.svelte` via the ambient `svelte-shim.d.ts`; this file is what *consumers* resolve through
|
|
8
|
+
* the `./image` export's `types` condition.
|
|
9
|
+
*/
|
|
10
|
+
type ComputedAttrs =
|
|
11
|
+
| "src"
|
|
12
|
+
| "srcset"
|
|
13
|
+
| "sizes"
|
|
14
|
+
| "width"
|
|
15
|
+
| "height"
|
|
16
|
+
| "alt"
|
|
17
|
+
| "loading"
|
|
18
|
+
| "decoding"
|
|
19
|
+
| "fetchpriority"
|
|
20
|
+
|
|
21
|
+
export interface ImageComponentProps
|
|
22
|
+
extends ImageProps,
|
|
23
|
+
Omit<HTMLImgAttributes, ComputedAttrs | keyof ImageProps> {
|
|
24
|
+
/** CDN URL builder. Defaults to the identity loader (no transform — still CLS-safe + lazy). */
|
|
25
|
+
loader?: ImageLoader
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare const Image: Component<ImageComponentProps>
|
|
29
|
+
export default Image
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Router.svelte — the reactive root for client navigation. Holds the agnostic store's snapshot in
|
|
3
|
+
`$state`, subscribes via `$effect` (cleaned up on unmount), and renders the matched layout chain
|
|
4
|
+
through `Chain.svelte`. `$derived` recomputes the chain + props on every store change, so client
|
|
5
|
+
navigations swap routes without a full reload. The initial snapshot matches the SSR markup (the
|
|
6
|
+
server rendered `Chain` for the same matched route), so `hydrate` reconciles cleanly.
|
|
7
|
+
|
|
8
|
+
Runes (`$state`/`$effect`/`$derived`) only work in compiled `.svelte` files — which is why this
|
|
9
|
+
reactive root is a component, not a function in `client.ts`.
|
|
10
|
+
-->
|
|
11
|
+
<script>
|
|
12
|
+
import Chain from "./Chain.svelte"
|
|
13
|
+
let { router, routes } = $props()
|
|
14
|
+
|
|
15
|
+
let snapshot = $state(router.snapshot())
|
|
16
|
+
$effect(() => router.subscribe(() => { snapshot = router.snapshot() }))
|
|
17
|
+
|
|
18
|
+
let chain = $derived(routes[snapshot.routeId] ?? [])
|
|
19
|
+
let props = $derived({
|
|
20
|
+
data: snapshot.data,
|
|
21
|
+
actionData: snapshot.actionData,
|
|
22
|
+
pending: snapshot.pending,
|
|
23
|
+
...(snapshot.submission ? { submission: snapshot.submission } : {}),
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<Chain {chain} {props} />
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MountRouterOptions, RenderProps } from "@nifrajs/web"
|
|
2
|
+
/**
|
|
3
|
+
* @nifrajs/web-svelte/client — Svelte 5 client runtime. `hydrate` hydrates a single SSR'd route via the
|
|
4
|
+
* recursive `Chain`; `mountRouter` hydrates the reactive `Router` component, which subscribes to the
|
|
5
|
+
* agnostic store and re-renders the matched chain on every client navigation (no full reload). Both
|
|
6
|
+
* use Svelte's `hydrate` (reconcile against the SSR markup). Kept in its own entry so server code
|
|
7
|
+
* stays out of the client bundle.
|
|
8
|
+
*/
|
|
9
|
+
import { hydrate as svelteHydrate } from "svelte"
|
|
10
|
+
import Chain from "./Chain.svelte"
|
|
11
|
+
import { setMountedRouter } from "./fetcher.ts"
|
|
12
|
+
import Router from "./Router.svelte"
|
|
13
|
+
|
|
14
|
+
// The `_error` boundary marker — Chain.svelte renders it as a <svelte:boundary>. Re-exported here so
|
|
15
|
+
// nifra's client codegen resolves it from `@nifrajs/web-svelte/client` alongside `mountRouter`.
|
|
16
|
+
export { errorBoundary } from "./error.ts"
|
|
17
|
+
|
|
18
|
+
/** Hydrate a server-rendered Svelte layout `chain` (with the loader `props`) inside `container`. */
|
|
19
|
+
export function hydrate(chain: readonly unknown[], props: RenderProps, container: unknown): void {
|
|
20
|
+
svelteHydrate(Chain, { target: container as Element, props: { chain, props } })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hydrate a stateful Svelte Router. The `Router` component holds the store snapshot in `$state` and
|
|
25
|
+
* re-renders the matched layout chain on each store change — so client navigations swap routes without
|
|
26
|
+
* a full reload. Its initial render matches the SSR markup (the server rendered `Chain` for the same
|
|
27
|
+
* matched route), so hydration reconciles cleanly.
|
|
28
|
+
*/
|
|
29
|
+
export function mountRouter(options: MountRouterOptions): void {
|
|
30
|
+
const { router, routes, container } = options
|
|
31
|
+
setMountedRouter(router) // expose it to useFetcher/useFetchers (same page, client-only)
|
|
32
|
+
svelteHydrate(Router, { target: container as Element, props: { router, routes } })
|
|
33
|
+
}
|