ferrovue 0.15.1 → 0.15.2

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 CHANGED
@@ -57,7 +57,7 @@ the configuration at your project root yourself:
57
57
 
58
58
  ```json
59
59
  {
60
- "$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.15.1/schema.json",
60
+ "$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.15.2/schema.json",
61
61
  "components": "client/components",
62
62
  "out": "src/generated"
63
63
  }
@@ -242,7 +242,7 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
242
242
  | 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 |
243
243
  | 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 |
244
244
  | HTML comments | left out, as Vue's production build leaves them out, or kept with `comments` or `"isProduction": false`, as on the dev server: written as Vue writes them, at the root, between roots, inside elements and in slot content |
245
- | Whitespace | text condensed as Vue condenses it, or with `"whitespace": "trim-edges"` also trimmed where it meets its element's tags across a line break and spaced between inline elements and components across one, the client compiled alike with `trimEdges`: in elements, components and `<template v-slot>`, keeping `<pre>`, `<textarea>`, `v-pre` and a static `white-space: pre*` style. See [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#whitespace) |
245
+ | Whitespace | text condensed as Vue condenses it, or with `"whitespace": "trim-edges"` also trimmed where it meets a block or inline-block element's tags across a line break and spaced between inline elements and components across one, the client compiled alike with `trimEdges`: in elements, components and `<template v-slot>`, keeping `<pre>`, `<textarea>`, `v-pre` and a static `white-space: pre*` style. See [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#whitespace) |
246
246
  | Attributes | static and bound attributes, boolean attributes, `:hidden`, `data-*` and `aria-*`, `v-bind` objects |
247
247
  | `class` | strings, arrays, objects (`{ active: on }`, computed keys), `cond && "x"`, `cond ? "x" : null`, merged with a static `class` |
248
248
  | `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 |
package/dist/config.d.ts CHANGED
@@ -104,8 +104,9 @@ export interface Config {
104
104
  * `false` and left out when it is `true`. */
105
105
  comments?: boolean;
106
106
  /** How the text of templates is spaced: `"condense"` (the default) as Vue spaces it, or
107
- * `"trim-edges"`, which also trims the whitespace holding a line break where text meets its
108
- * element's tags and renders a line break between two inline elements as a space. The client
107
+ * `"trim-edges"`, which also trims the whitespace holding a line break where text meets a block
108
+ * or inline-block element's tags and renders a line break between two inline elements as a
109
+ * space. The client
109
110
  * compiles the same way with `trimEdges` from `ferrovue/whitespace` in `@vitejs/plugin-vue`'s
110
111
  * `template.compilerOptions.nodeTransforms`. */
111
112
  whitespace?: WhitespaceMode;
@@ -1,10 +1,11 @@
1
1
  /** A node transform for `@vitejs/plugin-vue`'s `template.compilerOptions.nodeTransforms` that
2
- * trims the whitespace at the start and end of an element's text, where it meets the element's
3
- * tags, when that whitespace holds a line break, as `"whitespace": "trim-edges"` makes ferrovue's
4
- * Rust render it. A space typed on the same line as the tag stays. A line break between two
5
- * inline elements, components, `<slot>`s or `<template>`s, such as `<span>` and `<a>`, renders as
6
- * one space, as a formatter reads it.
7
- * It trims the content of elements, components and `<template v-slot>`, and leaves `<pre>`, `<textarea>`, `v-pre` and an
8
- * element whose `style` sets `white-space: pre`, `pre-wrap`, `pre-line` or `break-spaces` as they
9
- * are, with everything inside them. */
2
+ * trims the whitespace at the start and end of a block or inline-block element's text, where it
3
+ * meets the element's tags, when that whitespace holds a line break, as `"whitespace": "trim-edges"`
4
+ * makes ferrovue's Rust render it. An inline element's edges keep Vue's one space, and a space
5
+ * typed on the same line as the tag stays. A line break between two inline elements, components,
6
+ * `<slot>`s or `<template>`s, such as `<span>` and `<a>`, renders as one space, as a formatter
7
+ * reads it, also at the template's root and across a comment.
8
+ * It trims the content of block and inline-block elements, components and `<template v-slot>`, and
9
+ * leaves `<pre>`, `<textarea>`, `v-pre` and an element whose `style` sets `white-space: pre`,
10
+ * `pre-wrap`, `pre-line` or `break-spaces` as they are, with everything inside them. */
10
11
  export declare function trimEdges(node: object): void;
@@ -1,6 +1,7 @@
1
1
  const ROOT = 0;
2
2
  const ELEMENT = 1;
3
3
  const TEXT = 2;
4
+ const COMMENT = 3;
4
5
  const PLAIN = 0;
5
6
  const COMPONENT = 1;
6
7
  const SLOT = 2;
@@ -17,6 +18,8 @@ const SPACELESS_TAGS = new Set([
17
18
  ]
18
19
  .join(" ")
19
20
  .split(" "));
21
+ const INLINE_BLOCK_TAGS = new Set(["audio", "button", "meter", "object", "progress", "select", "video"]);
22
+ const COMMENTS = /<!--[\s\S]*?(?:-->|$)/g;
20
23
  const KEPT_TAGS = new Set(["pre", "textarea"]);
21
24
  const PRE_STYLE = /(?:^|;)\s*white-space\s*:\s*(?:pre|pre-wrap|pre-line|break-spaces)\s*(?:!important\s*)?(?:;|$)/i;
22
25
  const OPEN_TAG = /^<[^\s/>]+(?:[^"'>]|"[^"]*"|'[^']*')*/;
@@ -33,7 +36,11 @@ function keeps(el) {
33
36
  return V_PRE.test(open.replace(QUOTED, '""'));
34
37
  }
35
38
  function trims(el) {
36
- if (el.tagType === PLAIN || el.tagType === COMPONENT)
39
+ if (el.tagType === PLAIN) {
40
+ const tag = el.tag.toLowerCase();
41
+ return el.ns !== HTML || SPACELESS_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag);
42
+ }
43
+ if (el.tagType === COMPONENT)
37
44
  return true;
38
45
  return el.tagType === TEMPLATE && el.props.some((p) => p.type === 7 && p.name === "slot");
39
46
  }
@@ -64,22 +71,26 @@ function inline(node) {
64
71
  const tag = node.tag.toLowerCase();
65
72
  return (node.ns === HTML || tag === "svg" || tag === "math") && !SPACELESS_TAGS.has(tag);
66
73
  }
67
- function spaceInline(el) {
68
- if (el.ns !== HTML)
69
- return;
70
- const children = el.children;
74
+ function spaceInline(node, source, start) {
75
+ const children = node.children;
71
76
  const spaced = [];
72
- children.forEach((child, i) => {
73
- const prev = children[i - 1];
77
+ let prev;
78
+ let after = 0;
79
+ children.forEach((child) => {
80
+ if (child.type === COMMENT) {
81
+ spaced.push(child);
82
+ return;
83
+ }
74
84
  if (prev !== undefined && inline(prev) && inline(child)) {
75
- const start = el.loc.start.offset;
76
- const gap = el.loc.source.slice(prev.loc.end.offset - start, child.loc.start.offset - start);
85
+ const gap = source.slice(prev.loc.end.offset - start, child.loc.start.offset - start).replace(COMMENTS, "");
77
86
  if (BLANK.test(gap) && LINE_BREAK.test(gap))
78
- spaced.push({ type: TEXT, content: " ", loc: { source: gap, start: prev.loc.end, end: child.loc.start } });
87
+ spaced.splice(after, 0, { type: TEXT, content: " ", loc: { source: gap, start: prev.loc.end, end: child.loc.start } });
79
88
  }
80
89
  spaced.push(child);
90
+ prev = child;
91
+ after = spaced.length;
81
92
  });
82
- el.children = spaced;
93
+ node.children = spaced;
83
94
  }
84
95
  function walk(node) {
85
96
  for (const child of node.children ?? []) {
@@ -87,22 +98,26 @@ function walk(node) {
87
98
  continue;
88
99
  if (trims(child))
89
100
  trimChildren(child);
90
- spaceInline(child);
101
+ if (child.ns === HTML)
102
+ spaceInline(child, child.loc.source, child.loc.start.offset);
91
103
  walk(child);
92
104
  }
93
105
  }
94
106
  /** A node transform for `@vitejs/plugin-vue`'s `template.compilerOptions.nodeTransforms` that
95
- * trims the whitespace at the start and end of an element's text, where it meets the element's
96
- * tags, when that whitespace holds a line break, as `"whitespace": "trim-edges"` makes ferrovue's
97
- * Rust render it. A space typed on the same line as the tag stays. A line break between two
98
- * inline elements, components, `<slot>`s or `<template>`s, such as `<span>` and `<a>`, renders as
99
- * one space, as a formatter reads it.
100
- * It trims the content of elements, components and `<template v-slot>`, and leaves `<pre>`, `<textarea>`, `v-pre` and an
101
- * element whose `style` sets `white-space: pre`, `pre-wrap`, `pre-line` or `break-spaces` as they
102
- * are, with everything inside them. */
107
+ * trims the whitespace at the start and end of a block or inline-block element's text, where it
108
+ * meets the element's tags, when that whitespace holds a line break, as `"whitespace": "trim-edges"`
109
+ * makes ferrovue's Rust render it. An inline element's edges keep Vue's one space, and a space
110
+ * typed on the same line as the tag stays. A line break between two inline elements, components,
111
+ * `<slot>`s or `<template>`s, such as `<span>` and `<a>`, renders as one space, as a formatter
112
+ * reads it, also at the template's root and across a comment.
113
+ * It trims the content of block and inline-block elements, components and `<template v-slot>`, and
114
+ * leaves `<pre>`, `<textarea>`, `v-pre` and an element whose `style` sets `white-space: pre`,
115
+ * `pre-wrap`, `pre-line` or `break-spaces` as they are, with everything inside them. */
103
116
  export function trimEdges(node) {
104
117
  const n = node;
105
- if (n.type === ROOT)
106
- walk(n);
118
+ if (n.type !== ROOT)
119
+ return;
120
+ spaceInline(n, n.source ?? n.loc.source, n.source === undefined ? n.loc.start.offset : 0);
121
+ walk(n);
107
122
  }
108
123
  Object.defineProperty(trimEdges, MARK, { value: "trim-edges" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ferrovue",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Vue templates compiled to Rust render functions: server-side rendering with no JavaScript at run time",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
package/schema.json CHANGED
@@ -311,8 +311,8 @@
311
311
  "type": "boolean"
312
312
  },
313
313
  "whitespace": {
314
- "description": "How the text of templates is spaced: `\"condense\"` (the default) as Vue spaces it, or `\"trim-edges\"`, which also trims the whitespace holding a line break where text meets its element's tags and renders a line break between two inline elements as a space. The client compiles the same way with `trimEdges` from `ferrovue/whitespace` in `@vitejs/plugin-vue`'s `template.compilerOptions.nodeTransforms`.",
315
- "markdownDescription": "How the text of templates is spaced: `\"condense\"` (the default) as Vue spaces it, or `\"trim-edges\"`, which also trims the whitespace holding a line break where text meets its element's tags and renders a line break between two inline elements as a space. The client compiles the same way with `trimEdges` from `ferrovue/whitespace` in `@vitejs/plugin-vue`'s `template.compilerOptions.nodeTransforms`.",
314
+ "description": "How the text of templates is spaced: `\"condense\"` (the default) as Vue spaces it, or `\"trim-edges\"`, which also trims the whitespace holding a line break where text meets a block or inline-block element's tags and renders a line break between two inline elements as a space. The client compiles the same way with `trimEdges` from `ferrovue/whitespace` in `@vitejs/plugin-vue`'s `template.compilerOptions.nodeTransforms`.",
315
+ "markdownDescription": "How the text of templates is spaced: `\"condense\"` (the default) as Vue spaces it, or `\"trim-edges\"`, which also trims the whitespace holding a line break where text meets a block or inline-block element's tags and renders a line break between two inline elements as a space. The client compiles the same way with `trimEdges` from `ferrovue/whitespace` in `@vitejs/plugin-vue`'s `template.compilerOptions.nodeTransforms`.",
316
316
  "type": "string",
317
317
  "enum": [
318
318
  "condense",