ferrovue 0.2.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 (86) hide show
  1. package/README.md +103 -15
  2. package/dist/attrs.d.ts +8 -42
  3. package/dist/attrs.js +148 -297
  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 +121 -27
  11. package/dist/client.d.ts +11 -2
  12. package/dist/client.js +45 -9
  13. package/dist/compiler.d.ts +4 -0
  14. package/dist/compiler.js +37 -90
  15. package/dist/component.js +19 -82
  16. package/dist/context.d.ts +6 -68
  17. package/dist/context.js +25 -38
  18. package/dist/diff.d.ts +1 -0
  19. package/dist/diff.js +68 -0
  20. package/dist/emitter.d.ts +0 -8
  21. package/dist/emitter.js +0 -12
  22. package/dist/expr.d.ts +0 -70
  23. package/dist/expr.js +30 -855
  24. package/dist/fallthrough.d.ts +9 -0
  25. package/dist/fallthrough.js +78 -0
  26. package/dist/fixture.d.ts +2 -1
  27. package/dist/fixture.js +37 -22
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +1 -0
  30. package/dist/islands.d.ts +6 -0
  31. package/dist/islands.js +5 -0
  32. package/dist/lists.d.ts +0 -11
  33. package/dist/lists.js +4 -39
  34. package/dist/loops.d.ts +3 -0
  35. package/dist/loops.js +164 -0
  36. package/dist/model.d.ts +22 -130
  37. package/dist/model.js +37 -9
  38. package/dist/narrowing.d.ts +19 -0
  39. package/dist/narrowing.js +126 -0
  40. package/dist/numbers.d.ts +8 -0
  41. package/dist/numbers.js +73 -0
  42. package/dist/parens.d.ts +0 -27
  43. package/dist/parens.js +0 -44
  44. package/dist/plugin.d.ts +84 -0
  45. package/dist/plugin.js +26 -0
  46. package/dist/plugins/i18n.d.ts +23 -0
  47. package/dist/{i18n.js → plugins/i18n.js} +76 -19
  48. package/dist/plugins/index.d.ts +2 -0
  49. package/dist/plugins/index.js +8 -0
  50. package/dist/plugins/router-link.d.ts +8 -0
  51. package/dist/{router.js → plugins/router-link.js} +65 -57
  52. package/dist/plugins/router.d.ts +42 -0
  53. package/dist/plugins/router.js +256 -0
  54. package/dist/plugins/scoped.d.ts +12 -0
  55. package/dist/{scoped.js → plugins/scoped.js} +55 -20
  56. package/dist/plugins/shared-types.d.ts +2 -0
  57. package/dist/plugins/shared-types.js +7 -0
  58. package/dist/plugins/stores.d.ts +37 -0
  59. package/dist/plugins/stores.js +377 -0
  60. package/dist/plugins/teleport.d.ts +5 -0
  61. package/dist/plugins/teleport.js +55 -0
  62. package/dist/rust.d.ts +2 -5
  63. package/dist/rust.js +66 -161
  64. package/dist/script.d.ts +0 -6
  65. package/dist/script.js +22 -121
  66. package/dist/slots.d.ts +13 -0
  67. package/dist/slots.js +160 -0
  68. package/dist/strings.d.ts +13 -0
  69. package/dist/strings.js +208 -0
  70. package/dist/styles.d.ts +14 -0
  71. package/dist/styles.js +181 -0
  72. package/dist/template.d.ts +2 -40
  73. package/dist/template.js +40 -691
  74. package/dist/testing.d.ts +1 -0
  75. package/dist/testing.js +1 -8
  76. package/dist/types.d.ts +2 -0
  77. package/dist/typescript.d.ts +0 -13
  78. package/dist/typescript.js +8 -41
  79. package/dist/vite.d.ts +1 -0
  80. package/dist/vite.js +35 -14
  81. package/package.json +11 -2
  82. package/dist/i18n.d.ts +0 -27
  83. package/dist/router.d.ts +0 -17
  84. package/dist/scoped.d.ts +0 -10
  85. package/dist/stores.d.ts +0 -6
  86. package/dist/stores.js +0 -227
package/dist/testing.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import type { Component } from "vue";
2
2
  export { fixtureApp, readFixture, routeRecords, type Fixture, type RouteEntry, type RouterOptions } from "./fixture.ts";
3
+ /** Give a component compiled for the browser the `ssrRender` its SSR build would have. */
3
4
  export declare function attachSsrRender(file: string, name: string, component: Component): void;
package/dist/testing.js CHANGED
@@ -1,19 +1,12 @@
1
- /* What a conformance suite needs from Vue: a component's real server render, and an app that
2
- * renders one fixture (`fixture.ts`, which describes a fixture's keys). */
3
1
  import { readFileSync } from "node:fs";
4
2
  import { compileScript, compileTemplate, parse as parseSfc } from "@vue/compiler-sfc";
5
3
  import * as vue from "vue";
6
4
  import * as serverRenderer from "vue/server-renderer";
7
5
  export { fixtureApp, readFixture, routeRecords } from "./fixture.js";
8
- /* A bundler compiles a `.vue` file for the browser, and `renderToString` on a component without
9
- * `ssrRender` falls back to rendering its virtual DOM — a different path with different output
10
- * (attribute order, `v-model` state, comment markers). So each component is given the `ssrRender`
11
- * its SSR build would have, which is also what the generator translates. */
6
+ /** Give a component compiled for the browser the `ssrRender` its SSR build would have. */
12
7
  export function attachSsrRender(file, name, component) {
13
8
  const { descriptor } = parseSfc(readFileSync(file, "utf8"), { filename: file });
14
9
  const script = compileScript(descriptor, { id: name });
15
- // A `<style scoped>` component's id is the one `@vitejs/plugin-vue` gave it, which its client
16
- // build carries and which ferrovue computes the same way.
17
10
  const scopeId = component.__scopeId;
18
11
  if (!scopeId && descriptor.styles.some((st) => st.scoped)) {
19
12
  throw new Error(`${file}: a \`<style scoped>\` component without \`__scopeId\`: load it through \`@vitejs/plugin-vue\``);
package/dist/types.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  declare const trusted: unique symbol;
2
+ /** HTML already made safe: the only type `v-html` may render. */
2
3
  export type TrustedHtml = string & {
3
4
  readonly [trusted]: true;
4
5
  };
6
+ /** A number that may have a fractional part: an `f64` on the server, where a plain `number` is an `i64`. */
5
7
  export type Float = number;
6
8
  export {};
@@ -1,19 +1,10 @@
1
1
  import { type Component, type Field, type N, type Struct, type Ty } from "./model.ts";
2
- /** The local names \`TrustedHtml\` and \`Float\` are imported under from \`ferrovue/types\`. */
3
2
  export declare function typesImports(comp: Component, body: N[]): void;
4
3
  export declare function tyOfTs(comp: Component, t: N, structs: Map<string, Struct>, seen?: Set<string>): Ty;
5
- /** Every local name an interface or object type alias declares a struct for, and every other
6
- * alias: what a type in that block may name. */
7
4
  export declare function declareTypes(comp: Component, body: N[], structs: Map<string, Struct>, aliases: Map<string, N>): N[];
8
- /** A relative import's file, resolved as a bundler resolves a TypeScript import. */
9
5
  export declare function resolveImport(fromFile: string, spec: string): string | null;
10
- /** A shared `.ts` file of types, read once: its interfaces and object types become structs in
11
- * `types.rs`, its other aliases are resolved where they are used. */
12
6
  export declare function readTypeFile(file: string): void;
13
- /** A type read in the file that declares it, marked with where it lives for readers elsewhere. */
14
7
  export declare function markHome(ty: Ty, home: string): Ty;
15
- /** Where a struct type is declared: its fields, the component whose types its fields name, and
16
- * the path the generated code names it by. */
17
8
  export declare function lookupStruct(comp: Component, ty: Ty & {
18
9
  k: "struct";
19
10
  }): {
@@ -22,10 +13,6 @@ export declare function lookupStruct(comp: Component, ty: Ty & {
22
13
  path: string;
23
14
  };
24
15
  export declare function structOf(comp: Component, name: string, members: N[], structs: Map<string, Struct>): Struct;
25
- /** The type literal (or interface) `defineProps<...>()` was given, seen through `withDefaults`. */
26
16
  export declare function definePropsType(call: N): N | null;
27
- /** The `default` of each prop in the runtime declaration `compileScript` wrote, by prop name. */
28
17
  export declare function runtimeDefaults(comp: Component, content: string): Map<string, N>;
29
- /** A prop's default as a Rust value of its type: a literal, or an empty list from `() => []`. */
30
18
  export declare function defaultValue(comp: Component, f: Field, node: N): string;
31
- export declare function markStore(ty: Ty): Ty;
@@ -1,12 +1,10 @@
1
- /* TypeScript types read as Rust ones: props, interfaces, aliases, shared type files, defaults. */
2
1
  import { parse as parseJs } from "@babel/parser";
3
2
  import { readFileSync, statSync } from "node:fs";
4
- import { dirname, join, relative, resolve } from "node:path";
5
- import { BOOL, fail, FLOAT, INT, opt, RUST_PRELUDE, rustStr, sameTy, snake, STR, tagAst } from "./model.js";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
4
+ import { blankComponent, BOOL, fail, FLOAT, INT, opt, RUST_PRELUDE, rustStr, sameTy, snake, STR, tagAst } from "./model.js";
6
5
  import { CONFIG_FILE, ctx, TYPES_MODULE } from "./context.js";
7
6
  import { childOf } from "./expr.js";
8
- import { storeHome } from "./stores.js";
9
- /** The local names \`TrustedHtml\` and \`Float\` are imported under from \`ferrovue/types\`. */
7
+ import { claim } from "./plugin.js";
10
8
  export function typesImports(comp, body) {
11
9
  for (const s of body) {
12
10
  if (s.type !== "ImportDeclaration" || s.source.value !== TYPES_MODULE)
@@ -35,7 +33,6 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
35
33
  case "TSParenthesizedType":
36
34
  return tyOfTs(comp, t.typeAnnotation, structs, seen);
37
35
  case "TSTypeLiteral": {
38
- // `{ [key: string]: T }`, which is `Record<string, T>`. Any other object type is an interface.
39
36
  const [sig] = t.members;
40
37
  const key = sig?.parameters?.[0]?.typeAnnotation?.typeAnnotation;
41
38
  if (t.members.length === 1 && sig.type === "TSIndexSignature" && key?.type === "TSStringKeyword" && sig.typeAnnotation) {
@@ -44,12 +41,10 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
44
41
  return fail(comp, "an object type in place: declare it as an interface, or as `{ [key: string]: T }` for a dictionary", t);
45
42
  }
46
43
  case "TSTypeOperator":
47
- // `readonly string[]`: the same list, which the server never writes to anyway.
48
44
  if (t.operator === "readonly")
49
45
  return tyOfTs(comp, t.typeAnnotation, structs, seen);
50
46
  break;
51
47
  case "TSLiteralType":
52
- // A literal type is a value of its kind: `"sm"` a string, `3` a number.
53
48
  if (t.literal.type === "StringLiteral" || t.literal.type === "TemplateLiteral")
54
49
  return STR;
55
50
  if (t.literal.type === "NumericLiteral")
@@ -58,7 +53,6 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
58
53
  return BOOL;
59
54
  break;
60
55
  case "TSUnionType": {
61
- // `"sm" | "md"` is a string; `T | undefined` is an optional `T`. `null` is a different value.
62
56
  const parts = t.types.filter((u) => u.type !== "TSUndefinedKeyword");
63
57
  if (t.types.some((u) => u.type === "TSNullKeyword" || (u.type === "TSLiteralType" && u.literal.type === "NullLiteral"))) {
64
58
  return fail(comp, "`null` in a type: use `undefined`, which is what an absent value is", t);
@@ -73,11 +67,9 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
73
67
  const name = t.typeName.type === "Identifier" ? t.typeName.name : undefined;
74
68
  if (name === undefined)
75
69
  break;
76
- // `Array<T>` and `ReadonlyArray<T>`, as `T[]`.
77
70
  if ((name === "Array" || name === "ReadonlyArray") && t.typeParameters?.params?.length === 1) {
78
71
  return { k: "list", of: tyOfTs(comp, t.typeParameters.params[0], structs, seen) };
79
72
  }
80
- // `Record<string, T>`, an object used as a dictionary.
81
73
  if (name === "Record" && t.typeParameters?.params?.length === 2) {
82
74
  const [key, value] = t.typeParameters.params;
83
75
  if (key.type !== "TSStringKeyword")
@@ -110,8 +102,6 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
110
102
  }
111
103
  return fail(comp, `unsupported prop type \`${t.type}\``, t);
112
104
  }
113
- /** A dictionary's type: its values strings, numbers, booleans, objects or lists of those. A value
114
- * may not be optional: JSON has no `undefined` to hold. */
115
105
  function recordOf(comp, value, structs, seen) {
116
106
  const of = tyOfTs(comp, value, structs, seen);
117
107
  const plain = (t) => ["str", "int", "float", "bool", "struct", "child"].includes(t.k) || (t.k === "list" && plain(t.of));
@@ -119,8 +109,6 @@ function recordOf(comp, value, structs, seen) {
119
109
  fail(comp, "a `Record`'s values are strings, numbers, booleans, objects or lists of those", value);
120
110
  return { k: "record", of };
121
111
  }
122
- /** Every local name an interface or object type alias declares a struct for, and every other
123
- * alias: what a type in that block may name. */
124
112
  export function declareTypes(comp, body, structs, aliases) {
125
113
  const decls = [];
126
114
  for (const st of body) {
@@ -128,7 +116,6 @@ export function declareTypes(comp, body, structs, aliases) {
128
116
  if (d?.type === "TSInterfaceDeclaration")
129
117
  decls.push({ name: d.id.name, members: d.body.body, node: d });
130
118
  else if (d?.type === "TSTypeAliasDeclaration") {
131
- // An object type is a struct — but for a dictionary, `{ [key: string]: T }`, an alias of its own.
132
119
  const dictionary = d.typeAnnotation.members?.some((m) => m.type === "TSIndexSignature");
133
120
  if (d.typeAnnotation.type === "TSTypeLiteral" && !dictionary)
134
121
  decls.push({ name: d.id.name, members: d.typeAnnotation.members, node: d });
@@ -144,7 +131,6 @@ export function declareTypes(comp, body, structs, aliases) {
144
131
  }
145
132
  return decls;
146
133
  }
147
- /** A relative import's file, resolved as a bundler resolves a TypeScript import. */
148
134
  export function resolveImport(fromFile, spec) {
149
135
  if (!spec.startsWith("."))
150
136
  return null;
@@ -155,20 +141,16 @@ export function resolveImport(fromFile, spec) {
155
141
  return candidate;
156
142
  }
157
143
  catch {
158
- // not this one
159
144
  }
160
145
  }
161
146
  return null;
162
147
  }
163
- /** A shared `.ts` file of types, read once: its interfaces and object types become structs in
164
- * `types.rs`, its other aliases are resolved where they are used. */
165
148
  export function readTypeFile(file) {
166
149
  if (ctx.typeRead.has(file))
167
150
  return;
168
151
  ctx.typeRead.add(file);
169
152
  const rel = relative(ctx.rootDir, file);
170
- const home = storeHome(rel);
171
- home.structs = ctx.typeStructs;
153
+ const home = blankComponent(basename(rel), "types", rel, ctx.typeStructs);
172
154
  home.aliases = ctx.typeAliases;
173
155
  home.source = readFileSync(file, "utf8");
174
156
  const body = parseJs(home.source, { sourceType: "module", plugins: ["typescript"] }).program.body;
@@ -190,20 +172,17 @@ export function readTypeFile(file) {
190
172
  ctx.typeStructs.set(d.name, st);
191
173
  }
192
174
  }
193
- /** A type read in the file that declares it, marked with where it lives for readers elsewhere. */
194
175
  export function markHome(ty, home) {
195
- if (ty.k === "struct" && !ty.store && !ty.home && ty.name !== "Props")
176
+ if (ty.k === "struct" && !claim((p) => p.struct?.(ty)) && !ty.home && ty.name !== "Props")
196
177
  return { ...ty, home };
197
178
  if (ty.k === "opt" || ty.k === "list" || ty.k === "record")
198
179
  return { ...ty, of: markHome(ty.of, home) };
199
180
  return ty;
200
181
  }
201
- /** Where a struct type is declared: its fields, the component whose types its fields name, and
202
- * the path the generated code names it by. */
203
182
  export function lookupStruct(comp, ty) {
204
- // Named from the module being written: plainly within its own, by path from any other.
205
- if (ty.store)
206
- return { st: ctx.storeStructs.get(ty.name), owner: comp, path: comp.module === "stores" ? "" : "super::stores::" };
183
+ const own = claim((p) => p.struct?.(ty));
184
+ if (own)
185
+ return { st: own.st, owner: comp, path: comp.module === own.module ? "" : `super::${own.module}::` };
207
186
  if (ty.home === "types")
208
187
  return { st: ctx.typeStructs.get(ty.name), owner: comp, path: comp.module === "types" ? "" : "super::types::" };
209
188
  if (ty.home !== undefined && ty.home !== comp.name) {
@@ -227,7 +206,6 @@ export function structOf(comp, name, members, structs) {
227
206
  }
228
207
  return { name, fields };
229
208
  }
230
- /** The type literal (or interface) `defineProps<...>()` was given, seen through `withDefaults`. */
231
209
  export function definePropsType(call) {
232
210
  if (call?.type === "CallExpression" && call.callee.type === "Identifier" && call.callee.name === "withDefaults") {
233
211
  return definePropsType(call.arguments[0]);
@@ -239,7 +217,6 @@ export function definePropsType(call) {
239
217
  }
240
218
  return null;
241
219
  }
242
- /** The `default` of each prop in the runtime declaration `compileScript` wrote, by prop name. */
243
220
  export function runtimeDefaults(comp, content) {
244
221
  const out = new Map();
245
222
  let program;
@@ -249,7 +226,6 @@ export function runtimeDefaults(comp, content) {
249
226
  catch {
250
227
  return out;
251
228
  }
252
- /** An object literal's properties, with `...{ … }` spreads opened, as `defineModel` writes them. */
253
229
  const props = (o) => o?.type !== "ObjectExpression"
254
230
  ? []
255
231
  : o.properties.flatMap((p) => (p.type === "SpreadElement" ? props(p.argument) : [p]));
@@ -257,7 +233,6 @@ export function runtimeDefaults(comp, content) {
257
233
  if (!n || typeof n !== "object")
258
234
  return;
259
235
  if (n.type === "ObjectProperty" && !n.computed && (n.key.name ?? n.key.value) === "props") {
260
- // `props: { … }`, or `_mergeModels({ … }, { … })` when there is a `defineModel`.
261
236
  const objects = n.value.type === "CallExpression" ? n.value.arguments : [n.value];
262
237
  for (const o of objects) {
263
238
  for (const p of props(o)) {
@@ -284,7 +259,6 @@ export function runtimeDefaults(comp, content) {
284
259
  void comp;
285
260
  return out;
286
261
  }
287
- /** A prop's default as a Rust value of its type: a literal, or an empty list from `() => []`. */
288
262
  export function defaultValue(comp, f, node) {
289
263
  const of = f.ty.k === "opt" ? f.ty.of : f.ty;
290
264
  if (of.k === "str" && node.type === "StringLiteral")
@@ -301,10 +275,3 @@ export function defaultValue(comp, f, node) {
301
275
  }
302
276
  return fail(comp, `the default of \`${f.js}\` must be a literal of its type, or \`() => []\` for a list`, node);
303
277
  }
304
- export function markStore(ty) {
305
- if (ty.k === "struct")
306
- return { ...ty, store: true };
307
- if (ty.k === "opt" || ty.k === "list" || ty.k === "record")
308
- return { ...ty, of: markStore(ty.of) };
309
- return ty;
310
- }
package/dist/vite.d.ts CHANGED
@@ -20,4 +20,5 @@ export declare function scopeIdMismatch(root: string, vue: {
20
20
  mode: string;
21
21
  root: string;
22
22
  } | null): string | null;
23
+ /** The Rust renderers regenerated by a Vite build and its dev server, and the `ferrovue/islands` module. */
23
24
  export default function ferrovue(options?: FerrovueOptions): Plugin;
package/dist/vite.js CHANGED
@@ -1,12 +1,3 @@
1
- /* `ferrovue/vite`: the Rust renderers regenerated as part of a Vite build and its dev server.
2
- *
3
- * import ferrovue from "ferrovue/vite";
4
- * export default defineConfig({ plugins: [vue(), ferrovue()] });
5
- *
6
- * A build regenerates them once and fails on a construct ferrovue refuses, naming the line in the
7
- * `.vue` file. The dev server regenerates them whenever a component, store, type file, the routes
8
- * or the configuration changes, and shows a refusal in its error overlay. Only files whose text
9
- * changed are rewritten, so `cargo watch` rebuilds no more than it must. */
10
1
  import { readdirSync, readFileSync } from "node:fs";
11
2
  import { join, relative, resolve, sep } from "node:path";
12
3
  import { CONFIG_FILE, GenError, loadConfig, write } from "./compiler.js";
@@ -21,7 +12,6 @@ export function affects(root, file) {
21
12
  out = loadConfig(root).out;
22
13
  }
23
14
  catch {
24
- // A broken configuration ignores nothing; regenerating reports it.
25
15
  }
26
16
  const parts = rel.split(sep);
27
17
  if (parts.some((p) => p === "node_modules" || p === "target" || p === ".git" || p === "dist"))
@@ -54,19 +44,46 @@ export function scopeIdMismatch(root, vue) {
54
44
  files = readdirSync(join(root, config.components)).filter((f) => f.endsWith(".vue"));
55
45
  }
56
46
  catch {
57
- // No components: generating reports it.
58
47
  }
59
48
  if (!files.some((f) => /<style\b[^>]*\bscoped\b/.test(readFileSync(join(root, config.components, f), "utf8"))))
60
49
  return null;
61
50
  return `\`<style scoped>\` ids: @vitejs/plugin-vue hashes "${vue.mode}" from ${vue.root}, ${CONFIG_FILE} "${mode}" from ${viteRoot}; set \`scopeId\` and \`viteRoot\` to match, or plugin-vue's \`features.componentIdGenerator\``;
62
51
  }
52
+ const ISLANDS = "ferrovue/islands";
53
+ const ISLANDS_ID = `\0${ISLANDS}`;
54
+ function islandsModule(root, islands) {
55
+ const entries = Object.entries(islands).map(([name, file]) => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(resolve(root, file).split(sep).join("/"))}),`);
56
+ return `export default {\n${entries.join("\n")}\n};\n`;
57
+ }
58
+ /** The Rust renderers regenerated by a Vite build and its dev server, and the `ferrovue/islands` module. */
63
59
  export default function ferrovue(options = {}) {
64
60
  const root = resolve(options.root ?? process.cwd());
65
- const regenerate = () => write(root, loadConfig(root));
61
+ let islands = null;
62
+ const regenerate = () => {
63
+ const written = write(root, loadConfig(root));
64
+ islands = written.islands;
65
+ return written;
66
+ };
66
67
  let vue = null;
67
68
  let building = true;
68
69
  return {
69
70
  name: "ferrovue",
71
+ enforce: "pre",
72
+ resolveId(id) {
73
+ return id === ISLANDS ? ISLANDS_ID : null;
74
+ },
75
+ load(id) {
76
+ if (id !== ISLANDS_ID)
77
+ return null;
78
+ try {
79
+ return islandsModule(root, islands ?? regenerate().islands);
80
+ }
81
+ catch (e) {
82
+ if (e instanceof GenError || e instanceof SyntaxError)
83
+ this.error(e.message);
84
+ throw e;
85
+ }
86
+ },
70
87
  configResolved(config) {
71
88
  vue = vueScopeIds(config);
72
89
  building = config.command === "build";
@@ -74,8 +91,6 @@ export default function ferrovue(options = {}) {
74
91
  buildStart() {
75
92
  try {
76
93
  regenerate();
77
- // A build whose styles would not apply to the server's pages fails, as a refusal does; the
78
- // dev server warns.
79
94
  const mismatch = scopeIdMismatch(root, vue);
80
95
  if (mismatch && building)
81
96
  this.error(`ferrovue: ${mismatch}`);
@@ -94,10 +109,16 @@ export default function ferrovue(options = {}) {
94
109
  if (!affects(root, file))
95
110
  return;
96
111
  try {
112
+ const before = JSON.stringify(islands);
97
113
  const { changed, removed } = regenerate();
98
114
  if (changed.length || removed.length) {
99
115
  server.config.logger.info(`ferrovue: ${changed.length} changed, ${removed.length} removed`, { timestamp: true });
100
116
  }
117
+ const loaded = server.moduleGraph.getModuleById(ISLANDS_ID);
118
+ if (loaded && JSON.stringify(islands) !== before) {
119
+ server.moduleGraph.invalidateModule(loaded);
120
+ server.ws.send({ type: "full-reload" });
121
+ }
101
122
  }
102
123
  catch (e) {
103
124
  if (!(e instanceof GenError || e instanceof SyntaxError))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ferrovue",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
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": {
@@ -29,6 +29,10 @@
29
29
  ],
30
30
  "exports": {
31
31
  ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./compiler": {
32
36
  "types": "./dist/compiler.d.ts",
33
37
  "default": "./dist/compiler.js"
34
38
  },
@@ -44,16 +48,21 @@
44
48
  "types": "./dist/client.d.ts",
45
49
  "default": "./dist/client.js"
46
50
  },
51
+ "./islands": {
52
+ "types": "./dist/islands.d.ts",
53
+ "default": "./dist/islands.js"
54
+ },
47
55
  "./vite": {
48
56
  "types": "./dist/vite.d.ts",
49
57
  "default": "./dist/vite.js"
50
58
  }
51
59
  },
60
+ "sideEffects": false,
52
61
  "bin": {
53
62
  "ferrovue": "./dist/cli.js"
54
63
  },
55
64
  "scripts": {
56
- "build": "tsc -p tsconfig.build.json",
65
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
57
66
  "prepack": "pnpm run build && cp ../../README.md ../../LICENSE-MIT ../../LICENSE-APACHE ."
58
67
  },
59
68
  "dependencies": {
package/dist/i18n.d.ts DELETED
@@ -1,27 +0,0 @@
1
- import { type N, type Scope, type Val } from "./model.ts";
2
- /** A locale's messages, each parsed by vue-i18n's own message compiler, by dotted key. */
3
- export interface LocaleMessages {
4
- name: string;
5
- file: string;
6
- messages: Map<string, N>;
7
- }
8
- /** The configured vue-i18n setup, read once per run. */
9
- export interface I18nSetup {
10
- /** The locale files' directory, as the configuration names it. */
11
- dir: string;
12
- locales: LocaleMessages[];
13
- /** The locale a fixture renders in when it names none, and the fallbacks in order. */
14
- locale: string;
15
- fallback: string[];
16
- }
17
- /** Every message of every `*.json` locale file in the directory: nested objects joined with dots,
18
- * as vue-i18n resolves a path — a nested key first, then a flat key of the same spelling. */
19
- export declare function readLocales(root: string, config: {
20
- messages: string;
21
- locale?: string;
22
- fallbackLocale?: string | string[];
23
- }): I18nSetup;
24
- export declare function i18nSource(setup: I18nSetup): string;
25
- /** `t(key)`, `t(key, plural)`, `t(key, { named })`, `t(key, [list])`, `t(key, { named }, plural)`:
26
- * the message translated in the request's locale. */
27
- export declare function translate(s: Scope, args: N[], n: N): Val;
package/dist/router.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { type N, type Scope, type Val } from "./model.ts";
2
- import { type RouteDef } from "./context.ts";
3
- import { Emitter } from "./emitter.ts";
4
- export declare const ROUTER_LINK_PROPS: Set<string>;
5
- /** `<RouterLink>` props that change only what a click does, which the server does not render. */
6
- export declare const ROUTER_LINK_INERT: Set<string>;
7
- /** The parameter names of a route path, in order. */
8
- export declare function routeParams(path: string): string[];
9
- /** A value written into a URL — a parameter or a query value — as the `&str` vue-router stringifies. */
10
- export declare function urlText(s: Scope, v: Val, n: N): string;
11
- /** \`let fv_link = …;\`: a \`<RouterLink>\`'s \`to\` resolved from the reader's route. */
12
- export declare function resolveLink(s: Scope, e: Emitter, to: N): void;
13
- /** \`<RouterLink to="...">\` as vue-router renders it: \`aria-current\` and the active classes when it
14
- * points where the reader is, then \`href\`, then the link's own class and attributes. */
15
- export declare function routerLink(s: Scope, e: Emitter, n: N): void;
16
- /** The routes file: vue-router paths, each a string or `{ "path", "name" }`. */
17
- export declare function readRoutes(root: string, file: string): RouteDef[];
package/dist/scoped.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import type { Component } from "./model.ts";
2
- /** Set every component's `inherits` and `passesSlotIds`, each a least fixed point: a component's
3
- * slot content may be given an id when one of its outlets passes one, or forwards what the slot
4
- * content it sits in was given; a root may be handed ids by a scoped parent, by a parent that
5
- * passes on what it inherits, or by slot content that may be given an id. */
6
- export declare function scopeFlow(read: {
7
- comp: Component;
8
- ssr: string;
9
- children: Map<string, string>;
10
- }[]): void;
package/dist/stores.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import { type Component } from "./model.ts";
2
- /** The store module an import names, resolved and without its extension, when it is one. */
3
- export declare function storeImport(comp: Component, from: string): string | null;
4
- /** Every store in the configured directory, and the interfaces their files declare. */
5
- export declare function readStores(root: string, dir: string): void;
6
- export declare function storeHome(file: string): Component;