ferrovue 0.6.0 → 0.7.0
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 +106 -13
- package/dist/args.d.ts +28 -0
- package/dist/args.js +69 -0
- package/dist/attrs.js +3 -0
- package/dist/calls.js +5 -2
- package/dist/children.js +18 -3
- package/dist/cli.js +80 -81
- package/dist/client-render.d.ts +23 -0
- package/dist/client-render.js +143 -0
- package/dist/client.d.ts +65 -6
- package/dist/client.js +269 -27
- package/dist/compiler.d.ts +9 -5
- package/dist/compiler.js +48 -14
- package/dist/component.d.ts +7 -3
- package/dist/component.js +37 -25
- package/dist/conformance.d.ts +24 -3
- package/dist/conformance.js +58 -7
- package/dist/constants.js +4 -2
- package/dist/context.d.ts +51 -4
- package/dist/context.js +173 -5
- package/dist/diagnostics.d.ts +6 -4
- package/dist/diagnostics.js +7 -3
- package/dist/editor.d.ts +18 -0
- package/dist/editor.js +59 -0
- package/dist/errors.d.ts +45 -3
- package/dist/errors.js +33 -3
- package/dist/expr.d.ts +2 -0
- package/dist/expr.js +91 -12
- package/dist/fallthrough.d.ts +1 -0
- package/dist/fallthrough.js +10 -2
- package/dist/files.d.ts +13 -0
- package/dist/files.js +49 -0
- package/dist/fixture.d.ts +3 -3
- package/dist/hydration.d.ts +12 -1
- package/dist/hydration.js +47 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/inputs.d.ts +24 -0
- package/dist/inputs.js +40 -0
- package/dist/link-router.d.ts +6 -2
- package/dist/link-router.js +7 -4
- package/dist/lists.d.ts +12 -1
- package/dist/lists.js +76 -2
- package/dist/model.d.ts +9 -1
- package/dist/model.js +2 -0
- package/dist/page-routes.d.ts +1 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +7 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugins/css-modules.d.ts +39 -0
- package/dist/plugins/css-modules.js +135 -0
- package/dist/plugins/head.js +4 -4
- package/dist/plugins/i18n.js +5 -12
- package/dist/plugins/index.js +2 -1
- package/dist/plugins/provide.js +4 -2
- package/dist/plugins/router.js +3 -2
- package/dist/plugins/scoped.d.ts +10 -0
- package/dist/plugins/scoped.js +17 -4
- package/dist/plugins/shared-types.js +2 -1
- package/dist/plugins/stores.js +7 -6
- package/dist/rust.d.ts +9 -1
- package/dist/rust.js +38 -15
- package/dist/schema.d.ts +25 -0
- package/dist/schema.js +73 -0
- package/dist/script.js +12 -5
- package/dist/slots.js +2 -0
- package/dist/ssr.js +42 -4
- package/dist/styles.d.ts +5 -1
- package/dist/styles.js +131 -4
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +1 -1
- package/dist/typescript.d.ts +11 -1
- package/dist/typescript.js +124 -33
- package/dist/vite.d.ts +18 -10
- package/dist/vite.js +106 -51
- package/dist/volar.d.ts +15 -0
- package/dist/volar.js +87 -0
- package/package.json +27 -7
- package/schema.json +333 -0
package/dist/editor.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { CONFIG_FILE, generate, loadConfig } from "./compiler.js";
|
|
4
|
+
import { diagnose, isRefusal } from "./diagnostics.js";
|
|
5
|
+
const FRESH_MS = 1000;
|
|
6
|
+
const recent = new Map();
|
|
7
|
+
/** The directory holding `config` that is `dir` or the nearest above it. */
|
|
8
|
+
export function projectRoot(dir, config = CONFIG_FILE) {
|
|
9
|
+
for (let at = resolve(dir);; at = dirname(at)) {
|
|
10
|
+
if (existsSync(join(at, config)))
|
|
11
|
+
return at;
|
|
12
|
+
if (dirname(at) === at)
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function offsetOf(text, line, column) {
|
|
17
|
+
let at = 0;
|
|
18
|
+
for (let l = 1; l < line; l++) {
|
|
19
|
+
const next = text.indexOf("\n", at);
|
|
20
|
+
if (next < 0)
|
|
21
|
+
return text.length;
|
|
22
|
+
at = next + 1;
|
|
23
|
+
}
|
|
24
|
+
return Math.min(at + column - 1, text.length);
|
|
25
|
+
}
|
|
26
|
+
function check(file, text, options) {
|
|
27
|
+
const root = projectRoot(dirname(file), options.config);
|
|
28
|
+
if (!root)
|
|
29
|
+
return [];
|
|
30
|
+
try {
|
|
31
|
+
generate(root, loadConfig(root, options.config), new Map([[file, text]]));
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
if (!isRefusal(e)) {
|
|
36
|
+
console.error(e);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const d = diagnose(e);
|
|
40
|
+
if (d.file === null || resolve(root, d.file) !== file)
|
|
41
|
+
return [];
|
|
42
|
+
const start = d.line === null ? 0 : offsetOf(text, d.line, d.column ?? 1);
|
|
43
|
+
const end = d.endLine === null ? null : offsetOf(text, d.endLine, d.endColumn ?? 1);
|
|
44
|
+
return [{ ...d, start, end }];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** The refusals in a component of a ferrovue project, compiling the project with `text` in place of
|
|
48
|
+
* the file. A project is the nearest directory above the file holding the configuration; a file in
|
|
49
|
+
* none has no refusals. The compiler stops at the first refusal, so a refusal in another file hides
|
|
50
|
+
* those in this one. */
|
|
51
|
+
export function refusalsIn(file, text, options = {}) {
|
|
52
|
+
const path = resolve(file);
|
|
53
|
+
const hit = recent.get(path);
|
|
54
|
+
if (hit && hit.text === text && Date.now() - hit.at < FRESH_MS)
|
|
55
|
+
return hit.found;
|
|
56
|
+
const found = check(path, text, options);
|
|
57
|
+
recent.set(path, { text, at: Date.now(), found });
|
|
58
|
+
return found;
|
|
59
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -312,6 +312,9 @@ export declare const ERRORS: {
|
|
|
312
312
|
readonly FV0513: {
|
|
313
313
|
readonly title: "Component resolved by name that is not imported";
|
|
314
314
|
};
|
|
315
|
+
readonly FV0514: {
|
|
316
|
+
readonly title: "Listener of unknown name given to a child with a prop named like a listener";
|
|
317
|
+
};
|
|
315
318
|
readonly FV0601: {
|
|
316
319
|
readonly title: "Name or function not available on the server";
|
|
317
320
|
};
|
|
@@ -393,6 +396,9 @@ export declare const ERRORS: {
|
|
|
393
396
|
readonly FV0627: {
|
|
394
397
|
readonly title: "Strict test against the absence a value cannot have";
|
|
395
398
|
};
|
|
399
|
+
readonly FV0628: {
|
|
400
|
+
readonly title: "`as` or `satisfies` stating a type the value does not have";
|
|
401
|
+
};
|
|
396
402
|
readonly FV0701: {
|
|
397
403
|
readonly title: "`Number()` of an unsupported value";
|
|
398
404
|
};
|
|
@@ -452,7 +458,7 @@ export declare const ERRORS: {
|
|
|
452
458
|
readonly title: "List method over a list of unsupported items";
|
|
453
459
|
};
|
|
454
460
|
readonly FV0805: {
|
|
455
|
-
readonly title: "List method given anything but an arrow function";
|
|
461
|
+
readonly title: "List method given anything but an arrow function or `Boolean`";
|
|
456
462
|
};
|
|
457
463
|
readonly FV0806: {
|
|
458
464
|
readonly title: "Arrow function with a block body";
|
|
@@ -582,10 +588,12 @@ export declare const ERRORS: {
|
|
|
582
588
|
readonly title: "Class binding of an unsupported type";
|
|
583
589
|
};
|
|
584
590
|
readonly FV1005: {
|
|
585
|
-
readonly title: "`<style module>`";
|
|
591
|
+
readonly title: "`<style module>` without `cssModules` in the configuration";
|
|
592
|
+
readonly detail: "Vite names a CSS module's classes by `css.modules.generateScopedName`. ferrovue computes the same names when `cssModules` in `ferrovue.config.json` is set as that option is; without it, it cannot know them.";
|
|
586
593
|
};
|
|
587
594
|
readonly FV1006: {
|
|
588
|
-
readonly title: "`v-bind()` in CSS";
|
|
595
|
+
readonly title: "`v-bind()` in CSS of a value that is not a string or a number";
|
|
596
|
+
readonly detail: "Vue's server writes a variable `v-bind()` sets from a string or a number, `initial` for `null` and `undefined`, and warns of anything else.";
|
|
589
597
|
};
|
|
590
598
|
readonly FV1007: {
|
|
591
599
|
readonly title: "Style binding of an unsupported type";
|
|
@@ -609,6 +617,13 @@ export declare const ERRORS: {
|
|
|
609
617
|
readonly title: "`class` written apart from a later `:class` in content rendered from virtual nodes";
|
|
610
618
|
readonly detail: "Inside an element `<component :is>` chooses, in the slot content such an element renders, and inside a `<RouterLink>`, Vue's server renders from virtual nodes, where a static `class` written before `:class` keeps its place and its names come first. ferrovue writes them so when nothing stands between the two; with attributes between them, write the two next to each other, or `:class` first.";
|
|
611
619
|
};
|
|
620
|
+
readonly FV1014: {
|
|
621
|
+
readonly title: "`<style module>` whose class names ferrovue cannot compute";
|
|
622
|
+
readonly detail: "ferrovue names the classes of a CSS module written in the component in plain CSS, as postcss-modules does in Vite. A module in another file (`src`), one a preprocessor compiles (`lang`), and one that imports names from another file (`composes: x from \"./a.css\"`, `@value x from \"./a.css\"`) are refused.";
|
|
623
|
+
};
|
|
624
|
+
readonly FV1015: {
|
|
625
|
+
readonly title: "`useCssModule` of a module the component does not declare";
|
|
626
|
+
};
|
|
612
627
|
readonly FV1101: {
|
|
613
628
|
readonly title: "Helper called with the wrong number of arguments";
|
|
614
629
|
};
|
|
@@ -645,6 +660,27 @@ export declare const ERRORS: {
|
|
|
645
660
|
readonly FV1112: {
|
|
646
661
|
readonly title: "Twin slot name that is not a plain name";
|
|
647
662
|
};
|
|
663
|
+
readonly FV1113: {
|
|
664
|
+
readonly title: "Configuration that is not a JSON object";
|
|
665
|
+
};
|
|
666
|
+
readonly FV1114: {
|
|
667
|
+
readonly title: "Unknown configuration key";
|
|
668
|
+
readonly detail: "A key ferrovue does not read is refused, as it is most often a misspelt one (`component` for `components`). The message names the key it most likely means.";
|
|
669
|
+
};
|
|
670
|
+
readonly FV1115: {
|
|
671
|
+
readonly title: "Configuration value of the wrong type";
|
|
672
|
+
};
|
|
673
|
+
readonly FV1116: {
|
|
674
|
+
readonly title: "Directory the configuration names that cannot be read";
|
|
675
|
+
};
|
|
676
|
+
readonly FV1117: {
|
|
677
|
+
readonly title: "Deprecated configuration key";
|
|
678
|
+
readonly detail: "A warning: ferrovue still reads the key, and stops reading it in the next major release. The message names what to write instead. A key is deprecated for at least one minor release before it is removed.";
|
|
679
|
+
};
|
|
680
|
+
readonly FV1118: {
|
|
681
|
+
readonly title: "Deprecated command-line option";
|
|
682
|
+
readonly detail: "A warning: the option still works, and is removed in the next major release. The message names what to use instead. An option is deprecated for at least one minor release before it is removed.";
|
|
683
|
+
};
|
|
648
684
|
readonly FV1201: {
|
|
649
685
|
readonly title: "Route parameter that is not a present string or number";
|
|
650
686
|
};
|
|
@@ -782,6 +818,12 @@ export declare const ERRORS: {
|
|
|
782
818
|
readonly FV1245: {
|
|
783
819
|
readonly title: "Page whose component name is taken or not a Rust name";
|
|
784
820
|
};
|
|
821
|
+
readonly FV1246: {
|
|
822
|
+
readonly title: "Routes file that cannot be read";
|
|
823
|
+
};
|
|
824
|
+
readonly FV1247: {
|
|
825
|
+
readonly title: "Routes file that is not valid JSON";
|
|
826
|
+
};
|
|
785
827
|
readonly FV1301: {
|
|
786
828
|
readonly title: "Type declared by two stores";
|
|
787
829
|
};
|
package/dist/errors.js
CHANGED
|
@@ -145,6 +145,7 @@ export const ERRORS = {
|
|
|
145
145
|
FV0511: { title: "Prop given a value of another type" },
|
|
146
146
|
FV0512: { title: "`Props` imported from a component that is not compiled" },
|
|
147
147
|
FV0513: { title: "Component resolved by name that is not imported" },
|
|
148
|
+
FV0514: { title: "Listener of unknown name given to a child with a prop named like a listener" },
|
|
148
149
|
FV0601: { title: "Name or function not available on the server" },
|
|
149
150
|
FV0602: { title: "Unsupported method" },
|
|
150
151
|
FV0603: { title: "Unsupported call" },
|
|
@@ -172,6 +173,7 @@ export const ERRORS = {
|
|
|
172
173
|
FV0625: { title: "Unsupported expression" },
|
|
173
174
|
FV0626: { title: "Strict test of a value that may be `null` or `undefined`" },
|
|
174
175
|
FV0627: { title: "Strict test against the absence a value cannot have" },
|
|
176
|
+
FV0628: { title: "`as` or `satisfies` stating a type the value does not have" },
|
|
175
177
|
FV0701: { title: "`Number()` of an unsupported value" },
|
|
176
178
|
FV0702: { title: "`parseInt()` or `parseFloat()` of a value that is not a string" },
|
|
177
179
|
FV0703: { title: "`parseInt()` with a radix other than a literal 10 or 16" },
|
|
@@ -194,7 +196,7 @@ export const ERRORS = {
|
|
|
194
196
|
FV0802: { title: "`.join()` with a separator that is not a string" },
|
|
195
197
|
FV0803: { title: "List method on a value that is not a list" },
|
|
196
198
|
FV0804: { title: "List method over a list of unsupported items" },
|
|
197
|
-
FV0805: { title: "List method given anything but an arrow function" },
|
|
199
|
+
FV0805: { title: "List method given anything but an arrow function or `Boolean`" },
|
|
198
200
|
FV0806: { title: "Arrow function with a block body" },
|
|
199
201
|
FV0807: { title: "Arrow function index that is not a plain name" },
|
|
200
202
|
FV0808: { title: "Defaults or nested patterns in an arrow function's item" },
|
|
@@ -240,8 +242,14 @@ export const ERRORS = {
|
|
|
240
242
|
FV1002: { title: "Computed class name that is not a string" },
|
|
241
243
|
FV1003: { title: "Class name with spaces around it" },
|
|
242
244
|
FV1004: { title: "Class binding of an unsupported type" },
|
|
243
|
-
FV1005: {
|
|
244
|
-
|
|
245
|
+
FV1005: {
|
|
246
|
+
title: "`<style module>` without `cssModules` in the configuration",
|
|
247
|
+
detail: "Vite names a CSS module's classes by `css.modules.generateScopedName`. ferrovue computes the same names when `cssModules` in `ferrovue.config.json` is set as that option is; without it, it cannot know them.",
|
|
248
|
+
},
|
|
249
|
+
FV1006: {
|
|
250
|
+
title: "`v-bind()` in CSS of a value that is not a string or a number",
|
|
251
|
+
detail: "Vue's server writes a variable `v-bind()` sets from a string or a number, `initial` for `null` and `undefined`, and warns of anything else.",
|
|
252
|
+
},
|
|
245
253
|
FV1007: { title: "Style binding of an unsupported type" },
|
|
246
254
|
FV1008: { title: "Computed or spread key in a style object" },
|
|
247
255
|
FV1009: { title: "Style property named by a number or starting with `:`" },
|
|
@@ -252,6 +260,11 @@ export const ERRORS = {
|
|
|
252
260
|
title: "`class` written apart from a later `:class` in content rendered from virtual nodes",
|
|
253
261
|
detail: "Inside an element `<component :is>` chooses, in the slot content such an element renders, and inside a `<RouterLink>`, Vue's server renders from virtual nodes, where a static `class` written before `:class` keeps its place and its names come first. ferrovue writes them so when nothing stands between the two; with attributes between them, write the two next to each other, or `:class` first.",
|
|
254
262
|
},
|
|
263
|
+
FV1014: {
|
|
264
|
+
title: "`<style module>` whose class names ferrovue cannot compute",
|
|
265
|
+
detail: "ferrovue names the classes of a CSS module written in the component in plain CSS, as postcss-modules does in Vite. A module in another file (`src`), one a preprocessor compiles (`lang`), and one that imports names from another file (`composes: x from \"./a.css\"`, `@value x from \"./a.css\"`) are refused.",
|
|
266
|
+
},
|
|
267
|
+
FV1015: { title: "`useCssModule` of a module the component does not declare" },
|
|
255
268
|
FV1101: { title: "Helper called with the wrong number of arguments" },
|
|
256
269
|
FV1102: { title: "Configuration file not found" },
|
|
257
270
|
FV1103: { title: "Configuration file that is not valid JSON" },
|
|
@@ -264,6 +277,21 @@ export const ERRORS = {
|
|
|
264
277
|
FV1110: { title: "Twin without `rust`" },
|
|
265
278
|
FV1111: { title: "Twin prop name that is not camelCase" },
|
|
266
279
|
FV1112: { title: "Twin slot name that is not a plain name" },
|
|
280
|
+
FV1113: { title: "Configuration that is not a JSON object" },
|
|
281
|
+
FV1114: {
|
|
282
|
+
title: "Unknown configuration key",
|
|
283
|
+
detail: "A key ferrovue does not read is refused, as it is most often a misspelt one (`component` for `components`). The message names the key it most likely means.",
|
|
284
|
+
},
|
|
285
|
+
FV1115: { title: "Configuration value of the wrong type" },
|
|
286
|
+
FV1116: { title: "Directory the configuration names that cannot be read" },
|
|
287
|
+
FV1117: {
|
|
288
|
+
title: "Deprecated configuration key",
|
|
289
|
+
detail: "A warning: ferrovue still reads the key, and stops reading it in the next major release. The message names what to write instead. A key is deprecated for at least one minor release before it is removed.",
|
|
290
|
+
},
|
|
291
|
+
FV1118: {
|
|
292
|
+
title: "Deprecated command-line option",
|
|
293
|
+
detail: "A warning: the option still works, and is removed in the next major release. The message names what to use instead. An option is deprecated for at least one minor release before it is removed.",
|
|
294
|
+
},
|
|
267
295
|
FV1201: { title: "Route parameter that is not a present string or number" },
|
|
268
296
|
FV1202: { title: "`to` that is not a string or an object literal" },
|
|
269
297
|
FV1203: { title: "Computed or spread key in a `to` object" },
|
|
@@ -315,6 +343,8 @@ export const ERRORS = {
|
|
|
315
343
|
FV1243: { title: "`<route>` block in a page" },
|
|
316
344
|
FV1244: { title: "`_parent.vue` with no pages to hold" },
|
|
317
345
|
FV1245: { title: "Page whose component name is taken or not a Rust name" },
|
|
346
|
+
FV1246: { title: "Routes file that cannot be read" },
|
|
347
|
+
FV1247: { title: "Routes file that is not valid JSON" },
|
|
318
348
|
FV1301: { title: "Type declared by two stores" },
|
|
319
349
|
FV1302: { title: "Store id that is not a string literal" },
|
|
320
350
|
FV1303: { title: "Store of an unsupported shape" },
|
package/dist/expr.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export declare function fieldVal(comp: Component, base: string, ty: Ty, js: stri
|
|
|
4
4
|
export declare function holdsNothing(comp: Component, v: Val, want: Ty, node: N, where: string): void;
|
|
5
5
|
export declare function coerce(comp: Component, v: Val, want: Ty, node: N): string;
|
|
6
6
|
export declare function childOf(name: string): Component;
|
|
7
|
+
/** `n` with the type `t` given to each empty array literal it may evaluate to, as an `as T[]` on it. */
|
|
8
|
+
export declare function typedBy(n: N, t: N): N;
|
|
7
9
|
export declare function expr(s: Scope, n: N): Val;
|
package/dist/expr.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { absence, BOOL, fail, FLOAT, GenError, INT, joinAbsence, nothing, NULL, opt, rustStr, sameTy, snake, STR, UNDEF, withAbsence } from "./model.js";
|
|
2
2
|
import { ctx } from "./context.js";
|
|
3
|
-
import { lookupStruct, markHome } from "./typescript.js";
|
|
3
|
+
import { lookupStruct, markHome, tyOfTs } from "./typescript.js";
|
|
4
4
|
import { claim } from "./plugin.js";
|
|
5
5
|
import { atom, bare, binary, enclosed, logical, negate, not, occurrences, strArg } from "./parens.js";
|
|
6
|
-
import { collected } from "./lists.js";
|
|
6
|
+
import { collected, sliced, typedEmpty, untypedEmpty } from "./lists.js";
|
|
7
7
|
import { asCow, formatted, isTemporary, loneOf, meet, stringsEqual } from "./strings.js";
|
|
8
8
|
import { arithmetic, asF64, compare, intFromF64, isNumber, negatedOrder, numberVal } from "./numbers.js";
|
|
9
9
|
import { boolOf, checkNullTest, choice, known, narrowing, narrowTo, nullTest, pathOf, presence, truthy } from "./narrowing.js";
|
|
@@ -45,8 +45,12 @@ export function fieldVal(comp, base, ty, js, node) {
|
|
|
45
45
|
fail(comp, "FV0604", `\`.${js}\` on a value that is not an object`, node);
|
|
46
46
|
const { st, owner } = lookupStruct(comp, ty);
|
|
47
47
|
const found = st?.fields.find((x) => x.js === js);
|
|
48
|
-
if (!found)
|
|
48
|
+
if (!found) {
|
|
49
|
+
const refused = st?.refused?.get(js) ?? st?.refused?.get("");
|
|
50
|
+
if (refused !== undefined)
|
|
51
|
+
throw refused;
|
|
49
52
|
fail(comp, "FV0605", `\`${ty.name}\` has no field \`${js}\``, node);
|
|
53
|
+
}
|
|
50
54
|
const f = owner === comp ? found : { ...found, ty: markHome(found.ty, owner.name) };
|
|
51
55
|
if (st.slot)
|
|
52
56
|
return { code: `${base}.${f.rust}`, ty: f.ty };
|
|
@@ -56,7 +60,7 @@ export function fieldVal(comp, base, ty, js, node) {
|
|
|
56
60
|
if (of.k === "str")
|
|
57
61
|
return { code: `${place}.as_deref().unwrap_or(${f.dflt})`, ty: STR };
|
|
58
62
|
if (of.k === "list")
|
|
59
|
-
return { code: `${place}.as_deref().unwrap_or(${f.dflt})`, ty: of };
|
|
63
|
+
return { code: `${place}.as_deref().unwrap_or(${f.dflt})`, ty: of, slice: true };
|
|
60
64
|
return { code: `${place}.unwrap_or(${f.dflt})`, ty: of };
|
|
61
65
|
}
|
|
62
66
|
switch (f.ty.k) {
|
|
@@ -120,6 +124,52 @@ function slotsObject(s, n) {
|
|
|
120
124
|
return "$slots";
|
|
121
125
|
return (owner === "$setup" || owner === "_ctx") && s.slotsBindings.has(name) ? name : null;
|
|
122
126
|
}
|
|
127
|
+
/** `n` with the type `t` given to each empty array literal it may evaluate to, as an `as T[]` on it. */
|
|
128
|
+
export function typedBy(n, t) {
|
|
129
|
+
switch (n.type) {
|
|
130
|
+
case "ArrayExpression":
|
|
131
|
+
return n.elements.length === 0 ? { ...n, type: "TSAsExpression", expression: n, typeAnnotation: t, fvGiven: true } : n;
|
|
132
|
+
case "ParenthesizedExpression":
|
|
133
|
+
return { ...n, expression: typedBy(n.expression, t) };
|
|
134
|
+
case "ConditionalExpression":
|
|
135
|
+
return { ...n, consequent: typedBy(n.consequent, t), alternate: typedBy(n.alternate, t) };
|
|
136
|
+
case "LogicalExpression":
|
|
137
|
+
return n.operator === "&&" ? n : { ...n, right: typedBy(n.right, t) };
|
|
138
|
+
default:
|
|
139
|
+
return n;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function stated(s, n) {
|
|
143
|
+
const comp = s.comp;
|
|
144
|
+
const keyword = n.type === "TSAsExpression" ? "as" : "satisfies";
|
|
145
|
+
const t = n.typeAnnotation;
|
|
146
|
+
if (t.type === "TSTypeReference" && t.typeName.type === "Identifier" && t.typeName.name === "const" && !t.typeParameters)
|
|
147
|
+
return expr(s, n.expression);
|
|
148
|
+
let want;
|
|
149
|
+
try {
|
|
150
|
+
want = tyOfTs(comp, t, comp.structs);
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
if (!(e instanceof GenError) || !n.fvGiven)
|
|
154
|
+
throw e;
|
|
155
|
+
return expr(s, n.expression);
|
|
156
|
+
}
|
|
157
|
+
const list = want.k === "opt" ? want.of : want;
|
|
158
|
+
let inner = n.expression;
|
|
159
|
+
while (inner.type === "ParenthesizedExpression")
|
|
160
|
+
inner = inner.expression;
|
|
161
|
+
if (inner.type === "ArrayExpression" && inner.elements.length === 0) {
|
|
162
|
+
if (list.k === "list")
|
|
163
|
+
return typedEmpty(comp, list.of);
|
|
164
|
+
if (n.fvGiven)
|
|
165
|
+
return expr(s, n.expression);
|
|
166
|
+
return fail(comp, "FV0628", `\`${keyword}\` gives an empty array literal ${describeTy(want)}, which it is not`, n);
|
|
167
|
+
}
|
|
168
|
+
const v = expr(s, typedBy(n.expression, t));
|
|
169
|
+
if (sameTy(v.ty, want) || (isNumber(v.ty) && isNumber(want)) || (want.k === "opt" && sameTy(v.ty, want.of)))
|
|
170
|
+
return v;
|
|
171
|
+
return fail(comp, "FV0628", `\`${keyword}\` states ${describeTy(want)} of ${describeTy(v.ty)}, which the server keeps as it is: write the value with the type it has`, n);
|
|
172
|
+
}
|
|
123
173
|
export function expr(s, n) {
|
|
124
174
|
const comp = s.comp;
|
|
125
175
|
const path = pathOf(n);
|
|
@@ -219,7 +269,9 @@ export function expr(s, n) {
|
|
|
219
269
|
return { code: "None", ty: UNDEF };
|
|
220
270
|
if (base.ty.k !== "opt")
|
|
221
271
|
return fieldVal(comp, base.code, base.ty, n.property.name, n);
|
|
222
|
-
const
|
|
272
|
+
const field = fieldVal(comp, "v", base.ty.of, n.property.name, n);
|
|
273
|
+
const place = ["list", "record", "struct"].includes(field.ty.k) && /^v(?:\.[\w#]+)+$/.test(field.code);
|
|
274
|
+
const f = place ? { ...field, code: `&${field.code}` } : field;
|
|
223
275
|
if (f.ty.k === "opt")
|
|
224
276
|
return { code: `${atom(base.code)}.and_then(|v| ${f.code})`, ty: opt(f.ty) };
|
|
225
277
|
return { code: `${atom(base.code)}.map(|v| ${f.code})`, ty: opt(f.ty) };
|
|
@@ -325,6 +377,9 @@ export function expr(s, n) {
|
|
|
325
377
|
return { ...a, ty: withAbsence(a.ty.of, absence(b.ty)) };
|
|
326
378
|
if (b.iter !== undefined)
|
|
327
379
|
fail(comp, "FV0615", "`??` falling back to a computed list", n);
|
|
380
|
+
if (a.ty.of.k === "list" && b.ty.k === "list" && (untypedEmpty(b) || (sameTy(a.ty.of, b.ty) && !b.code.startsWith("[")))) {
|
|
381
|
+
return { code: `${atom(a.code)}.map(|v| &v[..]).unwrap_or(${untypedEmpty(b) ? "&[]" : sliced(b)})`, ty: a.ty.of, slice: true, ...loneOf(a, b) };
|
|
382
|
+
}
|
|
328
383
|
if (a.held !== undefined && b.ty.k === "str")
|
|
329
384
|
return { code: `&*${atom(a.held)}.unwrap_or(${asCow(b)})`, ty: STR, ...loneOf(a, b) };
|
|
330
385
|
if (a.ty.of.k === "str" && b.ty.k === "str" && isTemporary(b)) {
|
|
@@ -451,14 +506,31 @@ export function expr(s, n) {
|
|
|
451
506
|
case "ConditionalExpression": {
|
|
452
507
|
const t = expr(s, n.test);
|
|
453
508
|
const k = known(t);
|
|
454
|
-
if (k !== undefined)
|
|
455
|
-
|
|
509
|
+
if (k !== undefined) {
|
|
510
|
+
const chosen = expr(s, k ? n.consequent : n.alternate);
|
|
511
|
+
if (!untypedEmpty(chosen))
|
|
512
|
+
return chosen;
|
|
513
|
+
try {
|
|
514
|
+
const other = expr(s, k ? n.alternate : n.consequent);
|
|
515
|
+
return other.ty.k === "list" && other.ty.of.k !== "undef" ? typedEmpty(comp, other.ty.of) : chosen;
|
|
516
|
+
}
|
|
517
|
+
catch (e) {
|
|
518
|
+
if (!(e instanceof GenError))
|
|
519
|
+
throw e;
|
|
520
|
+
return chosen;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
456
523
|
const p = presence(s, n.test);
|
|
457
524
|
const name = p ? `n${++ctx.narrowCount}` : "";
|
|
458
|
-
|
|
459
|
-
|
|
525
|
+
let a = expr(p && !p.negated ? narrowTo(s, p, name) : s, n.consequent);
|
|
526
|
+
let b = expr(p && p.negated ? narrowTo(s, p, name) : s, n.alternate);
|
|
460
527
|
if (sameTy(a.ty, b.ty) && a.code === b.code && (!name || !occurrences(a.code, name)))
|
|
461
528
|
return a;
|
|
529
|
+
const [emptyA, emptyB] = [untypedEmpty(a), untypedEmpty(b)];
|
|
530
|
+
if (emptyA && b.ty.k === "list")
|
|
531
|
+
a = { ...a, ty: b.ty };
|
|
532
|
+
if (emptyB && a.ty.k === "list")
|
|
533
|
+
b = { ...b, ty: a.ty };
|
|
462
534
|
const second = (code) => (a.ty.k === "str" ? strArg(code) : bare(code));
|
|
463
535
|
const choose = (yes, no) => {
|
|
464
536
|
if (!p)
|
|
@@ -478,9 +550,13 @@ export function expr(s, n) {
|
|
|
478
550
|
if (sameTy(a.ty, b.ty) && a.ty.k === "str" && (isTemporary(a) || isTemporary(b))) {
|
|
479
551
|
return { code: `&*(${choose(asCow(a), asCow(b))})`, ty: STR, ...lone };
|
|
480
552
|
}
|
|
481
|
-
if (sameTy(a.ty, b.ty) && a.ty.k === "list" &&
|
|
482
|
-
const
|
|
483
|
-
|
|
553
|
+
if (sameTy(a.ty, b.ty) && a.ty.k === "list" && a.ty.of.k !== "undef") {
|
|
554
|
+
const owned = (v, empty) => !empty && (v.iter !== undefined || v.code.startsWith("["));
|
|
555
|
+
if (owned(a, emptyA) || owned(b, emptyB)) {
|
|
556
|
+
const both = `(${choose(emptyA ? "Vec::new()" : collected(a), emptyB ? "Vec::new()" : collected(b))})`;
|
|
557
|
+
return { code: both, ty: a.ty, iter: `${both}.into_iter()`, ...lone };
|
|
558
|
+
}
|
|
559
|
+
return { code: choose(emptyA ? "&[]" : sliced(a), emptyB ? "&[]" : sliced(b)), ty: a.ty, slice: true, ...lone };
|
|
484
560
|
}
|
|
485
561
|
const optStr = (v) => nothing(v.ty) || (v.ty.k === "opt" && v.ty.of.k === "str") || v.ty.k === "str";
|
|
486
562
|
if (sameTy(a.ty, b.ty) && a.ty.k !== "opt")
|
|
@@ -503,6 +579,9 @@ export function expr(s, n) {
|
|
|
503
579
|
}
|
|
504
580
|
return fail(comp, "FV0623", "the two branches of `?:` differ in type", n);
|
|
505
581
|
}
|
|
582
|
+
case "TSAsExpression":
|
|
583
|
+
case "TSSatisfiesExpression":
|
|
584
|
+
return stated(s, n);
|
|
506
585
|
case "NewExpression": {
|
|
507
586
|
const made = n.callee.type === "Identifier" ? n.callee.name : n.callee.type === "MemberExpression" && !n.callee.computed ? `${n.callee.object.name ?? "…"}.${n.callee.property.name}` : "…";
|
|
508
587
|
return fail(comp, "FV0624", `\`new ${made}(…)\` builds an object the server has no twin for: compute the value in Rust and pass it as a prop`, n);
|
package/dist/fallthrough.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Component, type N } from "./model.ts";
|
|
2
2
|
export declare function isDollarAttrs(n: N, bindings: Set<string>): boolean;
|
|
3
3
|
export declare function passedKey(child: Component, key: string): boolean;
|
|
4
|
+
export declare function isListener(key: string): boolean;
|
|
4
5
|
export declare function attrsFlow(read: {
|
|
5
6
|
comp: Component;
|
|
6
7
|
ssr: string;
|
package/dist/fallthrough.js
CHANGED
|
@@ -9,10 +9,15 @@ export function isDollarAttrs(n, bindings) {
|
|
|
9
9
|
return n.object.name === "$setup" && prop !== undefined && bindings.has(prop);
|
|
10
10
|
}
|
|
11
11
|
export function passedKey(child, key) {
|
|
12
|
-
if (["", "key", "ref", "ref_for", "ref_key", "innerHTML", "textContent"].includes(key)
|
|
12
|
+
if (["", "key", "ref", "ref_for", "ref_key", "innerHTML", "textContent"].includes(key))
|
|
13
13
|
return false;
|
|
14
|
+
if (isListener(key))
|
|
15
|
+
return declares(child, key);
|
|
14
16
|
return !(key.endsWith("Modifiers") && declares(child, key === "modelModifiers" ? "modelValue" : key.slice(0, -"Modifiers".length)));
|
|
15
17
|
}
|
|
18
|
+
export function isListener(key) {
|
|
19
|
+
return /^on[^a-z]/.test(key);
|
|
20
|
+
}
|
|
16
21
|
export function attrsFlow(read) {
|
|
17
22
|
const byName = new Map(read.map((r) => [r.comp.name, r.comp]));
|
|
18
23
|
const flows = read.map((r) => {
|
|
@@ -41,7 +46,10 @@ export function attrsFlow(read) {
|
|
|
41
46
|
if (child) {
|
|
42
47
|
calls.push({
|
|
43
48
|
child,
|
|
44
|
-
keys:
|
|
49
|
+
keys: [
|
|
50
|
+
...parts.filter((p) => p.type === "ObjectExpression").flatMap((p) => p.properties.filter((q) => q.type === "ObjectProperty" && !q.computed).map((q) => q.key.name ?? String(q.key.value))),
|
|
51
|
+
...(parts.some((p) => p.type === "Identifier" && p.name === "_cssVars") ? ["style"] : []),
|
|
52
|
+
],
|
|
45
53
|
passesAttrs: parts.some((p) => p.type === "Identifier" && p.name === "_attrs"),
|
|
46
54
|
passesDollar: parts.some((p) => isDollarAttrs(p, r.attrsBindings)),
|
|
47
55
|
});
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Code } from "./errors.ts";
|
|
2
|
+
import { type Component, type N } from "./model.ts";
|
|
3
|
+
/** The entries of a directory the configuration names as `key`, refusing one that cannot be read. */
|
|
4
|
+
export declare function listDir(root: string, dir: string, key: string): string[];
|
|
5
|
+
/** A JSON file of the project's, parsed; one that cannot be read is refused with `missing`, and one
|
|
6
|
+
* that does not parse with `invalid`. */
|
|
7
|
+
export declare function readJsonFile(root: string, file: string, codes: {
|
|
8
|
+
missing?: Code;
|
|
9
|
+
invalid: Code;
|
|
10
|
+
}): unknown;
|
|
11
|
+
/** The statements of a TypeScript file of the project's, whose text `home.source` holds, refusing
|
|
12
|
+
* one that does not parse with where the parser stopped, as a component's script is. */
|
|
13
|
+
export declare function parseTs(home: Component): N[];
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { parse as parseJs } from "@babel/parser";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fail, failIn, GenError } from "./model.js";
|
|
5
|
+
function why(e) {
|
|
6
|
+
const code = e.code;
|
|
7
|
+
return code === "ENOENT" ? "it does not exist" : code === "ENOTDIR" ? "it is not a directory" : code === "EISDIR" ? "it is a directory" : e.message;
|
|
8
|
+
}
|
|
9
|
+
/** The entries of a directory the configuration names as `key`, refusing one that cannot be read. */
|
|
10
|
+
export function listDir(root, dir, key) {
|
|
11
|
+
try {
|
|
12
|
+
return readdirSync(join(root, dir));
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
throw new GenError("FV1116", `cannot read the \`${key}\` directory \`${dir}\`: ${why(e)}`, { file: dir });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** A JSON file of the project's, parsed; one that cannot be read is refused with `missing`, and one
|
|
19
|
+
* that does not parse with `invalid`. */
|
|
20
|
+
export function readJsonFile(root, file, codes) {
|
|
21
|
+
let text;
|
|
22
|
+
try {
|
|
23
|
+
text = readFileSync(join(root, file), "utf8");
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (!codes.missing)
|
|
27
|
+
throw e;
|
|
28
|
+
return failIn(file, codes.missing, `cannot read the file: ${why(e)}`);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return failIn(file, codes.invalid, e.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The statements of a TypeScript file of the project's, whose text `home.source` holds, refusing
|
|
38
|
+
* one that does not parse with where the parser stopped, as a component's script is. */
|
|
39
|
+
export function parseTs(home) {
|
|
40
|
+
try {
|
|
41
|
+
return parseJs(home.source ?? "", { sourceType: "module", plugins: ["typescript"] }).program.body;
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (!(e instanceof SyntaxError))
|
|
45
|
+
throw e;
|
|
46
|
+
const at = e.loc;
|
|
47
|
+
return fail(home, "FV0001", e.message.replace(/ \(\d+:\d+\)$/, ""), at ? { type: "ParseError", loc: { start: at }, __fv: "source" } : undefined);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/fixture.d.ts
CHANGED
|
@@ -36,13 +36,13 @@ export interface RouterOptions {
|
|
|
36
36
|
/** The application's own `pinia` module, used in place of the one `ferrovue/testing` would
|
|
37
37
|
* import: under vitest the application's stores use the module Vite loads, which must be the
|
|
38
38
|
* one that installs the fixture's state. */
|
|
39
|
-
pinia?: typeof import("pinia");
|
|
39
|
+
pinia?: typeof import("pinia") | undefined;
|
|
40
40
|
/** The application's own `vue-router` module, used in place of the one `ferrovue/testing`
|
|
41
41
|
* would import. */
|
|
42
|
-
vueRouter?: typeof import("vue-router");
|
|
42
|
+
vueRouter?: typeof import("vue-router") | undefined;
|
|
43
43
|
/** The application's own `vue-i18n` module, used in place of the one `ferrovue/testing` would
|
|
44
44
|
* import. */
|
|
45
|
-
vueI18n?: typeof import("vue-i18n");
|
|
45
|
+
vueI18n?: typeof import("vue-i18n") | undefined;
|
|
46
46
|
}
|
|
47
47
|
/** An app rendering one fixture of `component`: with a router over `routes` when there are any. */
|
|
48
48
|
export declare function fixtureApp(component: Component, fixture: Fixture, routes: RouteEntry[] | null, options?: RouterOptions): Promise<App>;
|
package/dist/hydration.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { App, Component } from "vue";
|
|
2
|
-
import { type IslandComponent, type PageOptions, type PageRecord } from "./client.ts";
|
|
2
|
+
import { type IslandComponent, type PageOptions, type PageRecord, type RenderPageOptions } from "./client.ts";
|
|
3
3
|
/** A page as the server wrote it: `html` holds the container and, unless `record` is given, the
|
|
4
4
|
* record's script. */
|
|
5
5
|
export interface RecordedPage {
|
|
@@ -13,3 +13,14 @@ export interface RecordedPage {
|
|
|
13
13
|
* such as a `<ClientOnly>` showing its content or an async component loading, is the app's to
|
|
14
14
|
* change. Resolves to the app, mounted and settled. */
|
|
15
15
|
export declare function hydrateRecordedPage(page: RecordedPage, layout: Component, components: Record<string, IslandComponent>, options?: PageOptions): Promise<App>;
|
|
16
|
+
/** Put a recorded page into the document's body and show it again from its record with
|
|
17
|
+
* `renderPage`, as a navigation shows the next page, throwing where the client's render differs
|
|
18
|
+
* from the markup the browser parsed, as `renderedShape` compares them (comments and empty text
|
|
19
|
+
* aside, classes, styles and form controls by what they hold), and on anything Vue warns or logs as
|
|
20
|
+
* an error. The render is compared as mounting leaves it, where `<ClientOnly>` still shows its
|
|
21
|
+
* fallback as the server wrote it, and, where that differs while async components are loading,
|
|
22
|
+
* once they have loaded. What is teleported out of the page is not compared. Resolves to the app,
|
|
23
|
+
* mounted. */
|
|
24
|
+
export declare function renderRecordedPage(page: RecordedPage, layout: Component, components: Record<string, IslandComponent>, options?: RenderPageOptions & {
|
|
25
|
+
record?: string;
|
|
26
|
+
}): Promise<App>;
|
package/dist/hydration.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { mountPage } from "./client.js";
|
|
1
|
+
import { mountPage, readPage, renderPage } from "./client.js";
|
|
2
|
+
import { renderDifference, renderedShape, shapeAtMount } from "./client-render.js";
|
|
2
3
|
import { settled } from "./settle.js";
|
|
3
4
|
function recordScript(id, record) {
|
|
4
5
|
const json = JSON.stringify(record).replace(/[<>&\u2028\u2029]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
@@ -78,3 +79,48 @@ export async function hydrateRecordedPage(page, layout, components, options = {}
|
|
|
78
79
|
}
|
|
79
80
|
return app;
|
|
80
81
|
}
|
|
82
|
+
/** Put a recorded page into the document's body and show it again from its record with
|
|
83
|
+
* `renderPage`, as a navigation shows the next page, throwing where the client's render differs
|
|
84
|
+
* from the markup the browser parsed, as `renderedShape` compares them (comments and empty text
|
|
85
|
+
* aside, classes, styles and form controls by what they hold), and on anything Vue warns or logs as
|
|
86
|
+
* an error. The render is compared as mounting leaves it, where `<ClientOnly>` still shows its
|
|
87
|
+
* fallback as the server wrote it, and, where that differs while async components are loading,
|
|
88
|
+
* once they have loaded. What is teleported out of the page is not compared. Resolves to the app,
|
|
89
|
+
* mounted. */
|
|
90
|
+
export async function renderRecordedPage(page, layout, components, options = {}) {
|
|
91
|
+
const doc = options.doc ?? document;
|
|
92
|
+
const id = options.record ?? "__fv_page";
|
|
93
|
+
doc.body.innerHTML = page.html + (page.record ? recordScript(id, page.record) : "");
|
|
94
|
+
const record = page.record ?? readPage(page.html, { record: id }).record;
|
|
95
|
+
const selector = options.container ?? "#app";
|
|
96
|
+
const container = typeof selector === "string" ? doc.querySelector(selector) : selector;
|
|
97
|
+
if (!container)
|
|
98
|
+
throw new Error(`the recorded page has no ${selector}`);
|
|
99
|
+
const expected = renderedShape(container);
|
|
100
|
+
const problems = [];
|
|
101
|
+
const watch = {
|
|
102
|
+
install(app) {
|
|
103
|
+
app.config.warnHandler = (message) => void problems.push(`warning: ${message}`);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const mounted = shapeAtMount();
|
|
107
|
+
const { warn, error } = console;
|
|
108
|
+
console.warn = (...args) => void problems.push(`console.warn: ${args.map(String).join(" ")}`);
|
|
109
|
+
console.error = (...args) => void problems.push(`console.error: ${args.map(String).join(" ")}`);
|
|
110
|
+
let app;
|
|
111
|
+
try {
|
|
112
|
+
app = await renderPage(layout, components, record, { ...options, doc, container, plugins: [...(options.plugins ?? []), watch, mounted.plugin] });
|
|
113
|
+
const difference = await renderDifference(app, mounted.container() ?? container, mounted.shape() ?? "", expected);
|
|
114
|
+
if (difference)
|
|
115
|
+
problems.push(difference);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
console.warn = warn;
|
|
119
|
+
console.error = error;
|
|
120
|
+
}
|
|
121
|
+
if (problems.length) {
|
|
122
|
+
app.unmount();
|
|
123
|
+
throw new Error(`the page did not render from its record exactly:\n${problems.join("\n")}`);
|
|
124
|
+
}
|
|
125
|
+
return app;
|
|
126
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ClientOnly, hydrateState, mountIslands, type IslandComponent, type Islands, type MountOptions } from "./client.ts";
|
|
2
|
-
export { createPageApp, mountPage, type PageOptions, type PagePart, type PageRecord } from "./client.ts";
|
|
1
|
+
export { ClientOnly, hydrateState, mountIslands, type IslandComponent, type Islands, type MountOptions, type StateText } from "./client.ts";
|
|
2
|
+
export { createPageApp, mountPage, readPage, renderPage, type PageAppOptions, type PageOptions, type PagePart, type PageRecord, type ReadPage, type ReadPageOptions, type RenderPageOptions, } from "./client.ts";
|
|
3
3
|
export type { Float, InlineHtml, TrustedHtml } from "./types.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { ClientOnly, hydrateState, mountIslands } from "./client.js";
|
|
2
|
-
export { createPageApp, mountPage } from "./client.js";
|
|
2
|
+
export { createPageApp, mountPage, readPage, renderPage, } from "./client.js";
|