ferrovue 0.3.0 → 0.4.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 +32 -13
- package/dist/attrs.d.ts +2 -52
- package/dist/attrs.js +18 -448
- package/dist/calls.d.ts +4 -0
- package/dist/calls.js +219 -0
- package/dist/children.d.ts +9 -0
- package/dist/children.js +273 -0
- package/dist/classes.d.ts +13 -0
- package/dist/classes.js +176 -0
- package/dist/cli.js +0 -17
- package/dist/client.js +0 -12
- package/dist/compiler.js +25 -90
- package/dist/component.js +10 -86
- package/dist/context.d.ts +4 -66
- package/dist/context.js +2 -33
- package/dist/diff.d.ts +0 -3
- package/dist/diff.js +0 -9
- package/dist/emitter.d.ts +0 -8
- package/dist/emitter.js +0 -12
- package/dist/expr.d.ts +0 -70
- package/dist/expr.js +27 -890
- package/dist/fallthrough.d.ts +9 -0
- package/dist/fallthrough.js +78 -0
- package/dist/fixture.d.ts +2 -1
- package/dist/fixture.js +37 -22
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/islands.d.ts +1 -0
- package/dist/islands.js +1 -0
- package/dist/lists.d.ts +0 -11
- package/dist/lists.js +4 -39
- package/dist/loops.d.ts +3 -0
- package/dist/loops.js +164 -0
- package/dist/model.d.ts +14 -143
- package/dist/model.js +27 -12
- package/dist/narrowing.d.ts +19 -0
- package/dist/narrowing.js +126 -0
- package/dist/numbers.d.ts +8 -0
- package/dist/numbers.js +73 -0
- package/dist/parens.d.ts +0 -27
- package/dist/parens.js +0 -44
- package/dist/plugin.d.ts +84 -0
- package/dist/plugin.js +26 -0
- package/dist/plugins/i18n.d.ts +23 -0
- package/dist/{i18n.js → plugins/i18n.js} +76 -19
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/router-link.d.ts +8 -0
- package/dist/{router.js → plugins/router-link.js} +19 -61
- package/dist/plugins/router.d.ts +42 -0
- package/dist/plugins/router.js +256 -0
- package/dist/plugins/scoped.d.ts +12 -0
- package/dist/plugins/scoped.js +125 -0
- package/dist/plugins/shared-types.d.ts +2 -0
- package/dist/plugins/shared-types.js +7 -0
- package/dist/plugins/stores.d.ts +37 -0
- package/dist/plugins/stores.js +377 -0
- package/dist/plugins/teleport.d.ts +5 -0
- package/dist/plugins/teleport.js +55 -0
- package/dist/rust.d.ts +1 -7
- package/dist/rust.js +38 -160
- package/dist/script.d.ts +0 -6
- package/dist/script.js +13 -128
- package/dist/slots.d.ts +13 -0
- package/dist/slots.js +160 -0
- package/dist/strings.d.ts +13 -0
- package/dist/strings.js +208 -0
- package/dist/styles.d.ts +14 -0
- package/dist/styles.js +181 -0
- package/dist/template.d.ts +1 -42
- package/dist/template.js +14 -738
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -8
- package/dist/types.d.ts +2 -0
- package/dist/typescript.d.ts +0 -13
- package/dist/typescript.js +8 -41
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +1 -23
- package/package.json +7 -2
- package/dist/i18n.d.ts +0 -27
- package/dist/router.d.ts +0 -17
- package/dist/scoped.d.ts +0 -24
- package/dist/scoped.js +0 -177
- package/dist/stores.d.ts +0 -6
- package/dist/stores.js +0 -229
package/dist/rust.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
/* The Rust source written for each component, and the modules beside them. */
|
|
2
1
|
import { parse as parseJs } from "@babel/parser";
|
|
3
2
|
import { basename } from "node:path";
|
|
4
|
-
import { fail, GenError, rustStr, snake, tagAst, takesAttrs } from "./model.js";
|
|
5
|
-
import {
|
|
3
|
+
import { blankComponent, fail, GenError, rustStr, snake, tagAst, takesAttrs } from "./model.js";
|
|
4
|
+
import { ctx } from "./context.js";
|
|
6
5
|
import { lookupStruct } from "./typescript.js";
|
|
7
6
|
import { childOf } from "./expr.js";
|
|
8
7
|
import { Emitter } from "./emitter.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { statements } from "./template.js";
|
|
9
|
+
import { slotFieldBorrows, slotFieldTy, slotTypeName } from "./slots.js";
|
|
10
|
+
import { extraParams, fieldInit, takesSlots } from "./children.js";
|
|
11
|
+
import { paramsOf, renderParams, slotFieldsOf } from "./plugin.js";
|
|
11
12
|
import { scopeFor } from "./script.js";
|
|
12
13
|
export function needsLifetime(ty, comp, seen = new Set()) {
|
|
13
14
|
switch (ty.k) {
|
|
14
|
-
// A record's keys are strings.
|
|
15
15
|
case "str":
|
|
16
16
|
case "record":
|
|
17
17
|
return true;
|
|
@@ -19,7 +19,6 @@ export function needsLifetime(ty, comp, seen = new Set()) {
|
|
|
19
19
|
case "list":
|
|
20
20
|
return needsLifetime(ty.of, comp, seen);
|
|
21
21
|
case "struct": {
|
|
22
|
-
// A struct that holds itself borrows only if something else in it does.
|
|
23
22
|
if (seen.has(ty.name))
|
|
24
23
|
return false;
|
|
25
24
|
const { st, owner } = lookupStruct(comp, ty);
|
|
@@ -30,7 +29,6 @@ export function needsLifetime(ty, comp, seen = new Set()) {
|
|
|
30
29
|
const child = childOf(ty.name);
|
|
31
30
|
return structLifetime(child.props, child);
|
|
32
31
|
}
|
|
33
|
-
// A configured type that borrows says so by naming the props' lifetime: `Trusted<'a>`.
|
|
34
32
|
case "html":
|
|
35
33
|
return ctx.trustedHtml?.includes("'a") ?? false;
|
|
36
34
|
default:
|
|
@@ -72,8 +70,6 @@ export function rustTy(ty, comp) {
|
|
|
72
70
|
throw new GenError("no Rust type for `undefined`");
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
|
-
/** A constructor parameter for a field of type \`ty\`, and how its argument becomes the field's
|
|
76
|
-
* value: strings from anything that turns into a \`Cow\`, lists of them from any iterator. */
|
|
77
73
|
function param(ty, comp, arg) {
|
|
78
74
|
if (ty.k === "str")
|
|
79
75
|
return { ty: "impl Into<Cow<'a, str>>", value: `${arg}.into()` };
|
|
@@ -82,12 +78,10 @@ function param(ty, comp, arg) {
|
|
|
82
78
|
}
|
|
83
79
|
return { ty: rustTy(ty, comp), value: arg };
|
|
84
80
|
}
|
|
85
|
-
/** \`new\` with every required field, and a chainable setter for each optional one, so building
|
|
86
|
-
* props from Rust takes neither \`Cow\` nor \`None\`. */
|
|
87
81
|
function builderSource(st, comp, life) {
|
|
88
82
|
const required = st.fields.filter((f) => f.ty.k !== "opt");
|
|
89
83
|
const optional = st.fields.filter((f) => f.ty.k === "opt");
|
|
90
|
-
const arg = (f) => f.rust
|
|
84
|
+
const arg = (f) => f.rust;
|
|
91
85
|
const params = required.map((f) => `${arg(f)}: ${param(f.ty, comp, arg(f)).ty}`).join(", ");
|
|
92
86
|
const inits = st.fields
|
|
93
87
|
.map((f) => (f.ty.k === "opt" ? `${f.rust}: None` : fieldInit(f.rust, param(f.ty, comp, arg(f)).value)))
|
|
@@ -105,7 +99,6 @@ function builderSource(st, comp, life) {
|
|
|
105
99
|
})
|
|
106
100
|
.join("\n");
|
|
107
101
|
const usesA = /'a/.test(params + setters);
|
|
108
|
-
// A struct without strings has no lifetime for its constructor to name.
|
|
109
102
|
const impl = life ? "impl<'a>" : usesA ? "impl<'a>" : "impl";
|
|
110
103
|
return `${impl} ${st.name}${life} {
|
|
111
104
|
/// ${st.name} with ${required.length ? "its required fields" : "nothing set"}${optional.length ? ", every optional one absent" : ""}.
|
|
@@ -126,9 +119,8 @@ export function structSource(st, comp, doc) {
|
|
|
126
119
|
return `${attrs}\n pub ${f.rust}: ${rustTy(f.ty, comp)},`;
|
|
127
120
|
})
|
|
128
121
|
.join("\n");
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return `${doc}#[derive(Debug, Clone, serde::Serialize)]
|
|
122
|
+
const derives = st.fields.some((f) => f.ty.k !== "opt") ? "Debug, Clone" : "Debug, Clone, Default";
|
|
123
|
+
return `${doc}#[derive(${derives}, serde::Serialize)]
|
|
132
124
|
#[cfg_attr(test, derive(serde::Deserialize))]
|
|
133
125
|
pub struct ${st.name}${life} {
|
|
134
126
|
${fields}
|
|
@@ -142,7 +134,6 @@ export function header(source, edit = "the `.vue` file") {
|
|
|
142
134
|
// \`ferrovue\`.
|
|
143
135
|
`;
|
|
144
136
|
}
|
|
145
|
-
/** Rust expressions summing the lengths of the strings a value of type `ty` at `place` holds. */
|
|
146
137
|
export function textLen(comp, place, ty, seen = new Set()) {
|
|
147
138
|
switch (ty.k) {
|
|
148
139
|
case "str":
|
|
@@ -160,7 +151,6 @@ export function textLen(comp, place, ty, seen = new Set()) {
|
|
|
160
151
|
}
|
|
161
152
|
return [];
|
|
162
153
|
case "struct": {
|
|
163
|
-
// A recursive structure is counted to its first level: the reservation is an estimate.
|
|
164
154
|
if (seen.has(ty.name))
|
|
165
155
|
return [];
|
|
166
156
|
const { st, owner } = lookupStruct(comp, ty);
|
|
@@ -179,18 +169,12 @@ export function textLen(comp, place, ty, seen = new Set()) {
|
|
|
179
169
|
return [];
|
|
180
170
|
}
|
|
181
171
|
}
|
|
182
|
-
/** `render`, and for a component a parent may hand scope ids or pass attributes to,
|
|
183
|
-
* `render_scoped`, which takes them last, as `ssrRenderAttrs` writes them onto its root; `render`
|
|
184
|
-
* hands it none. */
|
|
185
172
|
function renderSource(comp, life, args, e) {
|
|
186
173
|
const doc = "/// Write the component's server render into `out`.\n";
|
|
187
|
-
// Props or ids the render never reads, as when a component only passes its slot on.
|
|
188
174
|
const props = e.reads("props", 0) ? "props" : "_props";
|
|
189
175
|
if (!comp.inherits && !takesAttrs(comp))
|
|
190
176
|
return `${doc}pub fn render(out: &mut String, ${props}: &Props${life}${extraParams(comp)}) {\n${e.lines.join("\n")}\n}`;
|
|
191
|
-
// A root that is a fragment, or a `<Teleport>`, takes no ids.
|
|
192
177
|
const attrs = e.reads("fv_attrs", 0) ? "fv_attrs" : "_fv_attrs";
|
|
193
|
-
// A component a parent may pass attributes takes them with the ids, as one `fv::Attrs`.
|
|
194
178
|
const [none, ty, what] = takesAttrs(comp)
|
|
195
179
|
? ["&fv::Attrs::NONE", "&fv::Attrs<'_>", "the attributes a parent passes beyond the props, and the scope ids it hands the root"]
|
|
196
180
|
: ['""', "&str", "the scope ids a parent hands the root: ` data-v-…` each"];
|
|
@@ -204,10 +188,8 @@ pub fn render_scoped(out: &mut String, ${props}: &Props${life}${extraParams(comp
|
|
|
204
188
|
${e.lines.join("\n")}
|
|
205
189
|
}`;
|
|
206
190
|
}
|
|
207
|
-
/** Whether the component gets an `island()`: it renders from its props alone, which `data-props` is
|
|
208
|
-
* all the client gets to mount it with. */
|
|
209
191
|
export function isIsland(comp) {
|
|
210
|
-
return !takesSlots(comp) &&
|
|
192
|
+
return !takesSlots(comp) && comp.takes.size === 0;
|
|
211
193
|
}
|
|
212
194
|
export function componentSource(comp, ast, ssr, components) {
|
|
213
195
|
if (takesAttrs(comp) && comp.inheritAttrs && comp.attrsDropped) {
|
|
@@ -223,18 +205,12 @@ export function componentSource(comp, ast, ssr, components) {
|
|
|
223
205
|
for (const l of lets)
|
|
224
206
|
e.stmt(l);
|
|
225
207
|
statements(scope, e, fn.body.body);
|
|
226
|
-
// A setup value the render never reads is not computed. Checked from the last up, since one may
|
|
227
|
-
// read another declared before it.
|
|
228
208
|
for (let i = lets.length - 1; i >= 0; i--) {
|
|
229
209
|
const name = /^let (\w+)/.exec(lets[i])[1];
|
|
230
210
|
if (!e.reads(name, i + 1))
|
|
231
211
|
e.lines.splice(i, 1);
|
|
232
212
|
}
|
|
233
213
|
e.flush();
|
|
234
|
-
/* The literal markup, a loop's once per item, what the helpers can write, and every string the
|
|
235
|
-
* props hold. Literals in untaken branches overcount and escaping or a string used twice
|
|
236
|
-
* undercounts, so it is an estimate — close enough that the buffer is sized once, where
|
|
237
|
-
* reserving the literals alone leaves it to grow again at the first long label. */
|
|
238
214
|
const text = textLen(comp, "props", { k: "struct", name: "Props" });
|
|
239
215
|
const fixed = e.literalBytes + scope.helperBytes.n;
|
|
240
216
|
const reserve = [...(fixed || !(e.perItem.length + text.length) ? [String(fixed)] : []), ...e.perItem, ...text];
|
|
@@ -242,16 +218,12 @@ export function componentSource(comp, ast, ssr, components) {
|
|
|
242
218
|
const life = structLifetime(comp.props, comp) ? "<'_>" : "";
|
|
243
219
|
const gen = life ? "<'p, 'a>" : "<'p>";
|
|
244
220
|
const named = life ? "<'a>" : "";
|
|
245
|
-
// What holds its props borrows only what they borrow.
|
|
246
221
|
const owned = life ? "<'a>" : "";
|
|
247
222
|
const ownedLife = life ? "'a" : "'static";
|
|
248
|
-
// An `interface Props` that `defineProps` takes is the props struct itself, written once below.
|
|
249
223
|
const structs = [...comp.structs.values()]
|
|
250
224
|
.filter((st) => st.name !== "Props" && !st.slot)
|
|
251
225
|
.map((st) => structSource(st, comp, `/// \`${st.name}\` in \`${basename(comp.file)}\`.\n`))
|
|
252
226
|
.join("\n");
|
|
253
|
-
// `Cow` is imported only where a field is one: a lifetime that comes from another component's props
|
|
254
|
-
// alone borrows through that type, not through a `Cow` written here.
|
|
255
227
|
const usesCow = /Cow</.test(structs + structSource(comp.props, comp, ""));
|
|
256
228
|
const plain = isIsland(comp);
|
|
257
229
|
const slotFields = [
|
|
@@ -260,7 +232,7 @@ export function componentSource(comp, ast, ssr, components) {
|
|
|
260
232
|
const type = comp.slotShapes.has(n) ? `&'s ${slotTypeName(n, "Slot")}<'s>` : "fv::Slot<'s>";
|
|
261
233
|
return ` /// ${outlet}\n pub ${snake(n)}: Option<${type}>,`;
|
|
262
234
|
}),
|
|
263
|
-
...(comp.
|
|
235
|
+
...slotFieldsOf(comp).map((f) => ` /// ${f.doc}\n pub ${f.rust}: fv::Slot<'s>,`),
|
|
264
236
|
];
|
|
265
237
|
const slotTypes = [...comp.slotShapes.entries()]
|
|
266
238
|
.map(([n, shape]) => {
|
|
@@ -281,19 +253,15 @@ pub type ${slotTypeName(n, "Slot")}<'s> = dyn ${borrows ? "for<'v> " : ""}Fn(&mu
|
|
|
281
253
|
.join("");
|
|
282
254
|
const slotsStruct = takesSlots(comp)
|
|
283
255
|
? `${slotTypes}/// What a parent puts in the slots \`${basename(comp.file)}\` renders.
|
|
284
|
-
#[derive(Clone, Copy${comp.
|
|
256
|
+
#[derive(Clone, Copy${slotFieldsOf(comp).length ? "" : ", Default"})]
|
|
285
257
|
pub struct Slots<'s> {
|
|
286
258
|
${slotFields.join("\n")}
|
|
287
259
|
}
|
|
288
260
|
|
|
289
261
|
`
|
|
290
262
|
: "";
|
|
291
|
-
const args = (takesSlots(comp) ? ", fv_slots" : "") + (comp.
|
|
292
|
-
const params = (takesSlots(comp) ? ", fv_slots: Slots<'p>" : "") +
|
|
293
|
-
(comp.usesRoute ? ", fv_route: &'p fv::Route<'p>" : "") +
|
|
294
|
-
(comp.usesStores ? ", fv_stores: &'p super::stores::Stores<'p>" : "") +
|
|
295
|
-
(comp.usesI18n ? ", fv_i18n: &'p fv::I18n" : "") +
|
|
296
|
-
(comp.usesTeleports ? ", fv_teleports: &'p fv::Teleports" : "");
|
|
263
|
+
const args = (takesSlots(comp) ? ", fv_slots" : "") + paramsOf(comp).map((p) => `, ${p.name}`).join("");
|
|
264
|
+
const params = (takesSlots(comp) ? ", fv_slots: Slots<'p>" : "") + paramsOf(comp).map((p) => `, ${p.name}: ${p.pageTy}`).join("");
|
|
297
265
|
const wrappers = plain
|
|
298
266
|
? `/// The component's markup, for a maud page that shows it without hydrating it.
|
|
299
267
|
pub fn html${gen}(props: &'p Props${named}) -> fv::Html<'p, Props${named}> {
|
|
@@ -331,20 +299,22 @@ ${slotsStruct}${renderSource(comp, life, args, e)}
|
|
|
331
299
|
|
|
332
300
|
${wrappers}`;
|
|
333
301
|
}
|
|
334
|
-
|
|
302
|
+
function readsFixture(c) {
|
|
303
|
+
return takesSlots(c) || paramsOf(c).some((p) => p.test.fixture);
|
|
304
|
+
}
|
|
305
|
+
export function modSource(comps, modules) {
|
|
335
306
|
const arms = comps
|
|
336
307
|
.map((c) => {
|
|
337
308
|
const lines = [`let props: ${c.module}::Props = serde_json::from_str(json).map_err(|e| e.to_string())?;`];
|
|
338
309
|
let args = "";
|
|
339
|
-
if (
|
|
310
|
+
if (readsFixture(c))
|
|
340
311
|
lines.push("let fixture: Fixture = serde_json::from_str(json).map_err(|e| e.to_string())?;");
|
|
341
312
|
if (takesSlots(c)) {
|
|
342
|
-
const names = [...c.slotNames, ...(c.
|
|
313
|
+
const names = [...c.slotNames, ...slotFieldsOf(c).map((f) => f.js)];
|
|
343
314
|
for (const n of names) {
|
|
344
315
|
const local = `s_${snake(n).replace(/^r#/, "")}`;
|
|
345
316
|
const shape = c.slotShapes.get(n);
|
|
346
317
|
if (shape) {
|
|
347
|
-
// A fixture's content for a scoped slot is static: it is given the props and ignores them.
|
|
348
318
|
const life = shape.fields.some((f) => slotFieldBorrows(f.ty)) ? "<'_>" : "";
|
|
349
319
|
lines.push(`let ${local} = |out: &mut String, _: &${c.module}::${shape.name}${life}${c.passesSlotIds ? ", _: &str" : ""}| -> bool { out.push_str(fixture.slot(${rustStr(n)}).unwrap_or_default()); true };`);
|
|
350
320
|
}
|
|
@@ -356,34 +326,23 @@ export function modSource(comps) {
|
|
|
356
326
|
const value = c.slotShapes.has(n) ? `&${local} as &${c.module}::${slotTypeName(n, "Slot")}` : `ferrovue::Slot::new(&${local})`;
|
|
357
327
|
return `${snake(n)}: fixture.slot(${rustStr(n)}).map(|_| ${value})`;
|
|
358
328
|
});
|
|
359
|
-
|
|
360
|
-
fields.push(
|
|
329
|
+
for (const f of slotFieldsOf(c))
|
|
330
|
+
fields.push(`${f.rust}: ferrovue::Slot::new(&s_${snake(f.js).replace(/^r#/, "")})`);
|
|
361
331
|
args += `, ${c.module}::Slots { ${fields.join(", ")} }`;
|
|
362
332
|
}
|
|
363
|
-
|
|
364
|
-
lines.push(
|
|
365
|
-
args +=
|
|
366
|
-
}
|
|
367
|
-
if (c.usesStores) {
|
|
368
|
-
lines.push("let state: stores::Stores = serde_json::from_value(fixture.stores.clone()).map_err(|e| e.to_string())?;");
|
|
369
|
-
args += ", &state";
|
|
370
|
-
}
|
|
371
|
-
if (c.usesI18n) {
|
|
372
|
-
lines.push("let i18n = i18n::i18n(fixture.locale.as_deref().unwrap_or(i18n::LOCALE));");
|
|
373
|
-
args += ", &i18n";
|
|
374
|
-
}
|
|
375
|
-
if (c.usesTeleports) {
|
|
376
|
-
lines.push("let teleports = ferrovue::Teleports::new();");
|
|
377
|
-
args += ", &teleports";
|
|
333
|
+
for (const p of paramsOf(c)) {
|
|
334
|
+
lines.push(...p.test.lines);
|
|
335
|
+
args += `, ${p.test.arg}`;
|
|
378
336
|
}
|
|
379
337
|
lines.push(`${c.module}::render(&mut out, &props${args});`);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
lines.push("teleports_into(&mut out, teleports);");
|
|
338
|
+
for (const p of paramsOf(c))
|
|
339
|
+
lines.push(...(p.test.after ?? []));
|
|
383
340
|
return ` ${rustStr(c.name)} => {\n${lines.map((l) => " " + l).join("\n")}\n }`;
|
|
384
341
|
})
|
|
385
342
|
.join("\n");
|
|
386
|
-
const
|
|
343
|
+
const fields = renderParams().flatMap((p) => (p.fixtureField === undefined ? [] : [p.fixtureField]));
|
|
344
|
+
const defaults = renderParams().flatMap((p) => (p.fixtureDefault === undefined ? [] : [p.fixtureDefault]));
|
|
345
|
+
const fixture = comps.some(readsFixture)
|
|
387
346
|
? `
|
|
388
347
|
/// What a fixture holds besides the props: each slot's content, and the location it renders at.
|
|
389
348
|
#[cfg(test)]
|
|
@@ -391,25 +350,12 @@ export function modSource(comps) {
|
|
|
391
350
|
struct Fixture {
|
|
392
351
|
#[serde(rename = "$slots", default)]
|
|
393
352
|
slots: std::collections::HashMap<String, String>,
|
|
394
|
-
|
|
395
|
-
route: String,
|
|
396
|
-
#[serde(rename = "$stores", default = "Fixture::no_stores")]
|
|
397
|
-
stores: serde_json::Value,
|
|
398
|
-
#[serde(rename = "$locale", default)]
|
|
399
|
-
locale: Option<String>,
|
|
353
|
+
${fields.join("\n")}
|
|
400
354
|
}
|
|
401
355
|
|
|
402
356
|
#[cfg(test)]
|
|
403
357
|
impl Fixture {
|
|
404
|
-
fn
|
|
405
|
-
"/".to_owned()
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
fn no_stores() -> serde_json::Value {
|
|
409
|
-
serde_json::Value::Object(Default::default())
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
fn slot(&self, name: &str) -> Option<&str> {
|
|
358
|
+
${defaults.map((d) => `${d}\n\n`).join("")} fn slot(&self, name: &str) -> Option<&str> {
|
|
413
359
|
self.slots.get(name).map(String::as_str)
|
|
414
360
|
}
|
|
415
361
|
}
|
|
@@ -424,25 +370,12 @@ impl Fixture {
|
|
|
424
370
|
// it needs.
|
|
425
371
|
#![allow(dead_code)]
|
|
426
372
|
|
|
427
|
-
${comps.map((c) => `pub mod ${c.module};`).join("\n")}${
|
|
373
|
+
${comps.map((c) => `pub mod ${c.module};`).join("\n")}${modules.map((m) => `\npub mod ${m.replace(/\.rs$/, "")};`).join("")}
|
|
428
374
|
${fixture}
|
|
429
|
-
${
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if targets.is_empty() {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
let pairs: Vec<String> = targets
|
|
437
|
-
.iter()
|
|
438
|
-
.map(|(t, html)| format!("{}:{}", serde_json::to_string(t).unwrap(), serde_json::to_string(html).unwrap()))
|
|
439
|
-
.collect();
|
|
440
|
-
out.push_str("<!--fv-teleports-->{");
|
|
441
|
-
out.push_str(&pairs.join(","));
|
|
442
|
-
out.push('}');
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
` : ""}/// Render one component from its props as JSON, for the conformance suite.
|
|
375
|
+
${renderParams()
|
|
376
|
+
.filter((p) => p.testSupport !== undefined && comps.some((c) => c.takes.has(p.name)))
|
|
377
|
+
.map((p) => `${p.testSupport}\n\n`)
|
|
378
|
+
.join("")}/// Render one component from its props as JSON, for the conformance suite.
|
|
446
379
|
#[cfg(test)]
|
|
447
380
|
pub fn render_json(component: &str, json: &str) -> Result<String, String> {
|
|
448
381
|
let mut out = String::new();
|
|
@@ -454,28 +387,8 @@ ${arms}
|
|
|
454
387
|
}
|
|
455
388
|
`;
|
|
456
389
|
}
|
|
457
|
-
export function storesSource(dir) {
|
|
458
|
-
const home = storeHome(dir);
|
|
459
|
-
// Test-only `Default` lets a fixture name only the stores it reads; the rest are never looked at.
|
|
460
|
-
const testDerive = (src) => src.replace("#[cfg_attr(test, derive(serde::Deserialize))]", "#[cfg_attr(test, derive(Default, serde::Deserialize))]\n#[cfg_attr(test, serde(default))]");
|
|
461
|
-
const structs = [...ctx.storeStructs.values()]
|
|
462
|
-
.map((st) => testDerive(structSource(st, home, `/// \`${st.name}\` in \`${ctx.storeFiles.get(st.name)}\`.\n`)))
|
|
463
|
-
.join("\n");
|
|
464
|
-
const all = {
|
|
465
|
-
name: "Stores",
|
|
466
|
-
fields: [...ctx.stores.values()].map((st) => ({ js: st.id, rust: st.field, ty: { k: "struct", name: st.state, store: true } })),
|
|
467
|
-
};
|
|
468
|
-
const top = testDerive(structSource(all, home, "/// Every store's state, keyed by id as `pinia.state.value` is: what the page sends the client.\n"));
|
|
469
|
-
return `${header(dir, "the store files")}
|
|
470
|
-
//! The Pinia stores' state, which components read while they render on the server.
|
|
471
|
-
|
|
472
|
-
${/Cow</.test(structs) ? "use std::borrow::Cow;\n\n" : ""}${structs}
|
|
473
|
-
${top}`;
|
|
474
|
-
}
|
|
475
390
|
export function typesSource() {
|
|
476
|
-
const home =
|
|
477
|
-
home.structs = ctx.typeStructs;
|
|
478
|
-
home.module = "types";
|
|
391
|
+
const home = blankComponent("types", "types", "types", ctx.typeStructs);
|
|
479
392
|
const files = [...new Set(ctx.typeFiles.values())].toSorted();
|
|
480
393
|
const structs = [...ctx.typeStructs.values()]
|
|
481
394
|
.map((st) => structSource(st, home, `/// \`${st.name}\` in \`${ctx.typeFiles.get(st.name)}\`.\n`))
|
|
@@ -486,38 +399,3 @@ export function typesSource() {
|
|
|
486
399
|
|
|
487
400
|
${/Cow</.test(structs) ? "use std::borrow::Cow;\n\n" : ""}${structs}`;
|
|
488
401
|
}
|
|
489
|
-
/** Route definitions as \`ferrovue::RouteDef\` literals, children nested. */
|
|
490
|
-
function routeDefs(routes, depth) {
|
|
491
|
-
const pad = " ".repeat(depth);
|
|
492
|
-
return routes
|
|
493
|
-
.map((r) => {
|
|
494
|
-
const name = r.name === undefined ? "None" : `Some(${rustStr(r.name)})`;
|
|
495
|
-
const children = r.children?.length ? `&[\n${routeDefs(r.children, depth + 1)}\n${pad}]` : "&[]";
|
|
496
|
-
return `${pad}ferrovue::RouteDef { path: ${rustStr(r.path)}, name: ${name}, children: ${children} },`;
|
|
497
|
-
})
|
|
498
|
-
.join("\n");
|
|
499
|
-
}
|
|
500
|
-
export function routesSource(routes, file) {
|
|
501
|
-
return `${header(file, "the routes file")}
|
|
502
|
-
//! The app's routes: what \`<RouterLink>\` resolves against and \`useRoute()\` reads.
|
|
503
|
-
|
|
504
|
-
/// Each route: its vue-router path, its name if it has one, and the routes nested in it.
|
|
505
|
-
pub const ROUTES: &[ferrovue::RouteDef<'static>] = &[
|
|
506
|
-
${routeDefs(routes, 1)}
|
|
507
|
-
];
|
|
508
|
-
|
|
509
|
-
/// Every route's full path, nested ones included.
|
|
510
|
-
pub const PATHS: &[&str] = &[
|
|
511
|
-
${allRoutes(routes).map((r) => ` ${rustStr(r.fullPath)},`).join("\n")}
|
|
512
|
-
];
|
|
513
|
-
|
|
514
|
-
/// The history's base, which every link's \`href\` starts with.
|
|
515
|
-
pub const BASE: &str = ${rustStr(ctx.routerBase)};
|
|
516
|
-
|
|
517
|
-
/// The router these routes make: build it once, and resolve each request's location with
|
|
518
|
-
/// [\`ferrovue::Router::at\`].
|
|
519
|
-
pub fn router() -> ferrovue::Router {
|
|
520
|
-
ferrovue::Router::tree(ROUTES).with_base(BASE)
|
|
521
|
-
}
|
|
522
|
-
`;
|
|
523
|
-
}
|
package/dist/script.d.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { type Component, type N, type Scope } from "./model.ts";
|
|
2
|
-
/** Lifecycle hooks, which never run on the server: setup may register them freely. */
|
|
3
2
|
export declare const CLIENT_HOOKS: Set<string>;
|
|
4
|
-
/** Compiler macros and calls with no effect on the server's render. */
|
|
5
3
|
export declare const INERT_CALLS: Set<string>;
|
|
6
|
-
/** A statement in setup that is a call on its own: allowed only when it cannot change the render. */
|
|
7
4
|
export declare function setupStatement(comp: Component, st: N): void;
|
|
8
|
-
/** What a setup binding's value is computed from: `ref(x)` and `shallowRef(x)` hold `x`,
|
|
9
|
-
* `computed(() => x)` is `x`, and a plain `const y = x` is `x`. */
|
|
10
5
|
export declare function setupSource(init: N): N | null;
|
|
11
|
-
/** The names a destructuring pattern binds. */
|
|
12
6
|
export declare function patternNames(p: N): string[];
|
|
13
7
|
export declare function scopeFor(comp: Component, ast: N[], components: Map<string, Component>): {
|
|
14
8
|
scope: Scope;
|