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.
- package/README.md +103 -15
- package/dist/attrs.d.ts +8 -42
- package/dist/attrs.js +148 -297
- package/dist/calls.d.ts +4 -0
- package/dist/calls.js +219 -0
- package/dist/children.d.ts +9 -0
- package/dist/children.js +273 -0
- package/dist/classes.d.ts +13 -0
- package/dist/classes.js +176 -0
- package/dist/cli.js +121 -27
- package/dist/client.d.ts +11 -2
- package/dist/client.js +45 -9
- package/dist/compiler.d.ts +4 -0
- package/dist/compiler.js +37 -90
- package/dist/component.js +19 -82
- package/dist/context.d.ts +6 -68
- package/dist/context.js +25 -38
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +68 -0
- package/dist/emitter.d.ts +0 -8
- package/dist/emitter.js +0 -12
- package/dist/expr.d.ts +0 -70
- package/dist/expr.js +30 -855
- package/dist/fallthrough.d.ts +9 -0
- package/dist/fallthrough.js +78 -0
- package/dist/fixture.d.ts +2 -1
- package/dist/fixture.js +37 -22
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/islands.d.ts +6 -0
- package/dist/islands.js +5 -0
- package/dist/lists.d.ts +0 -11
- package/dist/lists.js +4 -39
- package/dist/loops.d.ts +3 -0
- package/dist/loops.js +164 -0
- package/dist/model.d.ts +22 -130
- package/dist/model.js +37 -9
- package/dist/narrowing.d.ts +19 -0
- package/dist/narrowing.js +126 -0
- package/dist/numbers.d.ts +8 -0
- package/dist/numbers.js +73 -0
- package/dist/parens.d.ts +0 -27
- package/dist/parens.js +0 -44
- package/dist/plugin.d.ts +84 -0
- package/dist/plugin.js +26 -0
- package/dist/plugins/i18n.d.ts +23 -0
- package/dist/{i18n.js → plugins/i18n.js} +76 -19
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/router-link.d.ts +8 -0
- package/dist/{router.js → plugins/router-link.js} +65 -57
- package/dist/plugins/router.d.ts +42 -0
- package/dist/plugins/router.js +256 -0
- package/dist/plugins/scoped.d.ts +12 -0
- package/dist/{scoped.js → plugins/scoped.js} +55 -20
- package/dist/plugins/shared-types.d.ts +2 -0
- package/dist/plugins/shared-types.js +7 -0
- package/dist/plugins/stores.d.ts +37 -0
- package/dist/plugins/stores.js +377 -0
- package/dist/plugins/teleport.d.ts +5 -0
- package/dist/plugins/teleport.js +55 -0
- package/dist/rust.d.ts +2 -5
- package/dist/rust.js +66 -161
- package/dist/script.d.ts +0 -6
- package/dist/script.js +22 -121
- package/dist/slots.d.ts +13 -0
- package/dist/slots.js +160 -0
- package/dist/strings.d.ts +13 -0
- package/dist/strings.js +208 -0
- package/dist/styles.d.ts +14 -0
- package/dist/styles.js +181 -0
- package/dist/template.d.ts +2 -40
- package/dist/template.js +40 -691
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -8
- package/dist/types.d.ts +2 -0
- package/dist/typescript.d.ts +0 -13
- package/dist/typescript.js +8 -41
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +35 -14
- package/package.json +11 -2
- package/dist/i18n.d.ts +0 -27
- package/dist/router.d.ts +0 -17
- package/dist/scoped.d.ts +0 -10
- package/dist/stores.d.ts +0 -6
- 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
|
-
|
|
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 {};
|
package/dist/typescript.d.ts
CHANGED
|
@@ -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;
|
package/dist/typescript.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
|
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
|
-
|
|
205
|
-
if (
|
|
206
|
-
return { st:
|
|
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
|
-
|
|
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.
|
|
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;
|