ferrovue 0.11.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.
- package/README.md +20 -17
- package/dist/args.d.ts +1 -1
- package/dist/args.js +1 -1
- package/dist/attrs.js +16 -14
- package/dist/blocks.js +3 -2
- package/dist/calls.js +21 -21
- package/dist/children.js +47 -12
- package/dist/classes.js +9 -9
- package/dist/cli.js +3 -1
- package/dist/client.d.ts +28 -7
- package/dist/client.js +84 -4
- package/dist/compiler.d.ts +11 -6
- package/dist/compiler.js +8 -5
- package/dist/component.js +9 -3
- package/dist/config.d.ts +128 -0
- package/dist/config.js +32 -0
- package/dist/conformance.d.ts +6 -43
- package/dist/conformance.js +4 -14
- package/dist/constants.js +116 -20
- package/dist/context.d.ts +9 -126
- package/dist/context.js +22 -31
- package/dist/date-plans.d.ts +11 -0
- package/dist/date-plans.js +10 -0
- package/dist/diagnostics.d.ts +3 -3
- package/dist/diagnostics.js +1 -1
- package/dist/dynamic.js +10 -7
- package/dist/editor.js +3 -2
- package/dist/enums.d.ts +36 -5
- package/dist/enums.js +200 -23
- package/dist/error-areas.d.ts +5 -0
- package/dist/error-areas.js +24 -0
- package/dist/errors.d.ts +75 -23
- package/dist/errors.js +82 -41
- package/dist/expr.d.ts +2 -1
- package/dist/expr.js +73 -27
- package/dist/file-routes.d.ts +2 -15
- package/dist/file-routes.js +2 -1
- package/dist/files.d.ts +3 -3
- package/dist/files.js +2 -1
- package/dist/functions.js +16 -3
- package/dist/inputs.d.ts +1 -1
- package/dist/inputs.js +1 -0
- package/dist/link-router.d.ts +1 -2
- package/dist/link-router.js +1 -2
- package/dist/lists.js +20 -19
- package/dist/loads.d.ts +6 -25
- package/dist/loads.js +1 -84
- package/dist/loops.js +19 -6
- package/dist/model.d.ts +47 -37
- package/dist/model.js +89 -41
- package/dist/narrowing.d.ts +8 -0
- package/dist/narrowing.js +51 -4
- package/dist/numbers.js +2 -2
- package/dist/objects.js +8 -4
- package/dist/page-routes.d.ts +1 -2
- package/dist/peer.d.ts +3 -0
- package/dist/peer.js +17 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +1 -0
- package/dist/plugins/client-only.js +2 -2
- package/dist/plugins/css-modules.js +2 -1
- package/dist/plugins/dates.js +9 -9
- package/dist/plugins/head.js +23 -23
- package/dist/plugins/i18n.js +4 -4
- package/dist/plugins/loaders.js +22 -21
- package/dist/plugins/router-link.js +12 -12
- package/dist/plugins/router.d.ts +1 -1
- package/dist/plugins/router.js +14 -12
- package/dist/plugins/scoped.d.ts +1 -1
- package/dist/plugins/scoped.js +2 -2
- package/dist/plugins/stores.js +2 -1
- package/dist/plugins/teleport.js +5 -5
- package/dist/plugins/twins.d.ts +1 -1
- package/dist/plugins/twins.js +6 -4
- package/dist/resolve.d.ts +0 -10
- package/dist/rust.d.ts +1 -1
- package/dist/rust.js +41 -34
- package/dist/schema.js +2 -1
- package/dist/script.js +49 -4
- package/dist/slots.js +6 -5
- package/dist/strings.d.ts +2 -2
- package/dist/strings.js +24 -22
- package/dist/styles.js +16 -22
- package/dist/template.js +19 -12
- package/dist/{fixture.d.ts → testing-browser.d.ts} +26 -13
- package/dist/{hydration.js → testing-browser.js} +102 -1
- package/dist/testing.d.ts +42 -5
- package/dist/testing.js +14 -4
- package/dist/types.d.ts +1 -1
- package/dist/typescript.js +9 -7
- package/dist/vhtml.js +1 -1
- package/dist/vite.d.ts +14 -2
- package/dist/vite.js +3 -1
- package/package.json +22 -6
- package/schema.json +13 -5
- package/dist/fixture.js +0 -122
- package/dist/hydration.d.ts +0 -26
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -3
- /package/dist/{routes.d.ts → route-records.d.ts} +0 -0
- /package/dist/{routes.js → route-records.js} +0 -0
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/ferrovue)
|
|
5
5
|
[](https://docs.rs/ferrovue)
|
|
6
6
|
[](https://github.com/Kinsmir/ferrovue/actions/workflows/ci.yml)
|
|
7
|
+
[](https://dashboard.stryker-mutator.io/reports/github.com/Kinsmir/ferrovue/main)
|
|
7
8
|
[](#licence)
|
|
8
9
|
|
|
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)
|
|
@@ -34,7 +35,7 @@ element structure, attribute order, whitespace and fragment markers.
|
|
|
34
35
|
| | What it is | Published as |
|
|
35
36
|
|---|---|---|
|
|
36
37
|
| `packages/ferrovue` | The compiler, the `ferrovue` command, the Vite plugin and the browser client (TypeScript) | npm: [`ferrovue`](https://www.npmjs.com/package/ferrovue) |
|
|
37
|
-
| `crates/ferrovue` | The runtime the generated Rust calls: escaping, slots, islands, `<RouterLink>` matching. It
|
|
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)) |
|
|
38
39
|
|
|
39
40
|
## Quick start
|
|
40
41
|
|
|
@@ -56,7 +57,7 @@ the configuration at your project root yourself:
|
|
|
56
57
|
|
|
57
58
|
```json
|
|
58
59
|
{
|
|
59
|
-
"$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.
|
|
60
|
+
"$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.12.0/schema.json",
|
|
60
61
|
"components": "client/components",
|
|
61
62
|
"out": "src/generated"
|
|
62
63
|
}
|
|
@@ -75,8 +76,9 @@ read `$schema`. Offline, the copy in the installed package works as well:
|
|
|
75
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()` |
|
|
76
77
|
| `router` | no | Instead of `routes`: `{ routes, base?, linkActiveClass?, linkExactActiveClass? }`, matching `createWebHistory(base)` and `createRouter`'s options |
|
|
77
78
|
| `stores` | no | Directory of Pinia option stores whose state components may read |
|
|
78
|
-
| `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` |
|
|
79
|
-
| `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) |
|
|
80
82
|
| `helpers` | no | `{ module, functions }`: functions a template may call, each mapped to a Rust twin |
|
|
81
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) |
|
|
82
84
|
| `i18n` | no | vue-i18n: `{ messages, locale?, fallbackLocale? }`, the directory of locale files (`en.json`, `nl.json`), the default locale and the fallbacks |
|
|
@@ -171,7 +173,7 @@ The crate's features:
|
|
|
171
173
|
|
|
172
174
|
- `router` and `i18n`, both on by default: vue-router and vue-i18n. An application with neither
|
|
173
175
|
routes nor translations can leave them out, and build neither `ferrovue-router` nor
|
|
174
|
-
`ferrovue-i18n`: `ferrovue = { version = "0.
|
|
176
|
+
`ferrovue-i18n`: `ferrovue = { version = "0.12", default-features = false }`.
|
|
175
177
|
- `axum` and `actix-web`: `Html` is an `IntoResponse` or a `Responder`, so a handler responds with
|
|
176
178
|
the component as `text/html; charset=utf-8`. `into_html(props)` and `into_island(props)` take the
|
|
177
179
|
props by value, so a handler that builds them returns the page:
|
|
@@ -184,12 +186,12 @@ The crate's features:
|
|
|
184
186
|
|
|
185
187
|
- `stream`, which `axum` and `actix-web` turn on: `HtmlStream`, a page sent in the order its parts
|
|
186
188
|
are ready.
|
|
187
|
-
- `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
|
|
188
190
|
into a `maud::html!` page.
|
|
189
191
|
- `dioxus`: `{greeting::island(&props)}` in a Dioxus 0.7 `rsx!` is the island element itself, with
|
|
190
192
|
exactly the markup Vue hydrates inside it (`examples/dioxus`).
|
|
191
|
-
- `ammonia`: `ferrovue::Sanitised`, HTML cleaned by [ammonia](https://docs.rs/ammonia), for
|
|
192
|
-
`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
|
|
193
195
|
[`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html) guide describes
|
|
194
196
|
all three.
|
|
195
197
|
|
|
@@ -221,11 +223,11 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
|
|
|
221
223
|
|
|
222
224
|
| Area | Supported |
|
|
223
225
|
|---|---|
|
|
224
|
-
| 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), `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 prop, optional or nullable too, and inside lists, dictionaries and interfaces: `ferrovue::JsDate`, or `chrono::DateTime<Utc>` with the `chrono` feature) |
|
|
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) |
|
|
225
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 |
|
|
226
|
-
| 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`, 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), 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 |
|
|
227
|
-
| 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 |
|
|
228
|
-
| 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 |
|
|
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 |
|
|
229
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 |
|
|
230
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 |
|
|
231
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,7 +236,7 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
|
|
|
234
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 |
|
|
235
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) |
|
|
236
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 |
|
|
237
|
-
| 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]`) |
|
|
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` |
|
|
238
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 |
|
|
239
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 |
|
|
240
242
|
| Attributes | static and bound attributes, boolean attributes, `:hidden`, `data-*` and `aria-*`, `v-bind` objects |
|
|
@@ -245,7 +247,7 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
|
|
|
245
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 |
|
|
246
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 |
|
|
247
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 |
|
|
248
|
-
| Escape hatches | `<ClientOnly>` from `ferrovue
|
|
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 |
|
|
249
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 |
|
|
250
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 |
|
|
251
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) |
|
|
@@ -253,8 +255,8 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
|
|
|
253
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 |
|
|
254
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 |
|
|
255
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 |
|
|
256
|
-
| `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 |
|
|
257
|
-
| 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 |
|
|
258
260
|
|
|
259
261
|
Anything else is refused at compile time, with an error that names the file, line and column,
|
|
260
262
|
quotes the line and puts a caret under the construct. The
|
|
@@ -305,7 +307,8 @@ The crate's guide, `ferrovue::guide` on docs.rs, covers each topic in depth:
|
|
|
305
307
|
from a Node server that renders with Vue, and the
|
|
306
308
|
[support policy](https://docs.rs/ferrovue/latest/ferrovue/guide/support/index.html): supported
|
|
307
309
|
versions, fixes, deprecations and where to report a problem
|
|
308
|
-
- Upgrading a project [from 0.
|
|
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),
|
|
309
312
|
[from 0.9 to 0.10](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_10/index.html),
|
|
310
313
|
[from 0.8 to 0.9](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_9/index.html)
|
|
311
314
|
and [from 0.7 to 0.8](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_8/index.html)
|
package/dist/args.d.ts
CHANGED
package/dist/args.js
CHANGED
package/dist/attrs.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, isSVGTag, propsToAttrMap } from "@vue/shared";
|
|
2
|
-
import { fail, nothing,
|
|
2
|
+
import { fail, nothing, rustStr } from "./model.js";
|
|
3
|
+
import { GenError } from "./errors.js";
|
|
3
4
|
import { asWritten } from "./numbers.js";
|
|
4
|
-
import { CONFIG_FILE
|
|
5
|
+
import { CONFIG_FILE } from "./config.js";
|
|
6
|
+
import { ctx } from "./context.js";
|
|
5
7
|
import { describeTy, expr, shown } from "./expr.js";
|
|
6
8
|
import { known, truthy } from "./narrowing.js";
|
|
7
9
|
import { asCow, isTemporary, unquote } from "./strings.js";
|
|
@@ -20,10 +22,10 @@ export function display(e, v) {
|
|
|
20
22
|
else if (v.ty.k === "bool" && v.konst !== undefined)
|
|
21
23
|
e.lit(String(v.konst));
|
|
22
24
|
else if (v.ty.k === "str")
|
|
23
|
-
e.stmt(`fv::escape_into(out, ${strArg(v.code)});`);
|
|
25
|
+
e.stmt(`fv::core::escape_into(out, ${strArg(v.code)});`);
|
|
24
26
|
else if (v.ty.k === "int" || v.ty.k === "float") {
|
|
25
27
|
const w = asWritten(v);
|
|
26
|
-
e.stmt(`fv::${w.ty.k === "int" ? "push_int" : "push_number"}(out, ${bare(w.code)});`);
|
|
28
|
+
e.stmt(`fv::core::${w.ty.k === "int" ? "push_int" : "push_number"}(out, ${bare(w.code)});`);
|
|
27
29
|
e.expect(NUMBER_BYTES);
|
|
28
30
|
}
|
|
29
31
|
else if (v.ty.k === "bool")
|
|
@@ -188,7 +190,7 @@ export function renderAttrs(s, e, n) {
|
|
|
188
190
|
else
|
|
189
191
|
e.close(" else {");
|
|
190
192
|
if (parts.length === 1 && parts[0] === n) {
|
|
191
|
-
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 : '""'});`);
|
|
192
194
|
e.close();
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
@@ -209,7 +211,7 @@ export function renderAttrs(s, e, n) {
|
|
|
209
211
|
fail(s.comp, "FV0407", "attributes must be an object literal", p);
|
|
210
212
|
}
|
|
211
213
|
const ids = idsAt >= 0 && s.attrs !== null ? s.attrs : '""';
|
|
212
|
-
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});`);
|
|
213
215
|
e.close();
|
|
214
216
|
}
|
|
215
217
|
function staticAttrs(s, e, n) {
|
|
@@ -324,27 +326,27 @@ export function attrOf(s, key, n, side) {
|
|
|
324
326
|
if (key === "style")
|
|
325
327
|
return styleAttr(s, n);
|
|
326
328
|
if (n.type === "NullLiteral")
|
|
327
|
-
return "fv::Attr::Undefined";
|
|
329
|
+
return "fv::attrs::Attr::Undefined";
|
|
328
330
|
return valueAttr(s, attrShown(s, key, n), n);
|
|
329
331
|
}
|
|
330
332
|
export function valueAttr(s, v, n) {
|
|
331
333
|
switch (v.ty.k) {
|
|
332
334
|
case "str":
|
|
333
|
-
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)})`;
|
|
334
336
|
case "int":
|
|
335
|
-
return `fv::Attr::Int(${bare(v.code)})`;
|
|
337
|
+
return `fv::attrs::Attr::Int(${bare(v.code)})`;
|
|
336
338
|
case "float":
|
|
337
|
-
return `fv::Attr::Float(${bare(v.code)})`;
|
|
339
|
+
return `fv::attrs::Attr::Float(${bare(v.code)})`;
|
|
338
340
|
case "bool":
|
|
339
|
-
return `fv::Attr::Bool(${bare(v.code)})`;
|
|
341
|
+
return `fv::attrs::Attr::Bool(${bare(v.code)})`;
|
|
340
342
|
case "undef":
|
|
341
343
|
case "null":
|
|
342
|
-
return "fv::Attr::Undefined";
|
|
344
|
+
return "fv::attrs::Attr::Undefined";
|
|
343
345
|
case "opt": {
|
|
344
346
|
const of = v.ty.of.k;
|
|
345
|
-
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;
|
|
346
348
|
if (make !== null)
|
|
347
|
-
return `${atom(v.code)}.map_or(fv::Attr::Undefined, ${make})`;
|
|
349
|
+
return `${atom(v.code)}.map_or(fv::attrs::Attr::Undefined, ${make})`;
|
|
348
350
|
break;
|
|
349
351
|
}
|
|
350
352
|
default:
|
package/dist/blocks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { fail,
|
|
1
|
+
import { fail, nothing, UNION_STR, unionStr } from "./model.js";
|
|
2
|
+
import { GenError } from "./errors.js";
|
|
2
3
|
import { ctx } from "./context.js";
|
|
3
4
|
import { fresh, jsHint } from "./names.js";
|
|
4
5
|
import { expr, typedBy } from "./expr.js";
|
|
@@ -27,7 +28,7 @@ export function bindingOf(v) {
|
|
|
27
28
|
const lone = v.lone ? { lone: true } : {};
|
|
28
29
|
if (v.variant !== undefined) {
|
|
29
30
|
const optional = v.ty.k === "opt";
|
|
30
|
-
return { init: bare(v.variant), read: (at) => ({ code: optional ? `${at}.map(
|
|
31
|
+
return { init: bare(v.variant), read: (at) => ({ code: optional ? `${at}.map(${UNION_STR})` : unionStr(at), ty: v.ty, variant: at, ...lone }), owned: false };
|
|
31
32
|
}
|
|
32
33
|
if (v.owned)
|
|
33
34
|
return { init: bare(v.code), read: (at) => ({ code: v.ty.k === "opt" ? `${at}.as_ref()` : at, ty: v.ty, ...lone }), owned: true };
|
package/dist/calls.js
CHANGED
|
@@ -62,7 +62,7 @@ export function call(s, n) {
|
|
|
62
62
|
if (a.ty.k === "str")
|
|
63
63
|
return a;
|
|
64
64
|
if (isNumber(a.ty))
|
|
65
|
-
return { code: `&*fv::Js(${bare(asWritten(a).code)}).to_string()`, ty: asciiTy() };
|
|
65
|
+
return { code: `&*fv::core::Js(${bare(asWritten(a).code)}).to_string()`, ty: asciiTy() };
|
|
66
66
|
if (a.ty.k === "bool")
|
|
67
67
|
return { code: `if ${condition(a.code)} { "true" } else { "false" }`, ty: asciiTy() };
|
|
68
68
|
return formatted(comp, [stringOf(comp, a, args[0], "`String()`", "FV0723")], n, "`String()`");
|
|
@@ -75,12 +75,12 @@ export function call(s, n) {
|
|
|
75
75
|
if (a.lone)
|
|
76
76
|
fail(comp, "FV0706", `${lonely(name)}; it throws a \`URIError\` on a half`, n);
|
|
77
77
|
const text = a.ty.k === "str" ? a : formatted(comp, [stringOf(comp, a, args[0], name, "FV0723")], n, name);
|
|
78
|
-
return { code: `&*fv::js_${callee.name === "encodeURI" ? "encode_uri" : "encode_uri_component"}(${strArg(text.code)})`, ty: STR };
|
|
78
|
+
return { code: `&*fv::core::js_${callee.name === "encodeURI" ? "encode_uri" : "encode_uri_component"}(${strArg(text.code)})`, ty: STR };
|
|
79
79
|
}
|
|
80
80
|
if (callee.name === "Number" && args.length === 1) {
|
|
81
81
|
const a = expr(s, args[0]);
|
|
82
82
|
if (a.ty.k === "str")
|
|
83
|
-
return { code: `fv::js_number(${strArg(a.code)})`, ty: FLOAT };
|
|
83
|
+
return { code: `fv::strings::js_number(${strArg(a.code)})`, ty: FLOAT };
|
|
84
84
|
if (isNumber(a.ty))
|
|
85
85
|
return a;
|
|
86
86
|
if (a.ty.k === "bool")
|
|
@@ -92,12 +92,12 @@ export function call(s, n) {
|
|
|
92
92
|
if (a.ty.k !== "str")
|
|
93
93
|
fail(comp, "FV0702", `\`${callee.name}()\` takes a string`, args[0]);
|
|
94
94
|
if (callee.name === "parseFloat")
|
|
95
|
-
return { code: `fv::js_parse_float(${strArg(a.code)})`, ty: FLOAT };
|
|
95
|
+
return { code: `fv::strings::js_parse_float(${strArg(a.code)})`, ty: FLOAT };
|
|
96
96
|
const radix = args[1];
|
|
97
97
|
if (radix && !(radix.type === "NumericLiteral" && (radix.value === 10 || radix.value === 16))) {
|
|
98
98
|
fail(comp, "FV0703", "`parseInt()` takes a radix of 10 or 16, written as a literal", radix);
|
|
99
99
|
}
|
|
100
|
-
return { code: `fv::js_parse_int(${strArg(a.code)}, ${radix ? radix.value : 0})`, ty: FLOAT };
|
|
100
|
+
return { code: `fv::strings::js_parse_int(${strArg(a.code)}, ${radix ? radix.value : 0})`, ty: FLOAT };
|
|
101
101
|
}
|
|
102
102
|
if ((callee.name === "isNaN" || callee.name === "isFinite") && global)
|
|
103
103
|
return numberTest(s, callee.name, args, n, true);
|
|
@@ -157,7 +157,7 @@ export function call(s, n) {
|
|
|
157
157
|
if ((method === "max" || method === "min") && vals.length >= 2) {
|
|
158
158
|
if (ints && !vals.some(isCast))
|
|
159
159
|
return { code: vals.slice(1).reduce((acc, v) => `${atom(acc)}.${method}(${bare(v.code)})`, vals[0].code), ty: INT };
|
|
160
|
-
const extreme = vals.slice(1).reduce((acc, v) => `fv::js_${method}(${bare(acc)}, ${bare(asF64(v))})`, asF64(vals[0]));
|
|
160
|
+
const extreme = vals.slice(1).reduce((acc, v) => `fv::core::js_${method}(${bare(acc)}, ${bare(asF64(v))})`, asF64(vals[0]));
|
|
161
161
|
if (ints)
|
|
162
162
|
return intFromF64(extreme, vals.find(isCast)?.at ?? refusalAt(comp, n));
|
|
163
163
|
return { code: extreme, ty: FLOAT };
|
|
@@ -167,7 +167,7 @@ export function call(s, n) {
|
|
|
167
167
|
if (method === "abs")
|
|
168
168
|
return ints ? intFromF64(`${receiver(asF64(x))}.abs()`, refusalAt(comp, n)) : { code: `${receiver(x.code)}.abs()`, ty: FLOAT };
|
|
169
169
|
if (method === "round")
|
|
170
|
-
return { code: `fv::js_round(${bare(asF64(x))})`, ty: FLOAT };
|
|
170
|
+
return { code: `fv::core::js_round(${bare(asF64(x))})`, ty: FLOAT };
|
|
171
171
|
if (method === "floor" || method === "ceil" || method === "trunc")
|
|
172
172
|
return { code: `${receiver(asF64(x))}.${method}()`, ty: FLOAT };
|
|
173
173
|
if (method === "sqrt")
|
|
@@ -175,7 +175,7 @@ export function call(s, n) {
|
|
|
175
175
|
if (method === "sign") {
|
|
176
176
|
if (x.num !== undefined)
|
|
177
177
|
return numberVal(Math.sign(x.num), ints);
|
|
178
|
-
const sign = `fv::js_sign(${bare(asF64(x))})`;
|
|
178
|
+
const sign = `fv::core::js_sign(${bare(asF64(x))})`;
|
|
179
179
|
return ints ? intFromF64(sign, null) : { code: sign, ty: FLOAT };
|
|
180
180
|
}
|
|
181
181
|
}
|
|
@@ -203,14 +203,14 @@ export function call(s, n) {
|
|
|
203
203
|
const same = strTy(isAscii(target.ty));
|
|
204
204
|
switch (args.length === 0 ? method : "") {
|
|
205
205
|
case "trim":
|
|
206
|
-
return { code: `fv::js_trim(${strArg(target.code)})`, ty: same, ...lone };
|
|
206
|
+
return { code: `fv::strings::js_trim(${strArg(target.code)})`, ty: same, ...lone };
|
|
207
207
|
case "trimStart":
|
|
208
|
-
return { code: `fv::js_trim_start(${strArg(target.code)})`, ty: same, ...lone };
|
|
208
|
+
return { code: `fv::strings::js_trim_start(${strArg(target.code)})`, ty: same, ...lone };
|
|
209
209
|
case "trimEnd":
|
|
210
210
|
case "trimRight":
|
|
211
|
-
return { code: `fv::js_trim_end(${strArg(target.code)})`, ty: same, ...lone };
|
|
211
|
+
return { code: `fv::strings::js_trim_end(${strArg(target.code)})`, ty: same, ...lone };
|
|
212
212
|
case "trimLeft":
|
|
213
|
-
return { code: `fv::js_trim_start(${strArg(target.code)})`, ty: same, ...lone };
|
|
213
|
+
return { code: `fv::strings::js_trim_start(${strArg(target.code)})`, ty: same, ...lone };
|
|
214
214
|
case "toUpperCase":
|
|
215
215
|
return { code: `&*${receiver(target.code)}.to_uppercase()`, ty: same, ...lone };
|
|
216
216
|
case "toLowerCase":
|
|
@@ -231,15 +231,15 @@ export function call(s, n) {
|
|
|
231
231
|
fail(comp, code, `\`.${method}()\` takes a literal ${what}, from ${low} to 100`, n);
|
|
232
232
|
const x = bare(asF64(target));
|
|
233
233
|
if (method === "toFixed")
|
|
234
|
-
return { code: `&*fv::js_to_fixed(${x}, ${given ?? 0})`, ty: asciiTy() };
|
|
234
|
+
return { code: `&*fv::core::js_to_fixed(${x}, ${given ?? 0})`, ty: asciiTy() };
|
|
235
235
|
if (method === "toExponential")
|
|
236
|
-
return { code: `&*fv::js_to_exponential(${x}, ${given === undefined ? "None" : `Some(${given})`})`, ty: asciiTy() };
|
|
236
|
+
return { code: `&*fv::core::js_to_exponential(${x}, ${given === undefined ? "None" : `Some(${given})`})`, ty: asciiTy() };
|
|
237
237
|
if (given === undefined)
|
|
238
|
-
return { code: `&*fv::Js(${bare(asWritten(target).code)}).to_string()`, ty: asciiTy() };
|
|
239
|
-
return { code: `&*fv::js_to_precision(${x}, ${given})`, ty: asciiTy() };
|
|
238
|
+
return { code: `&*fv::core::Js(${bare(asWritten(target).code)}).to_string()`, ty: asciiTy() };
|
|
239
|
+
return { code: `&*fv::core::js_to_precision(${x}, ${given})`, ty: asciiTy() };
|
|
240
240
|
}
|
|
241
241
|
if (isNumber(target.ty) && method === "toString" && args.length === 0) {
|
|
242
|
-
return { code: `&*fv::Js(${bare(asWritten(target).code)}).to_string()`, ty: asciiTy() };
|
|
242
|
+
return { code: `&*fv::core::Js(${bare(asWritten(target).code)}).to_string()`, ty: asciiTy() };
|
|
243
243
|
}
|
|
244
244
|
if (target.owned)
|
|
245
245
|
refuseInPlace(comp, `\`.${method}()\``, n);
|
|
@@ -266,7 +266,7 @@ export function call(s, n) {
|
|
|
266
266
|
const sep = args.length ? expr(s, args[0]) : { code: '","', ty: STR };
|
|
267
267
|
if (sep.ty.k !== "str")
|
|
268
268
|
fail(comp, "FV0802", "`.join()` takes a string", args[0]);
|
|
269
|
-
const each = of.k === "str" ? `${atom(target.code)}.iter().map(|v| &**v)` : `${atom(target.code)}.iter().map(|v| fv::Js(*v).to_string())`;
|
|
269
|
+
const each = of.k === "str" ? `${atom(target.code)}.iter().map(|v| &**v)` : `${atom(target.code)}.iter().map(|v| fv::core::Js(*v).to_string())`;
|
|
270
270
|
return { code: `&*${each}.collect::<Vec<_>>().join(${strArg(sep.code)})`, ty: strTy(writesAscii(target.ty) && isAscii(sep.ty)), ...loneOf(sep) };
|
|
271
271
|
}
|
|
272
272
|
}
|
|
@@ -349,11 +349,11 @@ function json(s, v, n) {
|
|
|
349
349
|
const one = (code, ty) => {
|
|
350
350
|
switch (ty.k) {
|
|
351
351
|
case "str":
|
|
352
|
-
return `fv::js_json_string(${code === "v" ? "&v" : strArg(code)})`;
|
|
352
|
+
return `fv::strings::js_json_string(${code === "v" ? "&v" : strArg(code)})`;
|
|
353
353
|
case "int":
|
|
354
|
-
return `fv::Js(${bare(code)}).to_string()`;
|
|
354
|
+
return `fv::core::Js(${bare(code)}).to_string()`;
|
|
355
355
|
case "float":
|
|
356
|
-
return `fv::js_json_number(${bare(code)})`;
|
|
356
|
+
return `fv::strings::js_json_number(${bare(code)})`;
|
|
357
357
|
case "bool":
|
|
358
358
|
return `(if ${condition(code)} { "true" } else { "false" }).to_owned()`;
|
|
359
359
|
default:
|
package/dist/children.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isSSRSafeAttrName, propsToAttrMap } from "@vue/shared";
|
|
2
|
-
import { absence, camelize, declares, enumOf, fail, nothing, REFUSALS, refusalAt, rustStr, sameTy, snake, takesAttrs, variantPath } from "./model.js";
|
|
2
|
+
import { absence, camelize, declares, enumItems, enumOf, fail, literalsOf, nothing, REFUSALS, refusalAt, rustStr, sameTy, snake, takesAttrs, variantPath } from "./model.js";
|
|
3
3
|
import { ctx } from "./context.js";
|
|
4
4
|
import { known } from "./narrowing.js";
|
|
5
|
-
import { intoEnum, knownLiteral } from "./enums.js";
|
|
5
|
+
import { enumItemVal, intoEnum, knownLiteral, refuseUnproven } from "./enums.js";
|
|
6
6
|
import { bareDates, markHome } from "./typescript.js";
|
|
7
7
|
import { items } from "./lists.js";
|
|
8
8
|
import { describeTy, expr, fieldVal, holdsNothing, typedBy } from "./expr.js";
|
|
@@ -254,10 +254,10 @@ function childAttrsArg(s, child, parts, merges, fallthrough, ids, n) {
|
|
|
254
254
|
return ids;
|
|
255
255
|
}
|
|
256
256
|
if (!sources.length)
|
|
257
|
-
return ids === null ? "&fv::Attrs::NONE" : `&fv::Attrs::scoped(${ids})`;
|
|
257
|
+
return ids === null ? "&fv::attrs::Attrs::NONE" : `&fv::attrs::Attrs::scoped(${ids})`;
|
|
258
258
|
if (sources.length === 1 && !merges)
|
|
259
|
-
return `&fv::Attrs::new(${sources[0]}, ${ids ?? '""'})`;
|
|
260
|
-
return `&fv::Attrs::merged(&[${sources.join(", ")}], ${ids ?? '""'})`;
|
|
259
|
+
return `&fv::attrs::Attrs::new(${sources[0]}, ${ids ?? '""'})`;
|
|
260
|
+
return `&fv::attrs::Attrs::merged(&[${sources.join(", ")}], ${ids ?? '""'})`;
|
|
261
261
|
}
|
|
262
262
|
export function fieldInit(name, value) {
|
|
263
263
|
return value === name ? name : `${name}: ${bare(value)}`;
|
|
@@ -271,7 +271,7 @@ export function extraParams(c, unread = () => false) {
|
|
|
271
271
|
export function callChild(s, e, child, propsCode, slots, attrs, later = false) {
|
|
272
272
|
const m = `super::${child.module}`;
|
|
273
273
|
const scoped = child.inherits || takesAttrs(child);
|
|
274
|
-
const trailing = paramsOf(child).map((p) => `, ${p.name}`).join("") + (scoped ? `, ${attrs ?? (takesAttrs(child) ? "&fv::Attrs::NONE" : '""')}` : "");
|
|
274
|
+
const trailing = paramsOf(child).map((p) => `, ${p.name}`).join("") + (scoped ? `, ${attrs ?? (takesAttrs(child) ? "&fv::attrs::Attrs::NONE" : '""')}` : "");
|
|
275
275
|
const render = scoped ? "try_render_scoped" : "try_render";
|
|
276
276
|
const around = later ? ctx.plugins.flatMap((p) => p.loadedLater?.(child) ?? []) : [];
|
|
277
277
|
if (around.length) {
|
|
@@ -377,19 +377,19 @@ export function callWith(s, e, child, m, head, slotsTy, trailing, slots, vnode =
|
|
|
377
377
|
e.close("),");
|
|
378
378
|
}
|
|
379
379
|
else if (sid !== null) {
|
|
380
|
-
e.open(`${field}: Some(fv::Slot::slotted(&|out: &mut String${sidParam}| -> bool`);
|
|
380
|
+
e.open(`${field}: Some(fv::render::Slot::slotted(&|out: &mut String${sidParam}| -> bool`);
|
|
381
381
|
const opened = e.lines.length - 1;
|
|
382
382
|
content(inner);
|
|
383
383
|
unread(opened, sid);
|
|
384
384
|
e.close(")),");
|
|
385
385
|
}
|
|
386
386
|
else if (staticallyFilled(body)) {
|
|
387
|
-
e.open(`${field}: Some(fv::Slot::new(&|out: &mut String|`);
|
|
387
|
+
e.open(`${field}: Some(fv::render::Slot::new(&|out: &mut String|`);
|
|
388
388
|
statements({ ...inner, fill: false }, e, body);
|
|
389
389
|
e.close(")),");
|
|
390
390
|
}
|
|
391
391
|
else {
|
|
392
|
-
e.open(`${field}: Some(fv::Slot::markup(&|out: &mut String| -> bool`);
|
|
392
|
+
e.open(`${field}: Some(fv::render::Slot::markup(&|out: &mut String| -> bool`);
|
|
393
393
|
e.stmt("let mut filled = false;");
|
|
394
394
|
statements({ ...inner, fill: true }, e, body);
|
|
395
395
|
e.stmt("filled");
|
|
@@ -402,6 +402,32 @@ function datesInto(of, at) {
|
|
|
402
402
|
const date = `|fv_t| ${REFUSALS}.date(fv_t, ${at})`;
|
|
403
403
|
return of.k === "date" ? `.map(${date})` : `.map(|v| v.map(${date}))`;
|
|
404
404
|
}
|
|
405
|
+
function enumListInto(comp, v, want, node) {
|
|
406
|
+
const refuse = () => fail(comp, "FV0511", `the prop is ${describeTy(want)}, and the value given is ${describeTy(v.ty)}`, node);
|
|
407
|
+
if (want.k === "opt") {
|
|
408
|
+
if (v.ty.k === "list")
|
|
409
|
+
return `Some(${enumListInto(comp, v, want.of, node)})`;
|
|
410
|
+
if (v.ty.k !== "opt" || v.ty.of.k !== "list" || absence(v.ty) !== absence(want))
|
|
411
|
+
return refuse();
|
|
412
|
+
if (v.enums === undefined)
|
|
413
|
+
return `${atom(v.code)}.map(|v| ${enumListInto(comp, { code: "v", ty: v.ty.of, slice: true }, want.of, node)})`;
|
|
414
|
+
return `${atom(v.enums.place)}.as_ref().map(|v| ${enumListInto(comp, { ...v, code: "v", ty: v.ty.of, enums: { ...v.enums, place: "v" } }, want.of, node)})`;
|
|
415
|
+
}
|
|
416
|
+
if (want.k !== "list" || v.ty.k !== "list")
|
|
417
|
+
return refuse();
|
|
418
|
+
if (v.ty.of.k === "undef")
|
|
419
|
+
return "Vec::new()";
|
|
420
|
+
const of = v.ty.of;
|
|
421
|
+
if (v.enums !== undefined) {
|
|
422
|
+
const item = ownInto(comp, enumItemVal("v", of, v.enums.def), want.of, node);
|
|
423
|
+
return item === "v" ? `${atom(v.enums.place)}.to_vec()` : `${atom(v.enums.place)}.iter().map(|&v| ${item}).collect()`;
|
|
424
|
+
}
|
|
425
|
+
if (of.k !== "str" && (of.k !== "opt" || of.of.k !== "str"))
|
|
426
|
+
return refuse();
|
|
427
|
+
const each = of.k === "str" ? { code: v.iter !== undefined ? "&*v" : "&**v", ty: of } : { code: v.iter !== undefined ? "v" : "v.as_deref()", ty: of };
|
|
428
|
+
const iter = v.iter ?? `${atom(v.code)}.iter()`;
|
|
429
|
+
return `${iter}.map(|v| ${ownInto(comp, each, want.of, node)}).collect()`;
|
|
430
|
+
}
|
|
405
431
|
export function ownInto(comp, v, want, node) {
|
|
406
432
|
holdsNothing(comp, v, want, node, "the prop");
|
|
407
433
|
if (v.owned && sameTy(v.ty, want))
|
|
@@ -410,13 +436,22 @@ export function ownInto(comp, v, want, node) {
|
|
|
410
436
|
return `Some(${bare(v.code)})`;
|
|
411
437
|
if (want.k === "opt" && nothing(v.ty))
|
|
412
438
|
return "None";
|
|
439
|
+
if (enumItems(want) !== undefined)
|
|
440
|
+
return enumListInto(comp, v, want, node);
|
|
413
441
|
const def = enumOf(want);
|
|
414
442
|
if (def !== undefined) {
|
|
415
|
-
const into = (v.ty.k === "opt" ? v.ty.of : v.ty).k === "str" ? intoEnum(comp, v, def) : null;
|
|
443
|
+
const into = (v.ty.k === "opt" ? v.ty.of : v.ty).k === "str" ? intoEnum(comp, v, def, node) : null;
|
|
416
444
|
if (into === null) {
|
|
445
|
+
refuseUnproven(v);
|
|
417
446
|
const literal = knownLiteral(v);
|
|
418
|
-
const
|
|
419
|
-
|
|
447
|
+
const inner = v.ty.k === "opt" ? v.ty.of : v.ty;
|
|
448
|
+
const outside = literal === undefined && inner.k === "str" ? literalsOf(inner)?.filter((l) => typeof l !== "string" || !def.literals.includes(l)) : undefined;
|
|
449
|
+
const union = `the prop is one of ${def.literals.map((l) => JSON.stringify(l)).join(" | ")}, the members of \`${def.name}\``;
|
|
450
|
+
if (outside !== undefined && outside.length > 0) {
|
|
451
|
+
fail(comp, "FV0515", `${union}, and the value given may be ${outside.map((l) => JSON.stringify(l)).join(" or ")}: narrow it to the members, by \`===\` or \`!==\` with a literal, by its truth or by \`||\`, in \`?:\`, \`&&\` or \`v-if\`, or pass a value typed as the union`, node);
|
|
452
|
+
}
|
|
453
|
+
const given = literal !== undefined ? `is ${JSON.stringify(literal)}` : inner.k === "str" ? "is a string that may be any other" : `is ${describeTy(v.ty)}`;
|
|
454
|
+
fail(comp, "FV0515", `${union}, and the value given ${given}: pass one of them, or a value typed as the union`, node);
|
|
420
455
|
}
|
|
421
456
|
return want.k === "opt" && v.ty.k !== "opt" ? `Some(${bare(into)})` : into;
|
|
422
457
|
}
|
package/dist/classes.js
CHANGED
|
@@ -36,7 +36,7 @@ export function classItems(s, n) {
|
|
|
36
36
|
keys.push(key);
|
|
37
37
|
return `(${bare(cond(s, p.value))}, ${bare(key.code)})`;
|
|
38
38
|
});
|
|
39
|
-
return [{ code: `&*fv::class_object(&[${entries.join(", ")}])` }];
|
|
39
|
+
return [{ code: `&*fv::attrs::class_object(&[${entries.join(", ")}])` }];
|
|
40
40
|
}
|
|
41
41
|
return n.properties.flatMap((p) => {
|
|
42
42
|
if (p.type !== "ObjectProperty")
|
|
@@ -52,7 +52,7 @@ export function classItems(s, n) {
|
|
|
52
52
|
const k = expr(s, p.key);
|
|
53
53
|
if (k.ty.k !== "str")
|
|
54
54
|
fail(s.comp, "FV1002", "a computed class name is a string", p.key);
|
|
55
|
-
name = { code: `fv::js_trim(${strArg(k.code)})` };
|
|
55
|
+
name = { code: `fv::strings::js_trim(${strArg(k.code)})` };
|
|
56
56
|
}
|
|
57
57
|
const v = expr(s, p.value);
|
|
58
58
|
const on = known(v);
|
|
@@ -115,7 +115,7 @@ export function renderClass(s, e, n, after = false) {
|
|
|
115
115
|
}
|
|
116
116
|
if (i < items.length) {
|
|
117
117
|
const rest = items.slice(i).map((it) => ("lit" in it ? rustStr(it.lit) : it.code));
|
|
118
|
-
e.stmt(`fv::class_into(out, ${wrote}, &[${rest.join(", ")}]);`);
|
|
118
|
+
e.stmt(`fv::attrs::class_into(out, ${wrote}, &[${rest.join(", ")}]);`);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
const UNWRITTEN = new Set(["on", "if", "else-if", "else", "for", "slot", "once", "memo", "cloak", "pre"]);
|
|
@@ -202,23 +202,23 @@ export function literalClass(n) {
|
|
|
202
202
|
export function classAttr(s, n, side) {
|
|
203
203
|
switch (n.type) {
|
|
204
204
|
case "StringLiteral":
|
|
205
|
-
return `fv::Attr::str(${rustStr(n.value)})`;
|
|
205
|
+
return `fv::attrs::Attr::str(${rustStr(n.value)})`;
|
|
206
206
|
case "NullLiteral":
|
|
207
|
-
return 'fv::Attr::str("")';
|
|
207
|
+
return 'fv::attrs::Attr::str("")';
|
|
208
208
|
case "ArrayExpression":
|
|
209
209
|
case "ObjectExpression": {
|
|
210
210
|
const items = classItems(s, n);
|
|
211
211
|
const names = items.every((it) => "lit" in it)
|
|
212
212
|
? rustStr(items.map((it) => ("lit" in it ? it.lit : "")).join(" "))
|
|
213
|
-
: `fv::class_names(&[${items.map((it) => ("lit" in it ? rustStr(it.lit) : it.code)).join(", ")}])`;
|
|
213
|
+
: `fv::attrs::class_names(&[${items.map((it) => ("lit" in it ? rustStr(it.lit) : it.code)).join(", ")}])`;
|
|
214
214
|
const literal = names.startsWith('"');
|
|
215
215
|
if (side === "vnode")
|
|
216
|
-
return literal ? `fv::Attr::str(${names})` : `fv::Attr::from(${names})`;
|
|
217
|
-
return `fv::Attr::Names(${literal ? `std::borrow::Cow::Borrowed(${names})` : `${names}.into()`})`;
|
|
216
|
+
return literal ? `fv::attrs::Attr::str(${names})` : `fv::attrs::Attr::from(${names})`;
|
|
217
|
+
return `fv::attrs::Attr::Names(${literal ? `std::borrow::Cow::Borrowed(${names})` : `${names}.into()`})`;
|
|
218
218
|
}
|
|
219
219
|
case "LogicalExpression":
|
|
220
220
|
if (n.operator === "&&") {
|
|
221
|
-
return narrowed(s, n.left, true, (inner) => classAttr(inner, n.right, side), () => 'fv::Attr::str("")') ?? `if ${condition(cond(s, n.left))} { ${classAttr(s, n.right, side)} } else { fv::Attr::str("") }`;
|
|
221
|
+
return narrowed(s, n.left, true, (inner) => classAttr(inner, n.right, side), () => 'fv::attrs::Attr::str("")') ?? `if ${condition(cond(s, n.left))} { ${classAttr(s, n.right, side)} } else { fv::attrs::Attr::str("") }`;
|
|
222
222
|
}
|
|
223
223
|
break;
|
|
224
224
|
case "ConditionalExpression":
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, watch, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { relativePath } from "./paths.js";
|
|
5
|
-
import { CONFIG_FILE,
|
|
5
|
+
import { CONFIG_FILE, loadConfig } from "./config.js";
|
|
6
|
+
import {} from "./errors.js";
|
|
7
|
+
import { generate, isGenerated, VERSION, write } from "./compiler.js";
|
|
6
8
|
import { helpText, parseArgs } from "./args.js";
|
|
7
9
|
import { unifiedDiff } from "./diff.js";
|
|
8
10
|
import { diagnose, diagnoseAll, formatRefusal, formatWarning, isRefusal } from "./diagnostics.js";
|