ferrovue 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +32 -13
  2. package/dist/attrs.d.ts +2 -52
  3. package/dist/attrs.js +18 -448
  4. package/dist/calls.d.ts +4 -0
  5. package/dist/calls.js +219 -0
  6. package/dist/children.d.ts +9 -0
  7. package/dist/children.js +273 -0
  8. package/dist/classes.d.ts +13 -0
  9. package/dist/classes.js +176 -0
  10. package/dist/cli.js +0 -17
  11. package/dist/client.js +0 -12
  12. package/dist/compiler.js +25 -90
  13. package/dist/component.js +10 -86
  14. package/dist/context.d.ts +4 -66
  15. package/dist/context.js +2 -33
  16. package/dist/diff.d.ts +0 -3
  17. package/dist/diff.js +0 -9
  18. package/dist/emitter.d.ts +0 -8
  19. package/dist/emitter.js +0 -12
  20. package/dist/expr.d.ts +0 -70
  21. package/dist/expr.js +27 -890
  22. package/dist/fallthrough.d.ts +9 -0
  23. package/dist/fallthrough.js +78 -0
  24. package/dist/fixture.d.ts +2 -1
  25. package/dist/fixture.js +37 -22
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +1 -0
  28. package/dist/islands.d.ts +1 -0
  29. package/dist/islands.js +1 -0
  30. package/dist/lists.d.ts +0 -11
  31. package/dist/lists.js +4 -39
  32. package/dist/loops.d.ts +3 -0
  33. package/dist/loops.js +164 -0
  34. package/dist/model.d.ts +14 -143
  35. package/dist/model.js +27 -12
  36. package/dist/narrowing.d.ts +19 -0
  37. package/dist/narrowing.js +126 -0
  38. package/dist/numbers.d.ts +8 -0
  39. package/dist/numbers.js +73 -0
  40. package/dist/parens.d.ts +0 -27
  41. package/dist/parens.js +0 -44
  42. package/dist/plugin.d.ts +84 -0
  43. package/dist/plugin.js +26 -0
  44. package/dist/plugins/i18n.d.ts +23 -0
  45. package/dist/{i18n.js → plugins/i18n.js} +76 -19
  46. package/dist/plugins/index.d.ts +2 -0
  47. package/dist/plugins/index.js +8 -0
  48. package/dist/plugins/router-link.d.ts +8 -0
  49. package/dist/{router.js → plugins/router-link.js} +19 -61
  50. package/dist/plugins/router.d.ts +42 -0
  51. package/dist/plugins/router.js +256 -0
  52. package/dist/plugins/scoped.d.ts +12 -0
  53. package/dist/plugins/scoped.js +125 -0
  54. package/dist/plugins/shared-types.d.ts +2 -0
  55. package/dist/plugins/shared-types.js +7 -0
  56. package/dist/plugins/stores.d.ts +37 -0
  57. package/dist/plugins/stores.js +377 -0
  58. package/dist/plugins/teleport.d.ts +5 -0
  59. package/dist/plugins/teleport.js +55 -0
  60. package/dist/rust.d.ts +1 -7
  61. package/dist/rust.js +38 -160
  62. package/dist/script.d.ts +0 -6
  63. package/dist/script.js +13 -128
  64. package/dist/slots.d.ts +13 -0
  65. package/dist/slots.js +160 -0
  66. package/dist/strings.d.ts +13 -0
  67. package/dist/strings.js +208 -0
  68. package/dist/styles.d.ts +14 -0
  69. package/dist/styles.js +181 -0
  70. package/dist/template.d.ts +1 -42
  71. package/dist/template.js +14 -738
  72. package/dist/testing.d.ts +1 -0
  73. package/dist/testing.js +1 -8
  74. package/dist/types.d.ts +2 -0
  75. package/dist/typescript.d.ts +0 -13
  76. package/dist/typescript.js +8 -41
  77. package/dist/vite.d.ts +1 -0
  78. package/dist/vite.js +1 -23
  79. package/package.json +7 -2
  80. package/dist/i18n.d.ts +0 -27
  81. package/dist/router.d.ts +0 -17
  82. package/dist/scoped.d.ts +0 -24
  83. package/dist/scoped.js +0 -177
  84. package/dist/stores.d.ts +0 -6
  85. package/dist/stores.js +0 -229
package/dist/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { I18nSetup } from "./i18n.ts";
2
- import { type Component, type N, type Struct, type Ty } from "./model.ts";
1
+ import type { Plugin } from "./plugin.ts";
2
+ import { type Component, type Struct, type Ty } from "./model.ts";
3
3
  /** A type a helper takes or returns, as the configuration spells it. */
4
4
  export type TypeName = "string" | "string?" | "int" | "int?" | "float" | "float?" | "bool";
5
5
  /** A function a template may call, and the Rust function that is its twin. */
@@ -64,48 +64,9 @@ export declare const CONFIG_FILE = "ferrovue.config.json";
64
64
  /** Read the project's configuration. */
65
65
  export declare function loadConfig(root: string, configPath?: string): Config;
66
66
  export declare function tyOfName(name: TypeName): Ty;
67
- /** A route as the routes file lists it: a vue-router path, and the name it may have. */
68
- export interface RouteDef {
69
- /** The path as written: relative to the parent's unless it starts with \`/\`. */
70
- path: string;
71
- name?: string;
72
- /** The routes nested in it. */
73
- children?: RouteDef[];
74
- /** The path with its ancestors', as vue-router normalises it. */
75
- fullPath: string;
76
- }
77
- /** Every route, nested ones included, parents first. */
78
- export declare function allRoutes(routes: RouteDef[]): RouteDef[];
79
- /** A getter: the parameter naming the state (an option store's), and the expression it returns.
80
- * \`setup\` marks a setup store's \`computed\`, which reads the state's refs and the other getters by
81
- * name, through \`.value\`. */
82
- export interface StoreGetter {
83
- param: string | null;
84
- body: N;
85
- file: string;
86
- setup?: true;
87
- }
88
- /** A Pinia option store: `export const usePrefs = defineStore("prefs", { state: (): PrefsState => ... })`. */
89
- export interface Store {
90
- /** `usePrefs` */
91
- hook: string;
92
- /** `prefs`: its key in `pinia.state.value`. */
93
- id: string;
94
- /** `prefs`: its field in the generated `Stores`. */
95
- field: string;
96
- /** `PrefsState`: the interface its state is. */
97
- state: string;
98
- /** The file, without its extension, as an import names it once resolved. */
99
- module: string;
100
- /** Its getters: name → the parameter that names the state, and the expression returned. */
101
- getters: Map<string, StoreGetter>;
102
- }
103
67
  /** Where `TrustedHtml` comes from. Only that import names the type `v-html` will write raw. */
104
68
  export declare const TYPES_MODULE = "ferrovue/types";
105
- /** What the configuration being compiled says, and what has been read so far: set up by
106
- * `generate()` at the start of each run, which is synchronous, so one run never sees another's. */
107
69
  export declare const ctx: {
108
- /** The helpers of the configuration being compiled: name → Rust twin, typed. */
109
70
  helpers: Record<string, {
110
71
  rust: string;
111
72
  params: Ty[];
@@ -114,38 +75,15 @@ export declare const ctx: {
114
75
  }>;
115
76
  helperModule: string | null;
116
77
  trustedHtml: string | null;
117
- /** Custom directives declared to render nothing on the server. */
118
78
  clientDirectives: Set<string>;
119
- /** The configured routes, when there are any. */
120
- routes: RouteDef[] | null;
121
- /** The history's base, and the class names active links take, from the configured router. */
122
- routerBase: string;
123
- linkActive: string;
124
- linkExactActive: string;
125
- /** The configured stores, by hook name, and every interface their files declare. */
126
- stores: Map<string, Store>;
127
- storeStructs: Map<string, Struct>;
128
- /** The store file each of those interfaces is declared in. */
129
- storeFiles: Map<string, string>;
130
- /** The project root, for resolving a component's imports. */
79
+ plugins: readonly Plugin[];
80
+ runs: Map<Plugin<unknown, unknown>, unknown>;
131
81
  rootDir: string;
132
- /** Interfaces and object types declared in shared `.ts` files that components import, and type
133
- * aliases there; the file each comes from. They are written once, to `types.rs`. */
134
82
  typeStructs: Map<string, Struct>;
135
83
  typeAliases: Map<string, any>;
136
84
  typeFiles: Map<string, string>;
137
- /** The shared files read so far. */
138
85
  typeRead: Set<string>;
139
- /** Every component being compiled, by name, for a type that names another one's props. */
140
86
  components: Map<string, Component>;
141
- /** Numbers the bindings narrowing introduces, restarted per component so that a file's text
142
- * depends on its own source alone. */
143
87
  narrowCount: number;
144
- /** The directory the components being compiled come from, for the generated headers. */
145
88
  componentsDir: string;
146
- /** The configured locales and their messages, when there are any. */
147
- i18n: I18nSetup | null;
148
- /** How scope ids are computed, and the directory a component's path is hashed from. */
149
- scopeId: ScopeIdMode;
150
- viteRoot: string;
151
89
  };
package/dist/context.js CHANGED
@@ -1,4 +1,3 @@
1
- /* The project's configuration, and the state one `generate()` run builds up. */
2
1
  import { readFileSync } from "node:fs";
3
2
  import { join, resolve } from "node:path";
4
3
  import { BOOL, FLOAT, GenError, INT, opt, STR } from "./model.js";
@@ -52,51 +51,21 @@ export function tyOfName(name) {
52
51
  throw new GenError(`unknown helper type \`${String(name)}\``);
53
52
  }
54
53
  }
55
- /** Every route, nested ones included, parents first. */
56
- export function allRoutes(routes) {
57
- return routes.flatMap((r) => [r, ...allRoutes(r.children ?? [])]);
58
- }
59
54
  /** Where `TrustedHtml` comes from. Only that import names the type `v-html` will write raw. */
60
55
  export const TYPES_MODULE = "ferrovue/types";
61
- /** What the configuration being compiled says, and what has been read so far: set up by
62
- * `generate()` at the start of each run, which is synchronous, so one run never sees another's. */
63
56
  export const ctx = {
64
- /** The helpers of the configuration being compiled: name → Rust twin, typed. */
65
57
  helpers: {},
66
58
  helperModule: null,
67
59
  trustedHtml: null,
68
- /** Custom directives declared to render nothing on the server. */
69
60
  clientDirectives: new Set(),
70
- /** The configured routes, when there are any. */
71
- routes: null,
72
- /** The history's base, and the class names active links take, from the configured router. */
73
- routerBase: "",
74
- linkActive: "router-link-active",
75
- linkExactActive: "router-link-exact-active",
76
- /** The configured stores, by hook name, and every interface their files declare. */
77
- stores: new Map(),
78
- storeStructs: new Map(),
79
- /** The store file each of those interfaces is declared in. */
80
- storeFiles: new Map(),
81
- /** The project root, for resolving a component's imports. */
61
+ plugins: [],
62
+ runs: new Map(),
82
63
  rootDir: "",
83
- /** Interfaces and object types declared in shared `.ts` files that components import, and type
84
- * aliases there; the file each comes from. They are written once, to `types.rs`. */
85
64
  typeStructs: new Map(),
86
65
  typeAliases: new Map(),
87
66
  typeFiles: new Map(),
88
- /** The shared files read so far. */
89
67
  typeRead: new Set(),
90
- /** Every component being compiled, by name, for a type that names another one's props. */
91
68
  components: new Map(),
92
- /** Numbers the bindings narrowing introduces, restarted per component so that a file's text
93
- * depends on its own source alone. */
94
69
  narrowCount: 0,
95
- /** The directory the components being compiled come from, for the generated headers. */
96
70
  componentsDir: "",
97
- /** The configured locales and their messages, when there are any. */
98
- i18n: null,
99
- /** How scope ids are computed, and the directory a component's path is hashed from. */
100
- scopeId: "filepath-source",
101
- viteRoot: "",
102
71
  };
package/dist/diff.d.ts CHANGED
@@ -1,4 +1 @@
1
- /** A unified diff of a module, as \`diff -u\` writes it, with three lines of context: what
2
- * \`ferrovue\` would write against what is committed. Lines the two share at the start and the end
3
- * are set aside first, so that a module changed in one place is compared over that place alone. */
4
1
  export declare function unifiedDiff(path: string, before: string, after: string): string;
package/dist/diff.js CHANGED
@@ -1,7 +1,3 @@
1
- /* `ferrovue --check --diff`: what would be written, against what is committed. */
2
- /** A unified diff of a module, as \`diff -u\` writes it, with three lines of context: what
3
- * \`ferrovue\` would write against what is committed. Lines the two share at the start and the end
4
- * are set aside first, so that a module changed in one place is compared over that place alone. */
5
1
  export function unifiedDiff(path, before, after) {
6
2
  const lines = (text) => (text === "" ? [] : text.replace(/\n$/, "").split("\n"));
7
3
  const a = lines(before);
@@ -16,7 +12,6 @@ export function unifiedDiff(path, before, after) {
16
12
  const midB = b.slice(head, b.length - tail);
17
13
  if (!midA.length && !midB.length)
18
14
  return "";
19
- // The longest common subsequence of the middle, by dynamic programming over its lines.
20
15
  const lcs = Array.from({ length: midA.length + 1 }, () => new Uint32Array(midB.length + 1));
21
16
  for (let i = midA.length - 1; i >= 0; i--) {
22
17
  for (let j = midB.length - 1; j >= 0; j--) {
@@ -26,7 +21,6 @@ export function unifiedDiff(path, before, after) {
26
21
  const edits = a.slice(0, head).map((text) => ({ op: " ", text }));
27
22
  let i = 0;
28
23
  let j = 0;
29
- // Of two ways to keep as many lines, a removal comes first, as `diff -u` orders them.
30
24
  while (i < midA.length || j < midB.length) {
31
25
  if (i < midA.length && j < midB.length && midA[i] === midB[j]) {
32
26
  edits.push({ op: " ", text: midA[i++] });
@@ -38,7 +32,6 @@ export function unifiedDiff(path, before, after) {
38
32
  edits.push({ op: "-", text: midA[i++] });
39
33
  }
40
34
  edits.push(...a.slice(a.length - tail).map((text) => ({ op: " ", text })));
41
- // Each changed line with three lines around it, runs that touch joined into one hunk.
42
35
  const CONTEXT = 3;
43
36
  const hunks = [];
44
37
  edits.forEach((e, k) => {
@@ -53,7 +46,6 @@ export function unifiedDiff(path, before, after) {
53
46
  hunks.push([from, to]);
54
47
  });
55
48
  const out = [`--- ${a.length ? `a/${path}` : "/dev/null"}`, `+++ ${b.length ? `b/${path}` : "/dev/null"}`];
56
- // Line numbers in each file where every edit starts.
57
49
  let lineA = 1;
58
50
  let lineB = 1;
59
51
  const at = edits.map((e) => {
@@ -68,7 +60,6 @@ export function unifiedDiff(path, before, after) {
68
60
  const span = edits.slice(from, to + 1);
69
61
  const countA = span.filter((e) => e.op !== "+").length;
70
62
  const countB = span.filter((e) => e.op !== "-").length;
71
- // An empty side is numbered by the line before it, as `diff -u` numbers it.
72
63
  const startA = countA ? at[from][0] : at[from][0] - 1;
73
64
  const startB = countB ? at[from][1] : at[from][1] - 1;
74
65
  out.push(`@@ -${startA},${countA} +${startB},${countB} @@`, ...span.map((e) => `${e.op}${e.text}`));
package/dist/emitter.d.ts CHANGED
@@ -1,23 +1,15 @@
1
- /** Statements, with adjacent literal pushes merged into one `push_str`. */
2
1
  export declare class Emitter {
3
2
  lines: string[];
4
- /** Bytes of markup written once per render, part of what `render` reserves up front: the literal
5
- * markup, and what the numbers written at run time are expected to take. */
6
3
  literalBytes: number;
7
- /** Rust expressions for the rest of the reservation: a loop's markup once per item. */
8
4
  perItem: string[];
9
5
  private pending;
10
6
  private depth;
11
7
  lit(s: string): void;
12
- /** `bytes` more expected of a value written at run time. */
13
8
  expect(bytes: number): void;
14
9
  stmt(code: string): void;
15
10
  open(code: string): void;
16
11
  close(tail?: string): void;
17
- /** Whether the lines from \`from\` on read \`name\`. Literals are skipped, so markup that happens to
18
- * spell the name does not count, and so are fields of that name. */
19
12
  reads(name: string, from: number): boolean;
20
- /** Replace \`from\` with \`to\` in line \`index\`: an unused binding renamed to \`_\`. */
21
13
  replace(index: number, from: string, to: string): void;
22
14
  flush(): void;
23
15
  }
package/dist/emitter.js CHANGED
@@ -1,13 +1,8 @@
1
- /* The Rust statements a render is written as. */
2
1
  import { rustChar, rustStr } from "./model.js";
3
2
  import { occurrences } from "./parens.js";
4
- /** Statements, with adjacent literal pushes merged into one `push_str`. */
5
3
  export class Emitter {
6
4
  lines = [];
7
- /** Bytes of markup written once per render, part of what `render` reserves up front: the literal
8
- * markup, and what the numbers written at run time are expected to take. */
9
5
  literalBytes = 0;
10
- /** Rust expressions for the rest of the reservation: a loop's markup once per item. */
11
6
  perItem = [];
12
7
  pending = "";
13
8
  depth = 1;
@@ -15,7 +10,6 @@ export class Emitter {
15
10
  this.pending += s;
16
11
  this.literalBytes += Buffer.byteLength(s);
17
12
  }
18
- /** `bytes` more expected of a value written at run time. */
19
13
  expect(bytes) {
20
14
  this.literalBytes += bytes;
21
15
  }
@@ -31,19 +25,14 @@ export class Emitter {
31
25
  this.flush();
32
26
  this.depth--;
33
27
  this.lines.push(" ".repeat(this.depth) + "}" + tail);
34
- // `} else {` closes one block and opens the next.
35
28
  if (tail.endsWith("{"))
36
29
  this.depth++;
37
30
  }
38
- /** Whether the lines from \`from\` on read \`name\`. Literals are skipped, so markup that happens to
39
- * spell the name does not count, and so are fields of that name. */
40
31
  reads(name, from) {
41
32
  this.flush();
42
33
  return occurrences(this.lines.slice(from).join("\n"), name) > 0;
43
34
  }
44
- /** Replace \`from\` with \`to\` in line \`index\`: an unused binding renamed to \`_\`. */
45
35
  replace(index, from, to) {
46
- // A function, so a `$&` or `$'` in the generated text is not read as a replacement pattern.
47
36
  this.lines[index] = this.lines[index].replace(from, () => to);
48
37
  }
49
38
  flush() {
@@ -51,7 +40,6 @@ export class Emitter {
51
40
  return;
52
41
  const text = this.pending;
53
42
  this.pending = "";
54
- // One character is pushed as a `char`.
55
43
  const push = /^.$/su.test(text) ? `out.push(${rustChar(text)});` : `out.push_str(${rustStr(text)});`;
56
44
  this.lines.push(" ".repeat(this.depth) + push);
57
45
  }
package/dist/expr.d.ts CHANGED
@@ -1,76 +1,6 @@
1
1
  import { type Component, type N, type Scope, type Ty, type Val } from "./model.ts";
2
- /** The refusal for a string that may hold half of a surrogate pair, used where that half matters. */
3
- export declare function lonely(what: string): string;
4
- /** Two strings meeting, where a half of a surrogate pair — which ferrovue holds as U+FFFD — would
5
- * change the result:
6
- * - `equal`: two halves may differ, and a half never equals a literal U+FFFD;
7
- * - `order`: a half orders as a surrogate, below U+E000, where U+FFFD orders above — the same only
8
- * against a literal of characters below U+D800, which both order after;
9
- * - `search`: a half searched for (`b`) matches half of a whole pair in `a`;
10
- * - `join`: two halves side by side join into one character.
11
- * A string holding U+FFFD in the data still equals, or is found in, a half: README, "Strings". */
12
- export declare function meet(comp: Component, a: Val, b: Val, what: string, n: N, how: "equal" | "order" | "search" | "join"): void;
13
- /** Whether a string expression borrows from a temporary it builds — \`&*format!(…)\`, a call that
14
- * returns a \`String\` — rather than from the props or the state. Such a borrow ends with the block
15
- * it is in, so a branch of \`?:\` or \`||\` must hand back the string itself. */
16
- export declare function isTemporary(v: Val): boolean;
17
- /** A string as a \`Cow\`: borrowed when it lives on, owned when it is a temporary. The \`Cow\` is
18
- * itself a temporary of the whole statement, so the \`&str\` taken from it lives to its end. A
19
- * \`String\` built for the purpose is moved into it, and one that is already a \`Cow\` — a branch
20
- * of \`?:\` or \`||\` — is taken as it is. */
21
- export declare function asCow(v: Val): string;
22
- /** Whether built code is a `Cow<str>` already: a runtime routine that borrows when it can
23
- * (`js_slice`, `js_replace`, `js_pad_start`, …), or a fallback taken from one (`.unwrap_or(Cow…)`). */
24
- export declare function yieldsCow(code: string): boolean;
25
- /** Whether a value is a JavaScript number: an integer or a fraction. */
26
- export declare function isNumber(ty: Ty): boolean;
27
- /** A number as an `f64`, as JavaScript holds every number. */
28
- export declare function asF64(v: Val): string;
29
- /** A type as an error names it. */
30
2
  export declare function describeTy(ty: Ty): string;
31
3
  export declare function fieldVal(comp: Component, base: string, ty: Ty, js: string, node: N): Val;
32
- /** A value's truthiness when it is known at generation time: a boolean constant, a string literal,
33
- * a number JavaScript computed. */
34
- export declare function known(v: Val): boolean | undefined;
35
- export declare function truthy(v: Val): string;
36
- /** A value passed where `want` is expected: a present value into an optional slot is wrapped. */
37
4
  export declare function coerce(comp: Component, v: Val, want: Ty, node: N): string;
38
- /** The JavaScript path an expression names — `$props.user`, `item.author.name` — or `null` when it
39
- * is not a plain chain of names. */
40
- export declare function pathOf(n: N): string | null;
41
- /** A test that an optional value is present, as TypeScript narrows on it: `x`, `x !== undefined`,
42
- * and, `negated`, `!x` and `x === undefined`, after which the value is present in the other branch. */
43
- export interface Presence {
44
- path: string;
45
- /** The value bound where it is present, and its type there. */
46
- pattern: (name: string) => string;
47
- /** The `Option` the pattern matches: the value, or for a truthiness test only its truthy values. */
48
- option: string;
49
- /** The test alone, for a branch that never reads the value. */
50
- present: string;
51
- of: Ty;
52
- negated: boolean;
53
- /** Tested by truthiness, so the test itself is a boolean only when it is negated. */
54
- truthy: boolean;
55
- }
56
- export declare function presence(s: Scope, n: N): Presence | null;
57
- /** The scope with `p`'s value present, bound as `name`. */
58
- export declare function narrowTo(s: Scope, p: Presence, name: string): Scope;
59
- export declare function cond(s: Scope, n: N): string;
60
5
  export declare function childOf(name: string): Component;
61
- /** The reader's route, which the component then takes. */
62
- export declare function theRoute(s: Scope, n: N): Val;
63
- /** A field of the route: what `useRoute()` gives that the server knows as vue-router does. */
64
- export declare function routeField(s: Scope, base: Val, prop: string, n: N): Val;
65
- /** A Pinia getter read from a store's state, `prefs.doubled`: its expression, translated with the
66
- * state it takes bound to that state. `null` when `name` is not a getter of that store. */
67
- export declare function storeGetter(s: Scope, base: Val, name: string, node: N): Val | null;
68
- /** A Rust string literal's text. */
69
- export declare function unquote(literal: string): string;
70
6
  export declare function expr(s: Scope, n: N): Val;
71
- /** A boolean computed at run time, or known now when it folded to a constant. */
72
- export declare function boolOf(code: string): Val;
73
- export declare function call(s: Scope, n: N): Val;
74
- /** Whether `n` is `Object.<method>(…)`. */
75
- export declare function isObjectCall(n: N, method: string): boolean;
76
- export declare function helperCall(s: Scope, name: string, args: N[], n: N): Val;