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/styles.js
CHANGED
|
@@ -1,10 +1,133 @@
|
|
|
1
1
|
import { escapeHtml, hyphenate, parseStringStyle } from "@vue/shared";
|
|
2
2
|
import { fail, nothing, rustStr } from "./model.js";
|
|
3
|
-
import { expr } from "./expr.js";
|
|
4
|
-
import { cond } from "./narrowing.js";
|
|
5
|
-
import { condition, logical, not, strArg } from "./parens.js";
|
|
3
|
+
import { describeTy, expr } from "./expr.js";
|
|
4
|
+
import { cond, known } from "./narrowing.js";
|
|
5
|
+
import { condition, logical, not, receiver, strArg } from "./parens.js";
|
|
6
6
|
import { Emitter } from "./emitter.js";
|
|
7
7
|
import { display, valueAttr } from "./attrs.js";
|
|
8
|
+
import { isTemporary } from "./strings.js";
|
|
9
|
+
/** Put the object Vue's server compiler declares as `_cssVars`, the `v-bind()` variables in
|
|
10
|
+
* `<style>`, in place of each use of it in the compiled template's statements, with each value
|
|
11
|
+
* marked to be written as `ssrRenderStyle` writes a variable. */
|
|
12
|
+
export function inlineCssVars(comp, body) {
|
|
13
|
+
const at = body.findIndex((st) => st.type === "VariableDeclaration" && st.declarations[0]?.id?.name === "_cssVars");
|
|
14
|
+
if (at < 0)
|
|
15
|
+
return body;
|
|
16
|
+
const vars = body[at].declarations[0].init.properties[0].value.properties;
|
|
17
|
+
const place = (n, where) => {
|
|
18
|
+
if (Array.isArray(n))
|
|
19
|
+
return n.map((x) => place(x, where));
|
|
20
|
+
if (!n || typeof n !== "object")
|
|
21
|
+
return n;
|
|
22
|
+
const copy = {};
|
|
23
|
+
for (const [k, v] of Object.entries(n))
|
|
24
|
+
copy[k] = k === "loc" ? where.loc : place(v, where);
|
|
25
|
+
if (typeof n.type === "string")
|
|
26
|
+
copy.__fv = "source";
|
|
27
|
+
return copy;
|
|
28
|
+
};
|
|
29
|
+
const object = () => ({
|
|
30
|
+
type: "ObjectExpression",
|
|
31
|
+
properties: [{
|
|
32
|
+
type: "ObjectProperty",
|
|
33
|
+
computed: false,
|
|
34
|
+
key: { type: "Identifier", name: "style" },
|
|
35
|
+
value: {
|
|
36
|
+
type: "ObjectExpression",
|
|
37
|
+
properties: vars.map((p, i) => {
|
|
38
|
+
const where = comp.cssVarsAt?.[i] ?? p;
|
|
39
|
+
return {
|
|
40
|
+
type: "ObjectProperty",
|
|
41
|
+
computed: false,
|
|
42
|
+
key: { type: "StringLiteral", value: String(p.key.value).replace(/^:/, "") },
|
|
43
|
+
value: { type: "FvCssVar", value: place(p.value, where), loc: where.loc, __fv: where.__fv },
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
}],
|
|
48
|
+
});
|
|
49
|
+
const swap = (n) => {
|
|
50
|
+
if (!n || typeof n !== "object")
|
|
51
|
+
return;
|
|
52
|
+
for (const [k, v] of Object.entries(n)) {
|
|
53
|
+
if (k === "loc")
|
|
54
|
+
continue;
|
|
55
|
+
if (Array.isArray(v)) {
|
|
56
|
+
v.forEach((x, i) => {
|
|
57
|
+
if (x?.type === "Identifier" && x.name === "_cssVars")
|
|
58
|
+
v[i] = object();
|
|
59
|
+
else
|
|
60
|
+
swap(x);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (v && typeof v === "object") {
|
|
64
|
+
if (v.type === "Identifier" && v.name === "_cssVars")
|
|
65
|
+
n[k] = object();
|
|
66
|
+
else
|
|
67
|
+
swap(v);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const rest = body.filter((_, i) => i !== at);
|
|
72
|
+
swap(rest);
|
|
73
|
+
return rest;
|
|
74
|
+
}
|
|
75
|
+
function cssVarOf(s, n) {
|
|
76
|
+
const v = expr(s, n.value);
|
|
77
|
+
const of = v.ty.k === "opt" ? v.ty.of : v.ty;
|
|
78
|
+
if (!nothing(v.ty) && of.k !== "str" && of.k !== "int" && of.k !== "float") {
|
|
79
|
+
fail(s.comp, "FV1006", `\`v-bind()\` in \`<style>\` of ${describeTy(of)}: Vue writes a string or a number, and warns of anything else`, n);
|
|
80
|
+
}
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
function writeCssVar(s, e, css, n) {
|
|
84
|
+
const one = (v) => {
|
|
85
|
+
if (nothing(v.ty)) {
|
|
86
|
+
e.lit(`${escapeHtml(css)}:initial;`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (v.ty.k === "opt") {
|
|
90
|
+
e.open(`if let Some(v) = ${v.code}`);
|
|
91
|
+
one({ code: "v", ty: v.ty.of });
|
|
92
|
+
e.close(" else {");
|
|
93
|
+
e.lit(`${escapeHtml(css)}:initial;`);
|
|
94
|
+
e.close();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
e.lit(`${escapeHtml(css)}:`);
|
|
98
|
+
const k = v.ty.k === "str" ? known(v) : true;
|
|
99
|
+
if (k === false)
|
|
100
|
+
e.lit(" ");
|
|
101
|
+
else if (k === undefined && isTemporary(v))
|
|
102
|
+
e.stmt(`match AsRef::<str>::as_ref(${strArg(v.code)}) { "" => out.push(' '), v => fv::escape_into(out, v) }`);
|
|
103
|
+
else if (k === undefined) {
|
|
104
|
+
e.open(`if ${receiver(v.code)}.is_empty()`);
|
|
105
|
+
e.lit(" ");
|
|
106
|
+
e.close(" else {");
|
|
107
|
+
display(e, v);
|
|
108
|
+
e.close();
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
display(e, v);
|
|
112
|
+
e.lit(";");
|
|
113
|
+
};
|
|
114
|
+
one(cssVarOf(s, n));
|
|
115
|
+
}
|
|
116
|
+
function cssVarAttr(s, n) {
|
|
117
|
+
const one = (v) => {
|
|
118
|
+
if (nothing(v.ty))
|
|
119
|
+
return 'fv::Attr::str("initial")';
|
|
120
|
+
if (v.ty.k === "opt")
|
|
121
|
+
return `match ${v.code} { Some(v) => ${one({ code: "v", ty: v.ty.of })}, None => fv::Attr::str("initial") }`;
|
|
122
|
+
const k = v.ty.k === "str" ? known(v) : true;
|
|
123
|
+
if (k === false)
|
|
124
|
+
return 'fv::Attr::str(" ")';
|
|
125
|
+
if (k === undefined)
|
|
126
|
+
return `if ${receiver(v.code)}.is_empty() { fv::Attr::str(" ") } else { ${valueAttr(s, v, n)} }`;
|
|
127
|
+
return valueAttr(s, v, n);
|
|
128
|
+
};
|
|
129
|
+
return one(cssVarOf(s, n));
|
|
130
|
+
}
|
|
8
131
|
export function mergedStyle(s, e, n) {
|
|
9
132
|
if (n.type === "StringLiteral") {
|
|
10
133
|
renderStyle(s, e, { type: "ArrayExpression", elements: [n] });
|
|
@@ -38,7 +161,7 @@ export function styleAttr(s, n) {
|
|
|
38
161
|
const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
|
|
39
162
|
if (/^\d+$/.test(key) || key.startsWith(":"))
|
|
40
163
|
fail(s.comp, "FV1009", `style property \`${key}\``, p);
|
|
41
|
-
return `(${rustStr(key)}, ${valueAttr(s, expr(s, p.value), p.value)})`;
|
|
164
|
+
return `(${rustStr(key)}, ${p.value.type === "FvCssVar" ? cssVarAttr(s, p.value) : valueAttr(s, expr(s, p.value), p.value)})`;
|
|
42
165
|
});
|
|
43
166
|
return `fv::Attr::style([${entries.join(", ")}])`;
|
|
44
167
|
}
|
|
@@ -123,6 +246,10 @@ export function renderStyle(s, e, n) {
|
|
|
123
246
|
}
|
|
124
247
|
}
|
|
125
248
|
const write = (css, value) => {
|
|
249
|
+
if (value.type === "FvCssVar") {
|
|
250
|
+
writeCssVar(s, e, css, value);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
126
253
|
if (value.type === "StringLiteral" || value.type === "NumericLiteral") {
|
|
127
254
|
e.lit(escapeHtml(`${css}:${String(value.value)};`));
|
|
128
255
|
return;
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { attachSsrRender } from "./ssr.ts";
|
|
2
2
|
export { conformanceSuite, type ConformanceOptions } from "./conformance.ts";
|
|
3
3
|
export { fixtureApp, readFixture, routeRecords, type Fixture, type RouteEntry, type RouterOptions } from "./fixture.ts";
|
|
4
|
-
export { hydrateRecordedPage, type RecordedPage } from "./hydration.ts";
|
|
4
|
+
export { hydrateRecordedPage, renderRecordedPage, type RecordedPage } from "./hydration.ts";
|
|
5
|
+
export type { IslandComponent, MountOptions, PageOptions, PagePart, PageRecord, RenderPageOptions } from "./client.ts";
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { attachSsrRender } from "./ssr.js";
|
|
2
2
|
export { conformanceSuite } from "./conformance.js";
|
|
3
3
|
export { fixtureApp, readFixture, routeRecords } from "./fixture.js";
|
|
4
|
-
export { hydrateRecordedPage } from "./hydration.js";
|
|
4
|
+
export { hydrateRecordedPage, renderRecordedPage } from "./hydration.js";
|
package/dist/typescript.d.ts
CHANGED
|
@@ -3,8 +3,18 @@ export declare function typesImports(comp: Component, body: N[]): void;
|
|
|
3
3
|
export declare const ONE_NOTHING = "its Rust type is an `Option`, whose `None` cannot be both, as `=== null` and the props sent to the client would need";
|
|
4
4
|
export declare function tyOfTs(comp: Component, t: N, structs: Map<string, Struct>, seen?: Set<string>): Ty;
|
|
5
5
|
export declare function declareTypes(comp: Component, body: N[], structs: Map<string, Struct>, aliases: Map<string, N>): N[];
|
|
6
|
+
export declare function refusePrelude(comp: Component, d: N): void;
|
|
6
7
|
export declare function resolveImport(fromFile: string, spec: string): string | null;
|
|
7
8
|
export declare function readTypeFile(file: string): void;
|
|
9
|
+
/** Translate a type a `.ts` file declares, the first time something the server renders reaches it. */
|
|
10
|
+
export declare function reachType(name: string): void;
|
|
11
|
+
/** Translate an interface a component declares, the first time something the server renders reaches it. */
|
|
12
|
+
export declare function reachLocal(comp: Component, name: string): void;
|
|
13
|
+
/** Refuse a type whose every field a value of it must carry, as props and store state do, where one
|
|
14
|
+
* of its fields, or of the types it holds, has no Rust type. */
|
|
15
|
+
export declare function requireWhole(comp: Component, ty: Ty, seen?: Set<Struct>): void;
|
|
16
|
+
/** Whether `types.rs` holds the type: one built from a constant list, or a declared one something reached. */
|
|
17
|
+
export declare function typeWritten(name: string): boolean;
|
|
8
18
|
export declare function markHome(ty: Ty, home: string): Ty;
|
|
9
19
|
export declare function lookupStruct(comp: Component, ty: Ty & {
|
|
10
20
|
k: "struct";
|
|
@@ -13,7 +23,7 @@ export declare function lookupStruct(comp: Component, ty: Ty & {
|
|
|
13
23
|
owner: Component;
|
|
14
24
|
path: string;
|
|
15
25
|
};
|
|
16
|
-
export declare function structOf(comp: Component, name: string, members: N[], structs: Map<string, Struct
|
|
26
|
+
export declare function structOf(comp: Component, name: string, members: N[], structs: Map<string, Struct>, lenient?: boolean): Struct;
|
|
17
27
|
export declare function definePropsType(call: N): N | null;
|
|
18
28
|
export declare function runtimeDefaults(comp: Component, content: string): Map<string, N>;
|
|
19
29
|
export declare function defaultValue(comp: Component, f: Field, node: N, written?: N): string;
|
package/dist/typescript.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { parse as parseJs } from "@babel/parser";
|
|
2
2
|
import { readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, join,
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { relativePath } from "./paths.js";
|
|
4
5
|
import { absence, blankComponent, BOOL, fail, FLOAT, GenError, INT, joinAbsence, opt, RUST_PRELUDE, rustStr, sameTy, snake, STR, tagAst, withAbsence } from "./model.js";
|
|
5
6
|
import { CONFIG_FILE, ctx, INLINE_HTML, TYPES_MODULE } from "./context.js";
|
|
6
7
|
import { childOf } from "./expr.js";
|
|
7
8
|
import { claim } from "./plugin.js";
|
|
8
9
|
import { declareConsts, enumType } from "./constants.js";
|
|
10
|
+
import { parseTs } from "./files.js";
|
|
9
11
|
export function typesImports(comp, body) {
|
|
10
12
|
for (const s of body) {
|
|
11
13
|
if (s.type !== "ImportDeclaration" || s.source.value !== TYPES_MODULE)
|
|
@@ -92,8 +94,13 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
92
94
|
fail(comp, "FV0313", "a `Record` is keyed by `string`", key);
|
|
93
95
|
return recordOf(comp, value, structs, seen);
|
|
94
96
|
}
|
|
95
|
-
if (structs.has(name))
|
|
97
|
+
if (structs.has(name)) {
|
|
98
|
+
if (structs === ctx.typeStructs)
|
|
99
|
+
reachType(name);
|
|
100
|
+
else if (structs === comp.structs)
|
|
101
|
+
reachLocal(comp, name);
|
|
96
102
|
return { k: "struct", name };
|
|
103
|
+
}
|
|
97
104
|
if (comp.childProps.has(name))
|
|
98
105
|
return { k: "child", name: comp.childProps.get(name) };
|
|
99
106
|
if (comp.floatName !== null && name === comp.floatName)
|
|
@@ -107,8 +114,11 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
107
114
|
return ctx.trustedHtml === INLINE_HTML ? { k: "html", inline: true } : { k: "html" };
|
|
108
115
|
}
|
|
109
116
|
const imported = comp.importedTypes.get(name);
|
|
110
|
-
if (imported)
|
|
117
|
+
if (imported) {
|
|
118
|
+
if (imported.k === "struct" && imported.home === "types")
|
|
119
|
+
reachType(imported.name);
|
|
111
120
|
return imported;
|
|
121
|
+
}
|
|
112
122
|
const alias = comp.aliases.get(name);
|
|
113
123
|
if (alias) {
|
|
114
124
|
if (seen.has(name))
|
|
@@ -150,14 +160,15 @@ export function declareTypes(comp, body, structs, aliases) {
|
|
|
150
160
|
}
|
|
151
161
|
}
|
|
152
162
|
}
|
|
153
|
-
for (const d of decls)
|
|
154
|
-
if (RUST_PRELUDE.has(d.name)) {
|
|
155
|
-
fail(comp, "FV0318", `an interface called \`${d.name}\` would hide Rust's own \`${d.name}\` in the generated code; rename it`, d.node);
|
|
156
|
-
}
|
|
163
|
+
for (const d of decls)
|
|
157
164
|
structs.set(d.name, { name: d.name, fields: [] });
|
|
158
|
-
}
|
|
159
165
|
return decls;
|
|
160
166
|
}
|
|
167
|
+
export function refusePrelude(comp, d) {
|
|
168
|
+
if (RUST_PRELUDE.has(d.name)) {
|
|
169
|
+
fail(comp, "FV0318", `an interface called \`${d.name}\` would hide Rust's own \`${d.name}\` in the generated code; rename it`, d.node);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
161
172
|
export function resolveImport(fromFile, spec) {
|
|
162
173
|
if (!spec.startsWith("."))
|
|
163
174
|
return null;
|
|
@@ -176,30 +187,88 @@ export function readTypeFile(file) {
|
|
|
176
187
|
if (ctx.typeRead.has(file))
|
|
177
188
|
return;
|
|
178
189
|
ctx.typeRead.add(file);
|
|
179
|
-
const rel =
|
|
190
|
+
const rel = relativePath(ctx.rootDir, file);
|
|
180
191
|
const home = blankComponent(basename(rel), "types", rel, ctx.typeStructs);
|
|
181
192
|
home.aliases = ctx.typeAliases;
|
|
182
193
|
home.source = readFileSync(file, "utf8");
|
|
183
|
-
const body =
|
|
194
|
+
const body = parseTs(home);
|
|
184
195
|
tagAst(body, "source");
|
|
185
196
|
typesImports(home, body);
|
|
186
197
|
ctx.constDecls.set(file, declareConsts(home, body));
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
for (const d of decls) {
|
|
191
|
-
if (before.has(d.name))
|
|
198
|
+
for (const d of declareTypes(home, body, new Map(), ctx.typeAliases)) {
|
|
199
|
+
const known = ctx.typeDecls.get(d.name);
|
|
200
|
+
if (known === undefined && ctx.typeStructs.has(d.name))
|
|
192
201
|
fail(home, "FV0319", `\`${d.name}\` is declared by ${ctx.typeFiles.get(d.name)} too`, d.node);
|
|
193
|
-
ctx.
|
|
202
|
+
if (known !== undefined && ctx.typeReached.has(d.name))
|
|
203
|
+
fail(home, "FV0319", `\`${d.name}\` is declared by ${known[0].home.file} too`, d.node);
|
|
204
|
+
if (known !== undefined) {
|
|
205
|
+
if (known.at(-1).home.file === rel)
|
|
206
|
+
known.pop();
|
|
207
|
+
known.push({ home, members: d.members, node: d.node });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
ctx.typeDecls.set(d.name, [{ home, members: d.members, node: d.node }]);
|
|
211
|
+
ctx.typeStructs.set(d.name, { name: d.name, fields: [] });
|
|
194
212
|
ctx.typeFiles.set(d.name, rel);
|
|
195
213
|
}
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
}
|
|
215
|
+
/** Translate a type a `.ts` file declares, the first time something the server renders reaches it. */
|
|
216
|
+
export function reachType(name) {
|
|
217
|
+
const decls = ctx.typeDecls.get(name);
|
|
218
|
+
if (decls === undefined || ctx.typeReached.has(name))
|
|
219
|
+
return;
|
|
220
|
+
ctx.typeReached.add(name);
|
|
221
|
+
try {
|
|
222
|
+
const [first, again] = decls;
|
|
223
|
+
if (again !== undefined)
|
|
224
|
+
fail(again.home, "FV0319", `\`${name}\` is declared by ${first.home.file} too`, again.node);
|
|
225
|
+
refusePrelude(first.home, { name, node: first.node });
|
|
226
|
+
const st = structOf(first.home, name, first.members, ctx.typeStructs, true);
|
|
198
227
|
for (const f of st.fields)
|
|
199
228
|
f.ty = markHome(f.ty, "types");
|
|
200
|
-
ctx.typeStructs.set(
|
|
229
|
+
ctx.typeStructs.set(name, st);
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
ctx.typeReached.delete(name);
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** Translate an interface a component declares, the first time something the server renders reaches it. */
|
|
237
|
+
export function reachLocal(comp, name) {
|
|
238
|
+
const decl = comp.typeDecls.get(name);
|
|
239
|
+
if (decl === undefined || comp.reached.has(name))
|
|
240
|
+
return;
|
|
241
|
+
comp.reached.add(name);
|
|
242
|
+
try {
|
|
243
|
+
refusePrelude(comp, { name, node: decl.node });
|
|
244
|
+
comp.structs.set(name, structOf(comp, name, decl.members, comp.structs, true));
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
comp.reached.delete(name);
|
|
248
|
+
throw e;
|
|
201
249
|
}
|
|
202
250
|
}
|
|
251
|
+
/** Refuse a type whose every field a value of it must carry, as props and store state do, where one
|
|
252
|
+
* of its fields, or of the types it holds, has no Rust type. */
|
|
253
|
+
export function requireWhole(comp, ty, seen = new Set()) {
|
|
254
|
+
if (ty.k === "opt" || ty.k === "list" || ty.k === "record")
|
|
255
|
+
return requireWhole(comp, ty.of, seen);
|
|
256
|
+
if (ty.k !== "struct")
|
|
257
|
+
return;
|
|
258
|
+
const { st, owner } = lookupStruct(comp, ty);
|
|
259
|
+
if (st === undefined || seen.has(st))
|
|
260
|
+
return;
|
|
261
|
+
seen.add(st);
|
|
262
|
+
const refused = st.refused?.values().next().value;
|
|
263
|
+
if (refused !== undefined)
|
|
264
|
+
throw refused;
|
|
265
|
+
for (const f of st.fields)
|
|
266
|
+
requireWhole(owner, f.ty, seen);
|
|
267
|
+
}
|
|
268
|
+
/** Whether `types.rs` holds the type: one built from a constant list, or a declared one something reached. */
|
|
269
|
+
export function typeWritten(name) {
|
|
270
|
+
return !ctx.typeDecls.has(name) || ctx.typeReached.has(name);
|
|
271
|
+
}
|
|
203
272
|
export function markHome(ty, home) {
|
|
204
273
|
if (ty.k === "struct" && !claim((p) => p.struct?.(ty)) && !ty.home && ty.name !== "Props")
|
|
205
274
|
return { ...ty, home };
|
|
@@ -211,31 +280,53 @@ export function lookupStruct(comp, ty) {
|
|
|
211
280
|
const own = claim((p) => p.struct?.(ty));
|
|
212
281
|
if (own)
|
|
213
282
|
return { st: own.st, owner: comp, path: comp.module === own.module ? "" : `super::${own.module}::` };
|
|
214
|
-
if (ty.home === "types")
|
|
283
|
+
if (ty.home === "types") {
|
|
284
|
+
reachType(ty.name);
|
|
215
285
|
return { st: ctx.typeStructs.get(ty.name), owner: comp, path: comp.module === "types" ? "" : "super::types::" };
|
|
286
|
+
}
|
|
216
287
|
if (ty.home !== undefined && ty.home !== comp.name) {
|
|
217
288
|
const owner = childOf(ty.home);
|
|
289
|
+
reachLocal(owner, ty.name);
|
|
218
290
|
return { st: owner.structs.get(ty.name), owner, path: `super::${owner.module}::` };
|
|
219
291
|
}
|
|
220
|
-
|
|
292
|
+
if (ty.name === "Props")
|
|
293
|
+
return { st: comp.props, owner: comp, path: "" };
|
|
294
|
+
reachLocal(comp, ty.name);
|
|
295
|
+
return { st: comp.structs.get(ty.name), owner: comp, path: "" };
|
|
221
296
|
}
|
|
222
|
-
export function structOf(comp, name, members, structs) {
|
|
297
|
+
export function structOf(comp, name, members, structs, lenient = false) {
|
|
223
298
|
const fields = [];
|
|
299
|
+
const refused = new Map();
|
|
224
300
|
for (const m of members) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
const base = tyOfTs(comp, m.typeAnnotation.typeAnnotation, structs);
|
|
229
|
-
if (m.optional && absence(base) === "null") {
|
|
230
|
-
fail(comp, "FV0321", `\`${m.key.name}?: T | null\` may be absent, which is \`undefined\`, or \`null\`: ${ONE_NOTHING}; declare it \`${m.key.name}: T | null\` or \`${m.key.name}?: T\``, m);
|
|
301
|
+
try {
|
|
302
|
+
fields.push(fieldOf(comp, name, m, structs));
|
|
231
303
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
304
|
+
catch (e) {
|
|
305
|
+
if (!lenient || !(e instanceof GenError))
|
|
306
|
+
throw e;
|
|
307
|
+
const key = memberKey(m);
|
|
308
|
+
if (!refused.has(key))
|
|
309
|
+
refused.set(key, e);
|
|
235
310
|
}
|
|
236
|
-
fields.push({ js: m.key.name, rust: snake(m.key.name), ty: m.optional ? opt(base) : base });
|
|
237
311
|
}
|
|
238
|
-
return { name, fields };
|
|
312
|
+
return refused.size ? { name, fields, refused } : { name, fields };
|
|
313
|
+
}
|
|
314
|
+
function memberKey(m) {
|
|
315
|
+
if (m.type !== "TSPropertySignature" && m.type !== "TSMethodSignature")
|
|
316
|
+
return "";
|
|
317
|
+
if (m.computed)
|
|
318
|
+
return "";
|
|
319
|
+
return m.key.type === "Identifier" ? m.key.name : m.key.type === "StringLiteral" ? m.key.value : "";
|
|
320
|
+
}
|
|
321
|
+
function fieldOf(comp, name, m, structs) {
|
|
322
|
+
if (m.type !== "TSPropertySignature" || m.key.type !== "Identifier") {
|
|
323
|
+
fail(comp, "FV0320", `\`${name}\` may only hold plain named fields`, m);
|
|
324
|
+
}
|
|
325
|
+
const base = tyOfTs(comp, m.typeAnnotation.typeAnnotation, structs);
|
|
326
|
+
if (m.optional && absence(base) === "null") {
|
|
327
|
+
fail(comp, "FV0321", `\`${m.key.name}?: T | null\` may be absent, which is \`undefined\`, or \`null\`: ${ONE_NOTHING}; declare it \`${m.key.name}: T | null\` or \`${m.key.name}?: T\``, m);
|
|
328
|
+
}
|
|
329
|
+
return { js: m.key.name, rust: snake(m.key.name), ty: m.optional && base.k !== "opt" ? opt(base) : base };
|
|
239
330
|
}
|
|
240
331
|
export function definePropsType(call) {
|
|
241
332
|
if (call?.type === "CallExpression" && call.callee.type === "Identifier" && call.callee.name === "withDefaults") {
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
2
|
import { type FileRoute } from "./file-routes.ts";
|
|
3
|
+
export type { FileRoute } from "./file-routes.ts";
|
|
3
4
|
export interface FerrovueOptions {
|
|
4
|
-
/** The
|
|
5
|
+
/** The project root, which the configuration's paths are relative to: Vite's working directory
|
|
6
|
+
* by default. */
|
|
5
7
|
root?: string;
|
|
8
|
+
/** The configuration file, relative to `root`, as the CLI's `--config` takes it:
|
|
9
|
+
* `ferrovue.config.json` by default. */
|
|
10
|
+
config?: string;
|
|
6
11
|
}
|
|
7
|
-
/** Whether a changed file can change what is generated: a component, a store or type file, the
|
|
8
|
-
* routes or the configuration: anything but the output itself, dependencies and build output. */
|
|
9
|
-
export declare function affects(root: string, file: string): boolean;
|
|
10
12
|
/** How `@vitejs/plugin-vue`, as this build configures it, computes a `<style scoped>` id: from the
|
|
11
|
-
* path alone or the path and source, hashed from Vite's root
|
|
12
|
-
* id generator of its own. */
|
|
13
|
+
* path alone or the path and source, hashed from Vite's root; and whether it compiles for production.
|
|
14
|
+
* `null` without the plugin, or with an id generator of its own. */
|
|
13
15
|
export declare function vueScopeIds(config: ResolvedConfig): {
|
|
14
16
|
mode: "filepath" | "filepath-source";
|
|
15
17
|
root: string;
|
|
18
|
+
production: boolean;
|
|
16
19
|
} | null;
|
|
17
|
-
/** Why the server's scope ids would differ from the client's,
|
|
18
|
-
*
|
|
19
|
-
* and show unstyled. */
|
|
20
|
+
/** Why the server's scope ids, or the CSS variables `v-bind()` sets, would differ from the client's,
|
|
21
|
+
* when a component has `<style scoped>` or `v-bind()` in `<style>` and the configuration,
|
|
22
|
+
* `configFile`, computes them otherwise than plugin-vue: a page would hydrate and show unstyled. */
|
|
20
23
|
export declare function scopeIdMismatch(root: string, vue: {
|
|
21
24
|
mode: string;
|
|
22
25
|
root: string;
|
|
23
|
-
|
|
26
|
+
production?: boolean;
|
|
27
|
+
} | null, configFile?: string): string | null;
|
|
28
|
+
/** Why the class names of the server's CSS modules would differ from those Vite gives the client,
|
|
29
|
+
* when a component has `<style module>` and Vite's `css`, run from `cwd`, names them otherwise than
|
|
30
|
+
* `cssModules` in the configuration, `configFile`. */
|
|
31
|
+
export declare function cssModulesMismatch(root: string, css: ResolvedConfig["css"] | undefined, cwd: string, configFile?: string): string | null;
|
|
24
32
|
/** The `ferrovue/routes` module: the routes vue-router's file-based routing builds from the pages,
|
|
25
33
|
* as vue-router's records with each page loaded lazily (`routes`), and as a routes file lists them
|
|
26
34
|
* (the default export), for `routeRecords` and `linkRouter`. */
|