ferrovue 0.10.0 → 0.12.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.
Files changed (121) hide show
  1. package/README.md +41 -30
  2. package/dist/args.d.ts +1 -1
  3. package/dist/args.js +1 -1
  4. package/dist/attrs.js +22 -21
  5. package/dist/blocks.d.ts +11 -3
  6. package/dist/blocks.js +349 -56
  7. package/dist/calls.js +45 -33
  8. package/dist/children.d.ts +1 -1
  9. package/dist/children.js +175 -25
  10. package/dist/classes.js +57 -22
  11. package/dist/cli.js +3 -1
  12. package/dist/client.d.ts +39 -11
  13. package/dist/client.js +94 -11
  14. package/dist/compiler.d.ts +11 -6
  15. package/dist/compiler.js +15 -9
  16. package/dist/component.js +142 -9
  17. package/dist/config.d.ts +128 -0
  18. package/dist/config.js +32 -0
  19. package/dist/conformance.d.ts +9 -43
  20. package/dist/conformance.js +19 -15
  21. package/dist/constants.d.ts +25 -0
  22. package/dist/constants.js +253 -32
  23. package/dist/context.d.ts +15 -126
  24. package/dist/context.js +25 -31
  25. package/dist/date-plans.d.ts +11 -0
  26. package/dist/date-plans.js +10 -0
  27. package/dist/diagnostics.d.ts +3 -3
  28. package/dist/diagnostics.js +1 -1
  29. package/dist/dynamic.js +21 -12
  30. package/dist/editor.js +3 -2
  31. package/dist/emitter.d.ts +4 -0
  32. package/dist/emitter.js +13 -1
  33. package/dist/enums.d.ts +66 -0
  34. package/dist/enums.js +421 -0
  35. package/dist/error-areas.d.ts +5 -0
  36. package/dist/error-areas.js +24 -0
  37. package/dist/errors.d.ts +191 -31
  38. package/dist/errors.js +198 -49
  39. package/dist/expr.d.ts +11 -4
  40. package/dist/expr.js +349 -64
  41. package/dist/fallthrough.js +15 -0
  42. package/dist/file-routes.d.ts +2 -15
  43. package/dist/file-routes.js +2 -1
  44. package/dist/files.d.ts +3 -3
  45. package/dist/files.js +2 -1
  46. package/dist/functions.js +40 -13
  47. package/dist/inputs.d.ts +1 -1
  48. package/dist/inputs.js +1 -0
  49. package/dist/link-router.d.ts +1 -2
  50. package/dist/link-router.js +1 -2
  51. package/dist/lists.d.ts +5 -0
  52. package/dist/lists.js +122 -42
  53. package/dist/loads.d.ts +6 -24
  54. package/dist/loads.js +1 -68
  55. package/dist/loops.js +35 -20
  56. package/dist/model.d.ts +147 -36
  57. package/dist/model.js +200 -41
  58. package/dist/names.d.ts +10 -0
  59. package/dist/names.js +98 -0
  60. package/dist/narrowing.d.ts +13 -0
  61. package/dist/narrowing.js +104 -5
  62. package/dist/numbers.d.ts +9 -3
  63. package/dist/numbers.js +21 -9
  64. package/dist/objects.d.ts +18 -0
  65. package/dist/objects.js +205 -0
  66. package/dist/page-routes.d.ts +1 -2
  67. package/dist/parens.d.ts +11 -0
  68. package/dist/parens.js +16 -2
  69. package/dist/peer.d.ts +3 -0
  70. package/dist/peer.js +17 -0
  71. package/dist/plugin.d.ts +1 -1
  72. package/dist/plugin.js +1 -0
  73. package/dist/plugins/client-only.js +2 -2
  74. package/dist/plugins/css-modules.js +2 -1
  75. package/dist/plugins/dates.js +16 -14
  76. package/dist/plugins/head.js +24 -24
  77. package/dist/plugins/i18n.js +4 -4
  78. package/dist/plugins/loaders.js +45 -26
  79. package/dist/plugins/provide.js +9 -4
  80. package/dist/plugins/router-link.js +15 -14
  81. package/dist/plugins/router.d.ts +1 -1
  82. package/dist/plugins/router.js +14 -12
  83. package/dist/plugins/scoped.d.ts +1 -1
  84. package/dist/plugins/scoped.js +4 -4
  85. package/dist/plugins/shared-types.js +2 -1
  86. package/dist/plugins/stores.js +9 -3
  87. package/dist/plugins/teleport.js +5 -5
  88. package/dist/plugins/twins.d.ts +1 -1
  89. package/dist/plugins/twins.js +8 -5
  90. package/dist/resolve.d.ts +0 -10
  91. package/dist/revive.d.ts +5 -0
  92. package/dist/revive.js +38 -0
  93. package/dist/rust.d.ts +1 -1
  94. package/dist/rust.js +109 -56
  95. package/dist/schema.js +2 -1
  96. package/dist/script.js +88 -14
  97. package/dist/slots.js +14 -9
  98. package/dist/statements.d.ts +11 -0
  99. package/dist/statements.js +436 -0
  100. package/dist/strings.d.ts +6 -3
  101. package/dist/strings.js +59 -40
  102. package/dist/styles.js +20 -26
  103. package/dist/template.js +60 -20
  104. package/dist/{fixture.d.ts → testing-browser.d.ts} +26 -3
  105. package/dist/{hydration.js → testing-browser.js} +102 -1
  106. package/dist/testing.d.ts +42 -5
  107. package/dist/testing.js +14 -4
  108. package/dist/types.d.ts +1 -1
  109. package/dist/typescript.d.ts +17 -10
  110. package/dist/typescript.js +120 -41
  111. package/dist/vhtml.js +1 -1
  112. package/dist/vite.d.ts +14 -2
  113. package/dist/vite.js +3 -1
  114. package/package.json +27 -11
  115. package/schema.json +13 -5
  116. package/dist/fixture.js +0 -112
  117. package/dist/hydration.d.ts +0 -26
  118. package/dist/index.d.ts +0 -4
  119. package/dist/index.js +0 -3
  120. /package/dist/{routes.d.ts → route-records.d.ts} +0 -0
  121. /package/dist/{routes.js → route-records.js} +0 -0
package/README.md CHANGED
@@ -4,8 +4,11 @@
4
4
  [![npm](https://img.shields.io/npm/v/ferrovue.svg)](https://www.npmjs.com/package/ferrovue)
5
5
  [![docs.rs](https://img.shields.io/docsrs/ferrovue)](https://docs.rs/ferrovue)
6
6
  [![CI](https://github.com/Kinsmir/ferrovue/actions/workflows/ci.yml/badge.svg)](https://github.com/Kinsmir/ferrovue/actions/workflows/ci.yml)
7
+ [![Mutation score](https://img.shields.io/endpoint?url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FKinsmir%2Fferrovue%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/Kinsmir/ferrovue/main)
7
8
  [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#licence)
8
9
 
10
+ Documentation: [docs.rs/ferrovue](https://docs.rs/ferrovue) (API and guide) · [kinsmir.github.io/ferrovue](https://kinsmir.github.io/ferrovue/) (guide and overview)
11
+
9
12
  Vue single-file components compiled to Rust render functions. A Rust server renders a component's
10
13
  HTML with no JavaScript runtime, and the browser hydrates that HTML with the same `.vue` file.
11
14
 
@@ -16,7 +19,9 @@ HTML with no JavaScript runtime, and the browser hydrates that HTML with the sam
16
19
  writes for the same props, so Vue hydrates the page without a mismatch. Every supported construct
17
20
  is held to fixtures recorded from Vue.
18
21
  - **Exact or refused.** What ferrovue cannot reproduce exactly is a compile error that names the
19
- file, line and construct. A component that compiles renders as Vue renders it.
22
+ file, line and construct. A component that compiles renders as Vue renders it, and props it
23
+ cannot render exactly, such as a date string from a query that browsers read differently, are
24
+ refused as it renders: `try_render` returns the refusal, so a server answers `400`.
20
25
 
21
26
  ```text
22
27
  Button.vue ──(ferrovue compiler, Node)──▶ button.rs ──(your Rust server)──▶ HTML ──▶ Vue hydrates it
@@ -30,7 +35,7 @@ element structure, attribute order, whitespace and fragment markers.
30
35
  | | What it is | Published as |
31
36
  |---|---|---|
32
37
  | `packages/ferrovue` | The compiler, the `ferrovue` command, the Vite plugin and the browser client (TypeScript) | npm: [`ferrovue`](https://www.npmjs.com/package/ferrovue) |
33
- | `crates/ferrovue` | The runtime the generated Rust calls: escaping, slots, islands, `<RouterLink>` matching. It re-exports `ferrovue-router`, `ferrovue-i18n` and `ferrovue-core`, released with it | crates.io: [`ferrovue`](https://crates.io/crates/ferrovue) ([API docs](https://docs.rs/ferrovue)) |
38
+ | `crates/ferrovue` | The runtime the generated Rust calls: escaping, slots, islands, `<RouterLink>` matching. It holds `ferrovue-core`, `ferrovue-router` and `ferrovue-i18n`, released with it, as its modules `core`, `router` and `i18n` | crates.io: [`ferrovue`](https://crates.io/crates/ferrovue) ([API docs](https://docs.rs/ferrovue)) |
34
39
 
35
40
  ## Quick start
36
41
 
@@ -52,7 +57,7 @@ the configuration at your project root yourself:
52
57
 
53
58
  ```json
54
59
  {
55
- "$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.8.0/schema.json",
60
+ "$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.12.0/schema.json",
56
61
  "components": "client/components",
57
62
  "out": "src/generated"
58
63
  }
@@ -71,8 +76,9 @@ read `$schema`. Offline, the copy in the installed package works as well:
71
76
  | `routes` | no | JSON file listing the app's routes: each a vue-router path, or `{ "path", "name", "children" }`. Or `{ "pages": "client/pages" }`: a folder of pages, whose file names give the routes as vue-router's file-based routing reads them (`index.vue`, `[id].vue`, `[[id]].vue`, `[...path].vue`, `(group)` folders, a `name.vue` beside `name/` as its layout), each page compiled as a component. Needed for `<RouterLink>`, `<RouterView>` and `useRoute()` |
72
77
  | `router` | no | Instead of `routes`: `{ routes, base?, linkActiveClass?, linkExactActiveClass? }`, matching `createWebHistory(base)` and `createRouter`'s options |
73
78
  | `stores` | no | Directory of Pinia option stores whose state components may read |
74
- | `trustedHtml` | no | Rust type of a `TrustedHtml` prop (needed for `v-html`): `ferrovue::BasicHtml` (text with a few formatting tags, built in), `ferrovue::InlineHtml` (inline formatting tags only, built in, which makes every `TrustedHtml` prop inline HTML), `ferrovue::Sanitised` (HTML cleaned by ammonia, the `ammonia` feature), or a type of your own such as `crate::html::CleanHtml` |
75
- | `dateType` | no | Rust type of a `Date` prop: `ferrovue::JsDate` (the default), `chrono::DateTime<chrono::Utc>` (the `chrono` feature), or a type of your own that implements `ferrovue::JsTime`. See [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html#date-props) |
79
+ | `trustedHtml` | no | Rust type of a `TrustedHtml` prop (needed for `v-html`): `ferrovue::html::BasicHtml` (text with a few formatting tags, built in), `ferrovue::html::InlineHtml` (inline formatting tags only, built in, which makes every `TrustedHtml` prop inline HTML), `ferrovue::html::Sanitised` (HTML cleaned by ammonia, the `ammonia` feature), or a type of your own such as `crate::html::CleanHtml` |
80
+ | `dateType` | no | Rust type of a `Date` prop: `ferrovue::date::JsDate` (the default), `chrono::DateTime<chrono::Utc>` (the `chrono` feature), or a type of your own that implements `ferrovue::date::JsTime`. See [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html#date-props) |
81
+ | `unions` | no | Rust enums of your own for unions of string literals, by `type` alias: `{ "Tone": "crate::domain::Tone" }`. Generated code uses the enum, which implements `ferrovue::props::Union` and has the variants ferrovue would generate, and generates none for the union. See [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#unions-mapped-to-existing-enums) |
76
82
  | `helpers` | no | `{ module, functions }`: functions a template may call, each mapped to a Rust twin |
77
83
  | `twins` | no | Components ferrovue does not compile, each rendered by a Rust function of yours: `{ "VBtn": { "rust": "crate::ui::v_btn", "props": { "label": "string" }, "slots": ["default"] } }`. See [Rust twins](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#rust-twins) |
78
84
  | `i18n` | no | vue-i18n: `{ messages, locale?, fallbackLocale? }`, the directory of locale files (`en.json`, `nl.json`), the default locale and the fallbacks |
@@ -167,7 +173,7 @@ The crate's features:
167
173
 
168
174
  - `router` and `i18n`, both on by default: vue-router and vue-i18n. An application with neither
169
175
  routes nor translations can leave them out, and build neither `ferrovue-router` nor
170
- `ferrovue-i18n`: `ferrovue = { version = "0.8", default-features = false }`.
176
+ `ferrovue-i18n`: `ferrovue = { version = "0.12", default-features = false }`.
171
177
  - `axum` and `actix-web`: `Html` is an `IntoResponse` or a `Responder`, so a handler responds with
172
178
  the component as `text/html; charset=utf-8`. `into_html(props)` and `into_island(props)` take the
173
179
  props by value, so a handler that builds them returns the page:
@@ -180,12 +186,12 @@ The crate's features:
180
186
 
181
187
  - `stream`, which `axum` and `actix-web` turn on: `HtmlStream`, a page sent in the order its parts
182
188
  are ready.
183
- - `maud`: `ferrovue::Html` implements `maud::Render`, so `(greeting::html(&props))` goes straight
189
+ - `maud`: `ferrovue::html::Html` implements `maud::Render`, so `(greeting::html(&props))` goes straight
184
190
  into a `maud::html!` page.
185
191
  - `dioxus`: `{greeting::island(&props)}` in a Dioxus 0.7 `rsx!` is the island element itself, with
186
192
  exactly the markup Vue hydrates inside it (`examples/dioxus`).
187
- - `ammonia`: `ferrovue::Sanitised`, HTML cleaned by [ammonia](https://docs.rs/ammonia), for
188
- `v-html`. `ferrovue::BasicHtml` and `ferrovue::InlineHtml` are built in; the
193
+ - `ammonia`: `ferrovue::html::Sanitised`, HTML cleaned by [ammonia](https://docs.rs/ammonia), for
194
+ `v-html`. `ferrovue::html::BasicHtml` and `ferrovue::html::InlineHtml` are built in; the
189
195
  [`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html) guide describes
190
196
  all three.
191
197
 
@@ -217,44 +223,47 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
217
223
 
218
224
  | Area | Supported |
219
225
  |---|---|
220
- | Prop types | `string`, `number` (integers, `i64`, written and computed as JavaScript does, exact within ±2⁵³), `Float` from `ferrovue/types` (fractions, `f64`), `boolean`, string-literal unions (`"sm" \| "md"`), `T \| undefined`, `T \| null` (an `Option` written as `null`), arrays (`T[]`, `Array<T>`, `readonly T[]`), interfaces and object type aliases (recursive ones too), dictionaries (`Record<string, T>`, `{ [key: string]: T }`, `ferrovue::Record` in Rust, which keeps JavaScript's order of keys), another component's exported `Props`, `TrustedHtml`, `InlineHtml`, `Date` (a component's own prop, optional or nullable too: `ferrovue::JsDate`, or `chrono::DateTime<Utc>` with the `chrono` feature) |
221
- | Shared types | Interfaces and type aliases imported from `.ts` files (generated once, into `types.rs`; imported by a relative path or through an alias the project declares, in its tsconfig.json's `compilerOptions.paths` or, with the Vite plugin, Vite's `resolve.alias`: `@/lib/format`), from a store's file, or from another component's `.vue` file; objects of a shared type can be passed between components. A TypeScript `enum` whose members are literals is the type of its values: a string, or a number |
222
- | Constants | Constants imported from `.ts` files, object constants declared in `<script setup>`, and `enum`s (imported, or declared in the component), evaluated when the component is compiled: strings, numbers, booleans, `null`, lists of them, objects of them read field by field (`LABELS.save`, `Tone.Loud`, `Rank[5]`), and lists of objects of them, which become a `const` in `types.rs` (`{ value, label }` items get a type named after the constant, `OptionsItem`, unless the constant is typed with an interface: `OPTIONS: Option[]`; a field that is `null` in some items is `T | null`); lookup tables: an object constant (`as const`, `Record<Size, T>` or untyped, keys computed from enum members too) read by a key whose type is a union of string or integer literals or an `enum` (a prop, a `computed`, a `v-for` item's field, a `?:` of literals), compiled to a `match` over the members, read on by field (`THEME[tone].fg`) in text, attributes, `:class` and `:style`, with `undefined` for a member the object lacks (`ICONS[kind] ?? "none"`, `ICONS[kind]?.label`, `v-if="ICONS[kind]"`). See the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#constants) guide |
223
- | Props | `withDefaults`, destructured props with defaults (`const { size = "md" } = defineProps<…>()`), the props object (`const props = defineProps<…>()`, then `props.label` in the template or in script code), optional booleans (Vue casts an absent one to `false`), `defineModel` (named, required, with defaults), components with no props. A generic component (`<script setup generic="T extends Item">`) renders each type parameter as its constraint, which is all the template can rely on |
224
- | Setup | `ref`/`shallowRef`, `computed` (an expression, or a body of statements: see Statements; one that may throw, through `.repeat()`, `.padStart()`, a date, `$t` or a helper, is evaluated where the render first reads it, once, as Vue does), refs that start empty, typed by their type argument (`ref<Row[]>([])`, `ref<Row>()`, `ref<string \| undefined>(undefined)`), plain `const`/`let`, `.value` in script code, helpers with Rust twins, functions (see Functions), a plain `<script>` block beside setup. Lifecycle hooks, `watch` (not `immediate`), `defineEmits`, `defineSlots`, `defineOptions`, `defineExpose` and template refs (`ref(null)`, `useTemplateRef`) are client-only, as is a function only an event handler calls. The template may name them only from event handlers, which the server drops |
225
- | Statements | in a `computed`'s body and in a list method's callback (`.map()`, `.filter()`, `.find()`, `.some()`, `.reduce()`, a `.toSorted()` comparator and the others): `const`, and `let` never assigned again, of any value the template may compute, destructured too (`const { name, posts: count } = author`); `if` / `else if` / `else` and early `return`, narrowing what follows as TypeScript narrows it (`if (!user) return ""; return user.name`, `if (!a \|\| !a.title) return "-"`); `switch` over a string or a number, with stacked `case`s, `default` and `break`. Each `const` is evaluated once, where JavaScript evaluates it, and only on the path that reaches it. Loops, assignments, `try`, `throw`, `var` and a path that ends without `return` are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html) guide |
226
- | Functions | functions the template or a `computed` calls: declared in `<script setup>` (`function f(a: T): R { … }`, `const f = (a: T) => …`) or exported from a `.ts` file the component imports, whose bodies hold what a `computed`'s may (see Statements) and read props, refs, `computed`s, constants and other such functions (a `.ts` file's own constants, functions and imports); parameters by name, optional (`x?: T`), with defaults (`x = 1`, also after an earlier parameter: ``plural = `${word}s` ``) and as object patterns. Each call is translated where it is written, its arguments evaluated once and in order, with the types they have there, so one function serves an integer and a `Float`. A function imported from a `.ts` file needs no `helpers` entry. Recursion, `...rest`, `async` and generators are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html#functions) guide |
227
- | Loaders | A top-level `await` in `<script setup>` of a loader declared with `defineLoader`, whose arguments are strings, numbers and booleans and whose result is any type a prop may have: the compiler writes a `Loaders` trait the Rust app implements, and the render stops where Vue's would until the result is in, starting siblings' loaders together. The results travel in the page for the client to hydrate from, inside a `<Suspense>` `mountPage` adds. See the crate's [`loaders`](https://docs.rs/ferrovue/latest/ferrovue/guide/loaders/index.html) guide |
226
+ | Prop types | `string`, `number` (integers, `i64`, written and computed as JavaScript does, exact within ±2⁵³), `Float` from `ferrovue/types` (fractions, `f64`), `boolean`, unions of two or more string literals (`"sm" \| "md"`, an alias, or written in place), each a Rust enum generated for it (`Size::Md`, `"md".parse::<Size>()`, written and serialised as its literal; see the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#unions-of-string-literals) guide), or an enum of the application's own that `unions` names for the alias; a list of such a union, a list of its enum (`Size[]` is `Vec<Size>`); `T \| undefined`, `T \| null` (an `Option` written as `null`), arrays (`T[]`, `Array<T>`, `readonly T[]`), interfaces and object type aliases (recursive ones too), dictionaries (`Record<string, T>`, `{ [key: string]: T }`, `ferrovue::props::Record` in Rust, which keeps JavaScript's order of keys), another component's exported `Props`, `TrustedHtml`, `InlineHtml`, `Date` (a prop, optional or nullable too, and inside lists, dictionaries and interfaces: `ferrovue::date::JsDate`, or `chrono::DateTime<Utc>` with the `chrono` feature) |
227
+ | Shared types | Interfaces and type aliases imported from `.ts` files (generated once, into `types.rs`; imported by a relative path or through an alias the project declares, in its tsconfig.json's `compilerOptions.paths` or, with the Vite plugin, Vite's `resolve.alias`: `@/lib/format`), from a store's file, or from another component's `.vue` file; objects of a shared type can be passed between components. A TypeScript `enum` of string members is a Rust enum, as a union of string literals is, its variants named after its members; one of numbers is a number |
228
+ | Constants | Constants imported from `.ts` files, object constants declared in `<script setup>`, and `enum`s (imported, or declared in the component, with members set to such constants or to earlier members), evaluated when the component is compiled: strings, numbers, booleans, `null`, arithmetic and joined strings of them as JavaScript evaluates them (`60 * MINUTE`, `` `${BASE}/feed` ``, `"#" + 1.5`, and `**` where it gives an exact integer within ±2⁵³), lists of them, objects of them read field by field (`LABELS.save`, `Tone.Loud`, `Rank[5]`), and lists of objects of them, which become a `const` in `types.rs` (`{ value, label }` items get a type named after the constant, `OptionsItem`, unless the constant is typed with an interface: `OPTIONS: Option[]`; a field that is `null` in some items is `T | null`); lookup tables: an object constant (`as const`, `Record<Size, T>` or untyped, keys computed from enum members too) read by a key whose type is a union of string or integer literals or an `enum` (a prop, a `computed`, a `v-for` item's field, a `?:` of literals), compiled to a `match` over the members (over the variants of a union's enum, with no catch-all arm; a table of a union's members, `Record<Tone, Tone>`, reaches a union prop as a `match` from variant to variant), read on by field (`THEME[tone].fg`) in text, attributes, `:class` and `:style`, with `undefined` for a member the object lacks (`ICONS[kind] ?? "none"`, `ICONS[kind]?.label`, `v-if="ICONS[kind]"`, `kind && ICONS[kind]`); a free-text key tested with `Object.hasOwn(ROLES, key)`, against the object's own keys, which narrows the key for `ROLES[key]` in what follows; `Object.keys()` of an object constant; a constant `new Map([[key, value], …])` of string or integer keys, read with `.get(key)` (`undefined` for a key it lacks) and `.has(key)`. See the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#constants) guide |
229
+ | Props | `withDefaults`, destructured props with defaults and new names (`const { size = "md", label: text } = defineProps<…>()`), the props object (`const props = defineProps<…>()`, then `props.label` in the template or in script code), optional booleans (Vue casts an absent one to `false`), `defineModel` (named, required, with defaults), components with no props. A generic component (`<script setup generic="T extends Item">`) renders each type parameter as its constraint, which is all the template can rely on |
230
+ | Setup | `ref`/`shallowRef`, `computed` (an expression, or a body of statements: see Statements; one that may throw, through `.repeat()`, `.padStart()`, a date, `$t` or a helper, is evaluated where the render first reads it, once, as Vue does), refs that start empty, typed by their type argument (`ref<Row[]>([])`, `ref<Row>()`, `ref<string \| undefined>(undefined)`), refs of a union of string literals, held as its enum (`ref<Choice>(props.tone ?? "")`, a member, a value of the union, a lookup table's value, or a `?:` or `??` of them), plain `const`/`let`, `.value` in script code, helpers with Rust twins, functions (see Functions), a plain `<script>` block beside setup. Lifecycle hooks, `watch` (not `immediate`), `defineEmits`, `defineSlots`, `defineOptions`, `defineExpose` and template refs (`ref(null)`, `useTemplateRef`) are client-only, as is a function only an event handler calls. The template may name them only from event handlers, which the server drops |
231
+ | Statements | in a `computed`'s body and in a list method's callback (`.map()`, `.filter()`, `.find()`, `.some()`, `.reduce()`, a `.toSorted()` comparator and the others): `const` and `let` of any value the template may compute, destructured too (`const { name, posts: count } = author`); `if` / `else if` / `else` and early `return`, narrowing what follows as TypeScript narrows it (`if (!user) return ""; return user.name`, `if (!a \|\| !a.title) return "-"`); `switch` over a string or a number, with stacked `case`s, `default` and `break`; assignments to the body's `let`s (`=`, `+=`, `-=`, `*=`, `++`, `--` and the others), with the type TypeScript gives them; `for (const x of list)` over a list, `list.entries()`, `Object.keys()` or `Object.entries()`, `for (const ch of text)` over a string by its characters (a character beyond U+FFFF is one), and counting loops `for (let i = 0; i < n; i++)` to an integer bound, with `break`, `continue`, nested loops and `return` inside them, carrying strings, numbers and booleans from one item to the next (`let total = 0; for (const r of rows) total += r.n;`, thousands separators, the largest of a list). Each value is evaluated once, where JavaScript evaluates it, and only on the path that reaches it. `while`, `do…while` and `for…in` loops, assignments to props, refs, `const`s, parameters and fields, `try`, `throw`, `var` and a path that ends without `return` are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html) guide |
232
+ | Functions | functions the template or a `computed` calls: declared in `<script setup>` (`function f(a: T): R { … }`, `const f = (a: T) => …`) or exported from a `.ts` file the component imports, whose bodies hold what a `computed`'s may (see Statements), arrow functions of their own included (`const pad = (k: number) => …`, reading the body's names) and read props, refs, `computed`s, constants and other such functions (a `.ts` file's own constants, functions and imports); parameters by name, optional (`x?: T`), with defaults (`x = 1`, also after an earlier parameter: ``plural = `${word}s` ``) and as object patterns. Each call is translated where it is written, its arguments evaluated once and in order, with the types they have there, so one function serves an integer and a `Float`. A function imported from a `.ts` file needs no `helpers` entry. Recursion, `...rest`, `async` and generators are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html#functions) guide |
233
+ | Object literals | object literals built as the Rust struct of the interface where they go: a `computed` annotated with it (`computed<Card>(…)`, `computed((): Card => …)`, `ComputedRef<Card>`), a function's return type or parameter, a `.map()` callback's return type or the list type its result is given (`computed<Row[]>`, a child's `Row[]` prop: `:rows="items.map((x) => ({ … }))"`), a child's prop typed with an interface (`:item="{ id, label }"`, `v-bind="{ item: { … } }"`), an annotated `const`, `satisfies`; shorthand keys, computed keys the compiler knows (a constant, an enum member), nested literals and array literals of them, a spread of the same type before the other keys (`{ ...card, title }`), optional fields left out or `undefined`, `null` for a `T \| null` field, a member of a field's union of string literals as its enum's variant (`{ size: "md" }`). An untyped literal is refused with a message that says how to type it. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html#object-literals) guide |
234
+ | Loaders | A top-level `await` in `<script setup>` of a loader declared with `defineLoader`, whose arguments are strings, numbers and booleans and whose result is any type a prop may have, dates inside it included: the compiler writes a `Loaders` trait the Rust app implements, and the render stops where Vue's would until the result is in, starting siblings' loaders together. The results travel in the page for the client to hydrate from, inside a `<Suspense>` `mountPage` adds. See the crate's [`loaders`](https://docs.rs/ferrovue/latest/ferrovue/guide/loaders/index.html) guide |
228
235
  | Provide and inject | `provide` and `inject` resolved through the component tree, slot content seeing the providers of the component that renders it, as Vue does: keys that are string literals or `InjectionKey` symbols exported from `.ts` files; values that are props, refs, `computed`s, constants or objects of them (an interface's fields, `reactive()` unwrapping refs); defaults, factory defaults (`inject(k, () => …, true)`), object defaults built as the key's interface (`inject(k, { size: "md" })`), and keys holding functions, which only event handlers may call. A page rendered from Rust passes the generated `Provides`, empty or holding what the client app gives `app.provide`. See the crate's [`provide_inject`](https://docs.rs/ferrovue/latest/ferrovue/guide/provide_inject/index.html) guide |
229
236
  | Text | `{{ }}` of strings, integers and booleans; `+`, `-`, `*`, `/`, `%`, with integers and fractions as JavaScript computes them; `<`, `>`, `<=`, `>=` (between numbers, or between strings by UTF-16 code unit), `===`, `!==`; unary `-`, `!`; `??`, `\|\|`, `&&`, `?:` (whose branches may differ in type where the value is only written out: in `{{ }}`, a string-valued attribute and a template literal, each branch is written as JavaScript writes it, `{{ n > 0 ? n : "none" }}`); template literals and `String()` of strings, numbers, booleans, values that may be absent (`undefined` or `null`, as JavaScript writes them) and lists (their items joined by `,`); `typeof`; optional chaining `a?.b`, `a?.[i]` and chains that go on past it (`a?.b.c`, `s?.trim().length`, `list?.at(-1)?.name`), with every property and method the value has when present, `undefined` as a whole once `a` is absent; `.length`; `.toString()`, `.toFixed()`, `.toPrecision()` and `.toExponential()` (with no argument or a literal one), `Math.max`/`min` (of a spread list too, `Math.max(...xs)`)/`abs`/`round`/`floor`/`ceil`/`trunc`/`sqrt`/`sign`, the constants of `Math` and `Number`, `**` and `Math.pow()` of an integer and a whole exponent known when compiling, exact within ±2⁵³; `Number()`, `parseInt()` (no radix, 10 or 16), `parseFloat()`; `Number.isNaN()`, `Number.isFinite()`, `Number.isInteger()` and `Number.isSafeInteger()` of a number, present or optional, and `isNaN()` and `isFinite()` of a present number; `JSON.stringify()` of strings, numbers, booleans and lists of those; `encodeURIComponent()` and `encodeURI()`; array literals |
230
- | Strings | `.trim()`, `.trimStart()`, `.trimEnd()`, `.trimLeft()`, `.trimRight()`, `.toUpperCase()`, `.toLowerCase()`, `.includes()`, `.startsWith()`, `.endsWith()`, `.indexOf()` and `.lastIndexOf()`, with a position or without, `.slice()`, `.substring()`, `.substr()`, `.at()`, `.charAt()`, `.charCodeAt()`, `.codePointAt()`, `.concat()`, `.split()` with a limit or without, `.replace()` and `.replaceAll()` with string patterns (`$&`, `` $` ``, `$'`, `$$` read as JavaScript reads them), `.padStart()`, `.padEnd()`, `.repeat()`; indices and lengths count UTF-16 code units, as JavaScript's do (see the crate's [`strings`](https://docs.rs/ferrovue/latest/ferrovue/guide/strings/index.html) guide) |
231
- | Dates | `new Date(s)` and `Date.parse(s)` of an ISO string (a date alone, or a date and time with a `Z` or an offset, as ECMAScript's date time string format specifies), `new Date(ms)`, `Date.UTC(…)` of one to seven parts, `.toISOString()`, `.toUTCString()`, `.toJSON()`, `JSON.stringify()`, `.getTime()`, `.valueOf()` and the `.getUTC…()` methods, and `<`, `<=`, `>` and `>=` between two dates, invalid dates included, as JavaScript computes them, a date's validity tested with `Number.isNaN(d.getTime())`, and dates chosen with `?:`, `??` and `||` (a date is always truthy, an invalid one too), of a date built in the template or of a `Date` prop, which an island's `data-props` carries as its ISO string and the client turns back into a `Date` before hydrating, and which may be handed to a child's `Date` prop. Prices and formatted dates are written in Rust and passed as strings, or filled into a `$t` message. See the crate's [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html) guide |
232
- | Conditions | `v-if` / `v-else-if` / `v-else`, `?:`, `&&` and `||`, with optional and nullable values narrowed as TypeScript narrows them: `v-if="user"`, `user !== undefined`, `user !== null`, `user != null`, `typeof user !== "undefined"`, and `!user`, `user === undefined` or `user === null` for the `v-else` |
237
+ | Strings | `.trim()`, `.trimStart()`, `.trimEnd()`, `.trimLeft()`, `.trimRight()`, `.toUpperCase()`, `.toLowerCase()`, `.includes()`, `.startsWith()`, `.endsWith()`, `.indexOf()` and `.lastIndexOf()`, with a position or without, `.slice()`, `.substring()`, `.substr()`, `.at()`, `.charAt()`, `.charCodeAt()`, `.codePointAt()`, `.concat()`, `.split()` with a limit or without, `.replace()` and `.replaceAll()` with string patterns (`$&`, `` $` ``, `$'`, `$$` read as JavaScript reads them), `.padStart()`, `.padEnd()`, `.repeat()`, `s[i]`; indices and lengths count UTF-16 code units, as JavaScript's do; a string known to hold ASCII alone (`String(n)`, `n.toFixed(2)`, literals and what is built of them) may be cut into characters and joined again, as a thousands separator is (`String(n).split("").reduce(…)`, slices) (see the crate's [`strings`](https://docs.rs/ferrovue/latest/ferrovue/guide/strings/index.html) guide) |
238
+ | Dates | `new Date(s)` and `Date.parse(s)` of an ISO string (a date alone, or a date and time with a `Z` or an offset, as ECMAScript's date time string format specifies), `new Date(ms)`, `Date.UTC(…)` of one to seven parts, `.toISOString()`, `.toUTCString()`, `.toJSON()`, `JSON.stringify()`, `.getTime()`, `.valueOf()` and the `.getUTC…()` methods, and `<`, `<=`, `>` and `>=` between two dates, invalid dates included, as JavaScript computes them, a date's validity tested with `Number.isNaN(d.getTime())`, and dates chosen with `?:`, `??` and `||` (a date is always truthy, an invalid one too), of a date built in the template or of a `Date` prop, which an island's `data-props` carries as its ISO string and the client turns back into a `Date` before hydrating, and which may be handed to a child's `Date` prop. A date inside a list (`Date[]`, `(Date \| null)[]`), a dictionary (`Record<string, Date>`) or an interface of the props or of a loader's result (`rows: Row[]` with `at: Date`) is read as a prop's date is, walked with `v-for` and the list methods that do not compare or write dates as text, built in an object literal, and turned back into a `Date` in the browser from a plan the compiler writes beside the props (`data-dates`) and the loaders' results. Prices and formatted dates are written in Rust and passed as strings, or filled into a `$t` message. See the crate's [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html) guide |
239
+ | Conditions | `v-if` / `v-else-if` / `v-else`, `?:`, `&&` and `||`, with optional and nullable values narrowed as TypeScript narrows them: `v-if="user"`, `user !== undefined`, `user !== null`, `user != null`, `typeof user !== "undefined"`, and `!user`, `user === undefined` or `user === null` for the `v-else`; a test narrows every operand after it in a chain (`n != null && n > 1 && n < 9`, `n == null \|\| n < 0 \|\| n > 9`, `n != null && n > 1 ? n * 2 : 0`), and `a && b` gives `undefined` where `a`, whose present values are all truthy, is absent (`kind && ICONS[kind]`); a string of a union of string literals narrowed the same way, by `===` and `!==` with a literal, by its truth and by `||`, so that a `ref<Tone | "">` reaches a child's `Tone` prop as `tone \|\| undefined`, `tone === '' ? undefined : tone`, under `v-if="tone !== ''"` or through a `computed` annotated `Tone \| undefined` |
233
240
  | Lists | `v-for` over arrays (of strings, numbers, objects or child props), array literals and number ranges (`n in 5`); with an index; with destructured items (`{ id, name } in rows`); nested; on `<template>`. `.includes()` (a number looked for in a list of numbers whether either is a `number` or a `Float`, as JavaScript compares them), `.join()`, and `.filter()`, `.map()`, `.some()`, `.every()`, `.find()`, `.findIndex()`, `.findLast()`, `.findLastIndex()` with an arrow function of the item (a name, or destructured) and its index, whose body is an expression or a body of statements (see Statements), narrowing inside as outside, or with `Boolean`; `.filter(Boolean)` of a list or an array literal whose items may be `null` or `undefined`, which keeps the ones present and truthy as a list of the values (`[name, title, city].filter(Boolean).join(" · ")`); `Boolean(x)`; `.slice()`; an item by its index, `list[i]` with an integer `i` and `list.at(i)` (from the end when negative), `undefined` outside the list and narrowed as other optional values are (`v-if="rows[0]"`); `.indexOf()` and `.lastIndexOf()` of a string, number or boolean, compared with `===` (`NaN` found nowhere, `-0` found as `0`); `.concat()` of lists of one item type (numbers whether `number` or `Float`); `.reduce()` and `.reduceRight()` with an initial value, into a string, a number or a boolean; `.toReversed()`, and `.toSorted()` by `String(x)` as JavaScript sorts without a comparator (`[10, 9, 1]` to `[1, 10, 9]`) or by a comparator `(a, b) => a - b`, `a.k - b.k` of integers or `?:` over `<` and `>` of integers or strings, stable as JavaScript's; `[...list].sort(…)` and `[...list].reverse()`; spread in array literals (`[...a, x, ...b]`) of one item type; `.flat()` of lists of lists, to a literal depth or `Infinity`, and `.flatMap()` with an arrow function that gives a list (flattened one level) or a value (kept as `.map()` keeps it); chained and nested; a computed list read by `v-for`, `.join()`, `.length`, `.includes()`, kept by `computed`, or handed to a child |
234
- | Dictionaries | `v-for="(value, key, index) in r"` over a `Record<string, T>`, and `([key, value], index) in Object.entries(r)`, in JavaScript's order: keys that are array indices first, in numeric order, then the others as the props give them; `Object.keys(r)`, `Object.values(r)` (of a record of lists too, which `.flat()` joins), and `Object.entries(r)` read by the list methods, whose arrow functions destructure each pair (`([key, value]) => …`, `([, value]) => …`, `([key, { id }]) => …`) or read its parts (`pair[0]`, `pair[1]`), and walked by `v-for="[key, value] in Object.entries(r).filter(…)"`, kept by `computed` and sorted by a comparator of either part; handed to a child |
241
+ | Dictionaries | `v-for="(value, key, index) in r"` over a `Record<string, T>`, and `([key, value], index) in Object.entries(r)`, in JavaScript's order: keys that are array indices first, in numeric order, then the others as the props give them; `Object.keys(r)`, `Object.values(r)` (of a record of lists too, which `.flat()` joins), `Object.hasOwn(r, key)`, and `Object.entries(r)` read by the list methods, whose arrow functions destructure each pair (`([key, value]) => …`, `([, value]) => …`, `([key, { id }]) => …`) or read its parts (`pair[0]`, `pair[1]`), and walked by `v-for="[key, value] in Object.entries(r).filter(…)"`, kept by `computed` and sorted by a comparator of either part; handed to a child |
235
242
  | Attributes | static and bound attributes, boolean attributes, `:hidden`, `data-*` and `aria-*`, `v-bind` objects |
236
243
  | `class` | strings, arrays, objects (`{ active: on }`, computed keys), `cond && "x"`, `cond ? "x" : null`, merged with a static `class` |
237
244
  | `style` | objects (camelCase or kebab-case keys, `--custom` properties), arrays of objects, strings, merged with a static `style`, and `v-show`; later values override earlier ones as in Vue. A global `<style>` block is allowed |
238
245
  | Scoped styles | `<style scoped>`: the id on every element, on child components' roots (a root that is itself a component, fragments, recursion and `inheritAttrs: false` as Vue renders them) and, from a component with `:slotted()` rules, on the slot content it is given, forwarded slots included; inside `<Transition>`, `<KeepAlive>`, `<Teleport>` and `v-if`; on `<RouterLink>` and what it holds, as vue-router renders them |
239
246
  | `v-bind()` in `<style>` | variables bound to props, setup values, `computed`s and constants, quoted expressions included (`v-bind('size + "px"')`), in a scoped or global `<style>`: written into the root's `style` after its own and what a parent passes, on each root of a fragment, and handed to a root that is a component, as Vue's server writes them; `undefined` and `null` as `initial`, an empty string as a space; named as plugin-vue names them for production or development (`isProduction`) |
240
247
  | CSS modules | `<style module>` and `<style module="name">`, read as `$style.x`, `name.x`, `$style['x-y']` and through `useCssModule()`, each class a constant in the generated code, when `cssModules` mirrors Vite's `css.modules.generateScopedName`: computed as postcss-modules computes it in Vite (`[local]`, `[name]`, `[hash:base64:5]`, `[path]` and the rest), composed classes, `@value`s and keyframes included |
241
- | Components | imported child components, `v-bind` of a child's own `Props`, listeners in any form (`@x`, `v-on="obj"`, `@[name]`), which the server skips, `v-model` on a child's `defineModel`, recursion; props named in `kebab-case` or `camelCase`; `defineAsyncComponent(() => import("./X.vue"))` (or `{ loader: … }`), rendered as the component it loads, which Vue's server renderer waits for |
242
- | `<component :is>` | over a closed set of choices, compiled to a `match`: an imported component (`:is="Card"`), an HTML element's name (`is="h2"`), a `computed` or `?:` choosing among them, a prop typed as a union of string literals (`as: "h1" \| "h2"`), and an object of imported components (or element names) declared in setup or imported from a `.ts` file, read by such a prop (`ICONS[name]`); props, fallthrough attributes, slots and scope ids reach each choice as they reach a static child; also inside `<KeepAlive>` and `<Transition>`. See the crate's [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#component-is) guide |
243
- | Escape hatches | `<ClientOnly>` from `ferrovue/client`, whose default slot the server never renders, so anything may go in it; components listed in `twins`, rendered by Rust functions of yours. See the crate's [`errors_and_limits`](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#client-only-content) guide |
248
+ | Components | imported child components, `v-bind` of a child's own `Props`, listeners in any form (`@x`, `v-on="obj"`, `@[name]`), which the server skips, `v-model` on a child's `defineModel`, recursion; props named in `kebab-case` or `camelCase`; props named like listeners (`on1`, `onSale`), left out when falsy where Vue's `mergeProps` joins a child's props, as Vue leaves them out; `defineAsyncComponent(() => import("./X.vue"))` (or `{ loader: … }`), rendered as the component it loads, which Vue's server renderer waits for |
249
+ | `<component :is>` | over a closed set of choices, compiled to a `match`: an imported component (`:is="Card"`), an HTML element's name (`is="h2"`), a `computed` or `?:` choosing among them, a prop typed as a union of string literals (`as: "h1" \| "h2"`, a `match` over its enum's variants), and an object of imported components (or element names) declared in setup or imported from a `.ts` file, read by such a prop (`ICONS[name]`); props, fallthrough attributes, slots and scope ids reach each choice as they reach a static child; also inside `<KeepAlive>` and `<Transition>`. See the crate's [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#component-is) guide |
250
+ | Escape hatches | `<ClientOnly>` from `ferrovue`, whose default slot the server never renders, so anything may go in it; components listed in `twins`, rendered by Rust functions of yours. See the crate's [`errors_and_limits`](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#client-only-content) guide |
244
251
  | Fallthrough attributes | what a parent passes a child beyond its props (static and bound attributes, `class`, `style`, `data-*`, `aria-*`, booleans, `undefined`): onto its single root, merged with the root's own class and style and replacing its other attributes where they stand, as Vue's `mergeProps` merges them; none for two roots; on through a root that is a component, or a `<RouterLink>`; with `inheritAttrs: false`, onto the elements or components that bind `v-bind="$attrs"` or a `useAttrs()` binding, before or after their own; beside scope ids. Listeners are dropped, as Vue's server drops them. See the crate's [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#fallthrough-attributes) guide |
245
252
  | Slots | default and named slots, fallbacks, `$slots.name` tests and the same through `useSlots()`, scoped slots (`<slot :item="x">` and `#item="{ item }"` or `v-slot="props"`), whose props a parent can hand to its own children |
246
- | Forms | `v-model` on text inputs, checkboxes, radios, `<select>` and `<textarea>` (renders the initial state) |
247
- | Built-ins | `<Transition>`, `<TransitionGroup>`, `<KeepAlive>`, `<Suspense>` (synchronous content), `<Teleport>` (to any target, nested, disabled: see below), `v-text`, `v-once`, `v-pre`, `v-memo`, custom directives listed in `clientDirectives` |
253
+ | Forms | `v-model` on text inputs, checkboxes, radios, `<select>` and `<textarea>` (renders the initial state); a `<textarea>`, or an `<input>` with `v-model`, sits inside another element when it would be the component's root (FV0424) |
254
+ | Built-ins | `<Transition>`, `<TransitionGroup>`, `<KeepAlive>`, `<Suspense>` (synchronous content), `<Teleport>` (to any target, nested, disabled: see below), `v-text`, `v-once`, `v-pre`, `v-memo`, custom directives listed in `clientDirectives`, on an element with no content when its attributes are bound by name and it is not the component's root (FV0425) |
248
255
  | Vue Router | `<RouterLink>` (resolved by name or imported) with a string `to` or `{ name, params, query, hash }` / `{ path, query, hash }`, `active-class`, `exact-active-class`, `aria-current-value`, `replace`; vue-router's own encoding and active-link matching, nested routes included (a parent link is active on its children's pages, exact only on its own); a history base; optional parameters (`:id?`); routes built from a folder of pages, which the Vite plugin also gives the client as `ferrovue/routes`. `useRoute()` and `$route`: `path`, `fullPath`, `hash`, `name`, `params`, and `query` (a value written once, without `=`, or repeated, exactly as vue-router parses it; `typeof route.query.q === "string"` narrows one to a single string). `<RouterView>`, at the top and in nested route components: each takes the page it shows as a slot |
249
256
  | Pinia | option stores with a typed `state`, and setup stores (`defineStore(id, () => { … })`) whose returned refs are typed by `ref<T>()` or their initial literal; read through `useX()` or `storeToRefs`, in the template or in `computed`; getters that are an expression of the state, and a setup store's computeds, which may read each other |
250
257
  | vue-i18n | `$t` and `useI18n()`'s `t` and `locale`: named and list values, plurals by vue-i18n's rule, literals, linked messages with `upper`/`lower`/`capitalize`, nested and flat keys, fallback locales, a missing key shown as itself. Messages are parsed at build time by vue-i18n's own compiler |
251
- | `v-html` | only on a `TrustedHtml` prop (`import type { TrustedHtml } from "ferrovue/types"`), or an `InlineHtml` one (inline tags only, `ferrovue::InlineHtml` on the server, with no `trustedHtml` needed), the only HTML it may write inside a `<p>`; never on a table's structure, a `<select>` or `<optgroup>`, or an SVG or MathML element that holds no HTML. See the crate's [`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html#where-v-html-may-go) guide |
252
- | Page head | `useHead` (and `useServerHead`) from `@unhead/vue` 3: `title`, `titleTemplate`, `base`, `meta`, `link`, `script`, `style`, `noscript`, `htmlAttrs` and `bodyAttrs`, with values the server computes (props, setup bindings, getters such as `() => props.title`, `list.map(x => ({ … }))` for a list of tags), `key`, `tagPosition`, `tagPriority` and `tagDuplicateStrategy`; `useSeoMeta` (and `useServerSeoMeta`) of string, number, boolean and `null` values. Collected into a `ferrovue::Head` in the order Vue's server runs them, and written as unhead's `renderSSRHead` writes them: its tag order, deduplication, title template and escaping. See the crate's [`head`](https://docs.rs/ferrovue/latest/ferrovue/guide/head/index.html) guide |
258
+ | `v-html` | only on a `TrustedHtml` prop (`import type { TrustedHtml } from "ferrovue/types"`), or an `InlineHtml` one (inline tags only, `ferrovue::html::InlineHtml` on the server, with no `trustedHtml` needed), the only HTML it may write inside a `<p>`; never on a table's structure, a `<select>` or `<optgroup>`, or an SVG or MathML element that holds no HTML. See the crate's [`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html#where-v-html-may-go) guide |
259
+ | Page head | `useHead` (and `useServerHead`) from `@unhead/vue` 3: `title`, `titleTemplate`, `base`, `meta`, `link`, `script`, `style`, `noscript`, `htmlAttrs` and `bodyAttrs`, with values the server computes (props, setup bindings, getters such as `() => props.title`, `list.map(x => ({ … }))` for a list of tags), `key`, `tagPosition`, `tagPriority` and `tagDuplicateStrategy`; `useSeoMeta` (and `useServerSeoMeta`) of string, number, boolean and `null` values. Collected into a `ferrovue::head::Head` in the order Vue's server runs them, and written as unhead's `renderSSRHead` writes them: its tag order, deduplication, title template and escaping. See the crate's [`head`](https://docs.rs/ferrovue/latest/ferrovue/guide/head/index.html) guide |
253
260
 
254
261
  Anything else is refused at compile time, with an error that names the file, line and column,
255
262
  quotes the line and puts a caret under the construct. The
256
263
  [`errors_and_limits`](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#refused-at-compile-time)
257
- guide lists what is refused and why, and
264
+ guide lists what is refused and why, and what a render refuses as it runs, where only the props
265
+ show the value cannot be written exactly (a date string outside the format above, an integer beyond
266
+ ±2⁶³ handed to a child's `number` prop), and
258
267
  [`error_codes`](https://docs.rs/ferrovue/latest/ferrovue/guide/error_codes/index.html) every code.
259
268
 
260
269
  ## Guides
@@ -298,7 +307,9 @@ The crate's guide, `ferrovue::guide` on docs.rs, covers each topic in depth:
298
307
  from a Node server that renders with Vue, and the
299
308
  [support policy](https://docs.rs/ferrovue/latest/ferrovue/guide/support/index.html): supported
300
309
  versions, fixes, deprecations and where to report a problem
301
- - Upgrading a project [from 0.9 to 0.10](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_10/index.html),
310
+ - Upgrading a project [from 0.11 to 0.12](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_12/index.html),
311
+ [from 0.10 to 0.11](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_11/index.html),
312
+ [from 0.9 to 0.10](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_10/index.html),
302
313
  [from 0.8 to 0.9](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_9/index.html)
303
314
  and [from 0.7 to 0.8](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_8/index.html)
304
315
 
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GenError } from "./model.ts";
1
+ import { GenError } from "./errors.ts";
2
2
  import type { Deprecation } from "./schema.ts";
3
3
  /** A command-line option: its names, short one first, the value it takes, and its line in `--help`. */
4
4
  export interface Flag {
package/dist/args.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GenError } from "./model.js";
1
+ import { GenError } from "./errors.js";
2
2
  /** The commands `ferrovue` takes, with their lines in `--help`. */
3
3
  export const COMMANDS = {
4
4
  init: "scaffold starter ferrovue.config.json and components",
package/dist/attrs.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, isSVGTag, propsToAttrMap } from "@vue/shared";
2
- import { fail, nothing, GenError, rustStr } from "./model.js";
3
- import { CONFIG_FILE, ctx } from "./context.js";
2
+ import { fail, nothing, rustStr } from "./model.js";
3
+ import { GenError } from "./errors.js";
4
+ import { asWritten } from "./numbers.js";
5
+ import { CONFIG_FILE } from "./config.js";
6
+ import { ctx } from "./context.js";
4
7
  import { describeTy, expr, shown } from "./expr.js";
5
8
  import { known, truthy } from "./narrowing.js";
6
9
  import { asCow, isTemporary, unquote } from "./strings.js";
7
- import { atom, bare, condition, receiver, strArg } from "./parens.js";
10
+ import { atom, bare, condition, receiver, scrutinee, strArg } from "./parens.js";
8
11
  import { Emitter } from "./emitter.js";
9
12
  import { isDollarAttrs } from "./fallthrough.js";
10
13
  import { claim } from "./plugin.js";
@@ -19,9 +22,10 @@ export function display(e, v) {
19
22
  else if (v.ty.k === "bool" && v.konst !== undefined)
20
23
  e.lit(String(v.konst));
21
24
  else if (v.ty.k === "str")
22
- e.stmt(`fv::escape_into(out, ${strArg(v.code)});`);
25
+ e.stmt(`fv::core::escape_into(out, ${strArg(v.code)});`);
23
26
  else if (v.ty.k === "int" || v.ty.k === "float") {
24
- e.stmt(`fv::${v.ty.k === "int" ? "push_int" : "push_number"}(out, ${bare(v.code)});`);
27
+ const w = asWritten(v);
28
+ e.stmt(`fv::core::${w.ty.k === "int" ? "push_int" : "push_number"}(out, ${bare(w.code)});`);
25
29
  e.expect(NUMBER_BYTES);
26
30
  }
27
31
  else if (v.ty.k === "bool")
@@ -38,7 +42,7 @@ export function interpolate(s, e, v, n) {
38
42
  case "null":
39
43
  return;
40
44
  case "opt":
41
- e.open(`if let Some(v) = ${v.code}`);
45
+ e.open(`if let Some(v) = ${scrutinee(v.code)}`);
42
46
  interpolate(s, e, { code: "v", ty: v.ty.of }, n);
43
47
  e.close();
44
48
  return;
@@ -66,7 +70,7 @@ export function renderAttr(s, e, key, v, n) {
66
70
  if (nothing(v.ty))
67
71
  return;
68
72
  if (v.ty.k === "opt") {
69
- e.open(`if let Some(v) = ${v.code}`);
73
+ e.open(`if let Some(v) = ${scrutinee(v.code)}`);
70
74
  renderAttr(s, e, key, { code: "v", ty: v.ty.of }, n);
71
75
  e.close();
72
76
  return;
@@ -101,7 +105,7 @@ export function renderDynamicAttr(s, e, key, v, n, tag = null) {
101
105
  return;
102
106
  }
103
107
  if (v.ty.k === "opt") {
104
- e.open(`if let Some(v) = ${v.code}`);
108
+ e.open(`if let Some(v) = ${scrutinee(v.code)}`);
105
109
  renderDynamicAttr(s, e, key, { code: "v", ty: v.ty.of }, n, tag);
106
110
  e.close();
107
111
  return;
@@ -186,7 +190,7 @@ export function renderAttrs(s, e, n) {
186
190
  else
187
191
  e.close(" else {");
188
192
  if (parts.length === 1 && parts[0] === n) {
189
- e.stmt(`fv::passed_attrs_into(out, ${s.fallthrough}.list(), ${isAttrs(n) && s.attrs !== null ? s.attrs : '""'});`);
193
+ e.stmt(`fv::attrs::passed_attrs_into(out, ${s.fallthrough}.list(), ${isAttrs(n) && s.attrs !== null ? s.attrs : '""'});`);
190
194
  e.close();
191
195
  return;
192
196
  }
@@ -207,7 +211,7 @@ export function renderAttrs(s, e, n) {
207
211
  fail(s.comp, "FV0407", "attributes must be an object literal", p);
208
212
  }
209
213
  const ids = idsAt >= 0 && s.attrs !== null ? s.attrs : '""';
210
- e.stmt(`fv::attrs_into(out, &[${sources.join(", ")}], ${idsAt >= 0 ? idsAt : sources.length}, ${ids});`);
214
+ e.stmt(`fv::attrs::attrs_into(out, &[${sources.join(", ")}], ${idsAt >= 0 ? idsAt : sources.length}, ${ids});`);
211
215
  e.close();
212
216
  }
213
217
  function staticAttrs(s, e, n) {
@@ -296,9 +300,6 @@ export function mergeProps(s, n) {
296
300
  else if (had && (key === "class" || key === "style")) {
297
301
  merged.set(key, { ...p, value: { type: "ArrayExpression", elements: [had.value, p.value], fvMerged: true } });
298
302
  }
299
- else if (had) {
300
- merged.set(key, { ...had, value: p.value });
301
- }
302
303
  else
303
304
  merged.set(key, p);
304
305
  }
@@ -325,27 +326,27 @@ export function attrOf(s, key, n, side) {
325
326
  if (key === "style")
326
327
  return styleAttr(s, n);
327
328
  if (n.type === "NullLiteral")
328
- return "fv::Attr::Undefined";
329
+ return "fv::attrs::Attr::Undefined";
329
330
  return valueAttr(s, attrShown(s, key, n), n);
330
331
  }
331
332
  export function valueAttr(s, v, n) {
332
333
  switch (v.ty.k) {
333
334
  case "str":
334
- return isTemporary(v) ? `fv::Attr::Str(${asCow(v)})` : `fv::Attr::str(${strArg(v.code)})`;
335
+ return isTemporary(v) ? `fv::attrs::Attr::Str(${asCow(v)})` : `fv::attrs::Attr::str(${strArg(v.code)})`;
335
336
  case "int":
336
- return `fv::Attr::Int(${bare(v.code)})`;
337
+ return `fv::attrs::Attr::Int(${bare(v.code)})`;
337
338
  case "float":
338
- return `fv::Attr::Float(${bare(v.code)})`;
339
+ return `fv::attrs::Attr::Float(${bare(v.code)})`;
339
340
  case "bool":
340
- return `fv::Attr::Bool(${bare(v.code)})`;
341
+ return `fv::attrs::Attr::Bool(${bare(v.code)})`;
341
342
  case "undef":
342
343
  case "null":
343
- return "fv::Attr::Undefined";
344
+ return "fv::attrs::Attr::Undefined";
344
345
  case "opt": {
345
346
  const of = v.ty.of.k;
346
- const make = of === "str" ? "fv::Attr::str" : of === "int" ? "fv::Attr::Int" : of === "float" ? "fv::Attr::Float" : of === "bool" ? "fv::Attr::Bool" : null;
347
+ const make = of === "str" ? "fv::attrs::Attr::str" : of === "int" ? "fv::attrs::Attr::Int" : of === "float" ? "fv::attrs::Attr::Float" : of === "bool" ? "fv::attrs::Attr::Bool" : null;
347
348
  if (make !== null)
348
- return `${atom(v.code)}.map_or(fv::Attr::Undefined, ${make})`;
349
+ return `${atom(v.code)}.map_or(fv::attrs::Attr::Undefined, ${make})`;
349
350
  break;
350
351
  }
351
352
  default:
package/dist/blocks.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { type Component, type N, type Scope, type Val } from "./model.ts";
2
- /** Runtime calls that panic where JavaScript throws, or user code that may. */
2
+ /** Runtime calls that refuse where JavaScript throws or a value cannot be held, or user code that
3
+ * may panic. */
3
4
  export declare const PANICS: RegExp;
5
+ /** Whether `code` calls a runtime routine that may refuse the render. */
6
+ export declare function refuses(code: string): boolean;
4
7
  export declare function panicky(code: string): boolean;
5
8
  /** How a value is held in a Rust `let`: what the `let` is given, how the name reads back, and
6
9
  * whether the `let` owns what a read borrows (a `String`, a `Cow`, a `Vec`). */
@@ -15,16 +18,21 @@ export declare function bindingOf(v: Val): {
15
18
  export declare const PATH_BUDGET = 4096;
16
19
  /** The expression a function's body computes: the expression of an arrow function, or a block of
17
20
  * statements in the subset ferrovue translates, written as one expression. */
18
- export declare function bodyExpr(comp: Component, fn: N): N;
21
+ export declare function bodyExpr(comp: Component, fn: N, whose?: string): N;
19
22
  /** `p` (a name, or an object or array pattern of names) bound to `value` around `body`. */
20
23
  export declare function destructured(comp: Component, p: N, value: N, body: N): N;
21
24
  /** `fn` with an expression for its body, where it is an arrow or function expression with a block. */
22
25
  export declare function expressionBodied(comp: Component, fn: N): N;
26
+ export declare function reps(v: Val): string[];
23
27
  /** `const name = init` around `body`: the value `body` computes with `name` bound once, as a Rust
24
28
  * `let` in a block that hands out nothing borrowed from it. */
25
29
  export declare function letIn(s: Scope, n: N): Val;
26
30
  /** `v` bound once to `name` for `body`, which is given the scope that reads it. */
27
- export declare function bound(s: Scope, local: string, v: Val, then: (inner: Scope) => Val): Val;
31
+ export declare function bound(s: Scope, local: string, v: Val, then: (inner: Scope) => Val, hint?: string): Val;
32
+ /** `body` after the Rust statements `lead`, as a block that hands out nothing borrowed from what
33
+ * they declare: `owned` when a statement owns what `body` may borrow (a `String`, a `Cow`, a
34
+ * `Vec`), `ownedUses` when it holds a struct or a list `body` reads. */
35
+ export declare function around(lead: string, body: Val, owned: boolean, ownedUses?: boolean): Val;
28
36
  /** Whether a desugared `if` whose test is `a && b` or `a || b` should be split, so that a side
29
37
  * that narrows a value narrows what follows it, as TypeScript narrows it. */
30
38
  export declare function splits(s: Scope, test: N): boolean;