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.
- package/README.md +32 -13
- package/dist/attrs.d.ts +2 -52
- package/dist/attrs.js +18 -448
- 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 +0 -17
- package/dist/client.js +0 -12
- package/dist/compiler.js +25 -90
- package/dist/component.js +10 -86
- package/dist/context.d.ts +4 -66
- package/dist/context.js +2 -33
- package/dist/diff.d.ts +0 -3
- package/dist/diff.js +0 -9
- package/dist/emitter.d.ts +0 -8
- package/dist/emitter.js +0 -12
- package/dist/expr.d.ts +0 -70
- package/dist/expr.js +27 -890
- 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 +1 -0
- package/dist/islands.js +1 -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 +14 -143
- package/dist/model.js +27 -12
- 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} +19 -61
- 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/plugins/scoped.js +125 -0
- 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 +1 -7
- package/dist/rust.js +38 -160
- package/dist/script.d.ts +0 -6
- package/dist/script.js +13 -128
- 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 +1 -42
- package/dist/template.js +14 -738
- 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 +1 -23
- package/package.json +7 -2
- package/dist/i18n.d.ts +0 -27
- package/dist/router.d.ts +0 -17
- package/dist/scoped.d.ts +0 -24
- package/dist/scoped.js +0 -177
- package/dist/stores.d.ts +0 -6
- package/dist/stores.js +0 -229
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,15 +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
|
-
*
|
|
11
|
-
* It also writes `ferrovue/islands`: every component that has an `island()`, by name, each a lazy
|
|
12
|
-
* `import()` of its `.vue` file, for `mountIslands`. */
|
|
13
1
|
import { readdirSync, readFileSync } from "node:fs";
|
|
14
2
|
import { join, relative, resolve, sep } from "node:path";
|
|
15
3
|
import { CONFIG_FILE, GenError, loadConfig, write } from "./compiler.js";
|
|
@@ -24,7 +12,6 @@ export function affects(root, file) {
|
|
|
24
12
|
out = loadConfig(root).out;
|
|
25
13
|
}
|
|
26
14
|
catch {
|
|
27
|
-
// A broken configuration ignores nothing; regenerating reports it.
|
|
28
15
|
}
|
|
29
16
|
const parts = rel.split(sep);
|
|
30
17
|
if (parts.some((p) => p === "node_modules" || p === "target" || p === ".git" || p === "dist"))
|
|
@@ -57,24 +44,20 @@ export function scopeIdMismatch(root, vue) {
|
|
|
57
44
|
files = readdirSync(join(root, config.components)).filter((f) => f.endsWith(".vue"));
|
|
58
45
|
}
|
|
59
46
|
catch {
|
|
60
|
-
// No components: generating reports it.
|
|
61
47
|
}
|
|
62
48
|
if (!files.some((f) => /<style\b[^>]*\bscoped\b/.test(readFileSync(join(root, config.components, f), "utf8"))))
|
|
63
49
|
return null;
|
|
64
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\``;
|
|
65
51
|
}
|
|
66
|
-
/** The import that names the islands, and the id the plugin gives the module it writes for it. */
|
|
67
52
|
const ISLANDS = "ferrovue/islands";
|
|
68
53
|
const ISLANDS_ID = `\0${ISLANDS}`;
|
|
69
|
-
/** The text of `ferrovue/islands`: each island's name, mapped to an `import()` of its `.vue` file,
|
|
70
|
-
* which the bundler splits into a chunk of its own. */
|
|
71
54
|
function islandsModule(root, islands) {
|
|
72
55
|
const entries = Object.entries(islands).map(([name, file]) => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(resolve(root, file).split(sep).join("/"))}),`);
|
|
73
56
|
return `export default {\n${entries.join("\n")}\n};\n`;
|
|
74
57
|
}
|
|
58
|
+
/** The Rust renderers regenerated by a Vite build and its dev server, and the `ferrovue/islands` module. */
|
|
75
59
|
export default function ferrovue(options = {}) {
|
|
76
60
|
const root = resolve(options.root ?? process.cwd());
|
|
77
|
-
// The islands as the last generation found them, for `ferrovue/islands`.
|
|
78
61
|
let islands = null;
|
|
79
62
|
const regenerate = () => {
|
|
80
63
|
const written = write(root, loadConfig(root));
|
|
@@ -85,7 +68,6 @@ export default function ferrovue(options = {}) {
|
|
|
85
68
|
let building = true;
|
|
86
69
|
return {
|
|
87
70
|
name: "ferrovue",
|
|
88
|
-
// Before Vite's own resolver, which would find `ferrovue/islands` in the package instead.
|
|
89
71
|
enforce: "pre",
|
|
90
72
|
resolveId(id) {
|
|
91
73
|
return id === ISLANDS ? ISLANDS_ID : null;
|
|
@@ -109,8 +91,6 @@ export default function ferrovue(options = {}) {
|
|
|
109
91
|
buildStart() {
|
|
110
92
|
try {
|
|
111
93
|
regenerate();
|
|
112
|
-
// A build whose styles would not apply to the server's pages fails, as a refusal does; the
|
|
113
|
-
// dev server warns.
|
|
114
94
|
const mismatch = scopeIdMismatch(root, vue);
|
|
115
95
|
if (mismatch && building)
|
|
116
96
|
this.error(`ferrovue: ${mismatch}`);
|
|
@@ -134,8 +114,6 @@ export default function ferrovue(options = {}) {
|
|
|
134
114
|
if (changed.length || removed.length) {
|
|
135
115
|
server.config.logger.info(`ferrovue: ${changed.length} changed, ${removed.length} removed`, { timestamp: true });
|
|
136
116
|
}
|
|
137
|
-
// A component that became an island, or stopped being one: the page loads the islands
|
|
138
|
-
// afresh, as the server now writes them.
|
|
139
117
|
const loaded = server.moduleGraph.getModuleById(ISLANDS_ID);
|
|
140
118
|
if (loaded && JSON.stringify(islands) !== before) {
|
|
141
119
|
server.moduleGraph.invalidateModule(loaded);
|
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
|
},
|
|
@@ -53,11 +57,12 @@
|
|
|
53
57
|
"default": "./dist/vite.js"
|
|
54
58
|
}
|
|
55
59
|
},
|
|
60
|
+
"sideEffects": false,
|
|
56
61
|
"bin": {
|
|
57
62
|
"ferrovue": "./dist/cli.js"
|
|
58
63
|
},
|
|
59
64
|
"scripts": {
|
|
60
|
-
"build": "tsc -p tsconfig.build.json",
|
|
65
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
|
|
61
66
|
"prepack": "pnpm run build && cp ../../README.md ../../LICENSE-MIT ../../LICENSE-APACHE ."
|
|
62
67
|
},
|
|
63
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,24 +0,0 @@
|
|
|
1
|
-
import { type Component, type N } 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;
|
|
11
|
-
/** Whether `n` is the template's `$attrs`: `_ctx.$attrs`, or a setup binding of `useAttrs()`. */
|
|
12
|
-
export declare function isDollarAttrs(n: N, bindings: Set<string>): boolean;
|
|
13
|
-
/** Whether a key a parent passes a child reaches it as an attribute, unless it is a prop: not one
|
|
14
|
-
* Vue reserves, not a listener (which the server drops), not a model's modifiers. */
|
|
15
|
-
export declare function passedKey(child: Component, key: string): boolean;
|
|
16
|
-
/** Set every component's `attrNames` and `idsInAttrs`: the attributes its parents' calls pass it
|
|
17
|
-
* beyond its props, and those passed on to it — by a parent whose root it is, or that binds
|
|
18
|
-
* `$attrs` to it — from that parent's own, a least fixed point. Runs after `scopeFlow`. */
|
|
19
|
-
export declare function attrsFlow(read: {
|
|
20
|
-
comp: Component;
|
|
21
|
-
ssr: string;
|
|
22
|
-
children: Map<string, string>;
|
|
23
|
-
attrsBindings: Set<string>;
|
|
24
|
-
}[]): void;
|
package/dist/scoped.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/* Where scope ids reach: which components' roots a parent may hand ids to, and which components'
|
|
2
|
-
* slot content may be given a slot scope id. Both decide a generated signature — `render` taking
|
|
3
|
-
* `fv_attrs`, slot content taking `fv_sid` — so they are worked out for every component before any
|
|
4
|
-
* is generated.
|
|
5
|
-
*
|
|
6
|
-
* With `<style scoped>`, Vue's server renderer writes the component's id onto each of its elements
|
|
7
|
-
* (the compiled template spells it out) and hands ids to a child component's root through the
|
|
8
|
-
* child's `_attrs`: the id of the component that created it, what its parent passes on when the
|
|
9
|
-
* child is that parent's root, and the slot scope ids of the slot content it is rendered in
|
|
10
|
-
* (`renderComponentSubTree`). A component with `:slotted()` styles gives its slot content
|
|
11
|
-
* `data-v-…-s` as well, which the content writes onto its elements as `_scopeId`.
|
|
12
|
-
*
|
|
13
|
-
* The same `_attrs` carries the attributes a parent passes a child beyond its props, ahead of the
|
|
14
|
-
* ids: which components may be passed some (`attrsFlow`) decides whether `fv_attrs` is an
|
|
15
|
-
* `fv::Attrs` holding both, or the ids alone. */
|
|
16
|
-
import { parse as parseJs } from "@babel/parser";
|
|
17
|
-
import { declares } from "./model.js";
|
|
18
|
-
/** The child components and outlets in one component's compiled template. */
|
|
19
|
-
function flowOf(comp, ssr, children) {
|
|
20
|
-
const calls = [];
|
|
21
|
-
const outlets = [];
|
|
22
|
-
// `const _component_Tree = _resolveComponent("Tree", true)`: the component itself, by name.
|
|
23
|
-
const self = new Set();
|
|
24
|
-
const resolve = (target) => {
|
|
25
|
-
if (target?.type === "MemberExpression" && target.object.name === "$setup") {
|
|
26
|
-
return children.get(target.computed ? target.property.value : target.property.name) ?? null;
|
|
27
|
-
}
|
|
28
|
-
return target?.type === "Identifier" && self.has(target.name) ? comp.name : null;
|
|
29
|
-
};
|
|
30
|
-
const mentions = (n, name) => !!n && typeof n === "object" && (n.type === "Identifier" ? n.name === name : Object.values(n).some((v) => mentions(v, name)));
|
|
31
|
-
const walk = (n, within) => {
|
|
32
|
-
if (!n || typeof n !== "object")
|
|
33
|
-
return;
|
|
34
|
-
if (Array.isArray(n)) {
|
|
35
|
-
for (const x of n)
|
|
36
|
-
walk(x, within);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (n.type === "VariableDeclarator" && n.init?.type === "CallExpression" && n.init.callee.name === "_resolveComponent") {
|
|
40
|
-
if (n.init.arguments[0]?.value === comp.name && n.init.arguments[1])
|
|
41
|
-
self.add(n.id.name);
|
|
42
|
-
}
|
|
43
|
-
if (n.type === "CallExpression" && n.callee.type === "Identifier") {
|
|
44
|
-
const a = n.arguments;
|
|
45
|
-
if (n.callee.name === "_ssrRenderComponent") {
|
|
46
|
-
const child = resolve(a[0]);
|
|
47
|
-
if (child)
|
|
48
|
-
calls.push({ child, passesAttrs: mentions(a[1], "_attrs"), within: a[4] ? within : null });
|
|
49
|
-
// Slot content given to the child: its `_scopeId` is what the child's outlets pass.
|
|
50
|
-
walk(a[2], child);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (n.callee.name === "_ssrRenderSlot" || n.callee.name === "_ssrRenderSlotInner") {
|
|
54
|
-
const id = a[6];
|
|
55
|
-
const passes = !id || id.type === "NullLiteral" ? "none" : id.type === "Identifier" && id.name === "_scopeId" ? "forward" : "id";
|
|
56
|
-
outlets.push({ passes, within });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
for (const [key, v] of Object.entries(n))
|
|
60
|
-
if (key !== "loc" && key !== "start" && key !== "end")
|
|
61
|
-
walk(v, within);
|
|
62
|
-
};
|
|
63
|
-
walk(parseJs(ssr, { sourceType: "module" }).program, null);
|
|
64
|
-
return { calls, outlets };
|
|
65
|
-
}
|
|
66
|
-
/** Set every component's `inherits` and `passesSlotIds`, each a least fixed point: a component's
|
|
67
|
-
* slot content may be given an id when one of its outlets passes one, or forwards what the slot
|
|
68
|
-
* content it sits in was given; a root may be handed ids by a scoped parent, by a parent that
|
|
69
|
-
* passes on what it inherits, or by slot content that may be given an id. */
|
|
70
|
-
export function scopeFlow(read) {
|
|
71
|
-
const flows = read.map((r) => ({ comp: r.comp, ...flowOf(r.comp, r.ssr, r.children) }));
|
|
72
|
-
const byName = new Map(read.map((r) => [r.comp.name, r.comp]));
|
|
73
|
-
const passes = (name) => (name !== null && byName.get(name)?.passesSlotIds) ?? false;
|
|
74
|
-
for (let changed = true; changed;) {
|
|
75
|
-
changed = false;
|
|
76
|
-
for (const f of flows) {
|
|
77
|
-
if (!f.comp.passesSlotIds && f.outlets.some((o) => o.passes === "id" || (o.passes === "forward" && passes(o.within)))) {
|
|
78
|
-
f.comp.passesSlotIds = changed = true;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
for (let changed = true; changed;) {
|
|
83
|
-
changed = false;
|
|
84
|
-
for (const f of flows) {
|
|
85
|
-
for (const c of f.calls) {
|
|
86
|
-
const child = byName.get(c.child);
|
|
87
|
-
if (!child || child.inherits)
|
|
88
|
-
continue;
|
|
89
|
-
if (f.comp.scopeId !== null || (c.passesAttrs && f.comp.inherits && child.inheritAttrs) || passes(c.within)) {
|
|
90
|
-
child.inherits = changed = true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/** Whether `n` is the template's `$attrs`: `_ctx.$attrs`, or a setup binding of `useAttrs()`. */
|
|
97
|
-
export function isDollarAttrs(n, bindings) {
|
|
98
|
-
if (n?.type !== "MemberExpression" || n.object.type !== "Identifier")
|
|
99
|
-
return false;
|
|
100
|
-
const prop = n.computed ? n.property.value : n.property.name;
|
|
101
|
-
if (n.object.name === "_ctx")
|
|
102
|
-
return prop === "$attrs";
|
|
103
|
-
return n.object.name === "$setup" && prop !== undefined && bindings.has(prop);
|
|
104
|
-
}
|
|
105
|
-
/** Whether a key a parent passes a child reaches it as an attribute, unless it is a prop: not one
|
|
106
|
-
* Vue reserves, not a listener (which the server drops), not a model's modifiers. */
|
|
107
|
-
export function passedKey(child, key) {
|
|
108
|
-
if (["", "key", "ref", "ref_for", "ref_key", "innerHTML", "textContent"].includes(key) || /^on[^a-z]/.test(key))
|
|
109
|
-
return false;
|
|
110
|
-
return !(key.endsWith("Modifiers") && declares(child, key === "modelModifiers" ? "modelValue" : key.slice(0, -"Modifiers".length)));
|
|
111
|
-
}
|
|
112
|
-
/** Set every component's `attrNames` and `idsInAttrs`: the attributes its parents' calls pass it
|
|
113
|
-
* beyond its props, and those passed on to it — by a parent whose root it is, or that binds
|
|
114
|
-
* `$attrs` to it — from that parent's own, a least fixed point. Runs after `scopeFlow`. */
|
|
115
|
-
export function attrsFlow(read) {
|
|
116
|
-
const byName = new Map(read.map((r) => [r.comp.name, r.comp]));
|
|
117
|
-
const flows = read.map((r) => {
|
|
118
|
-
const calls = [];
|
|
119
|
-
const self = new Set();
|
|
120
|
-
const walk = (n) => {
|
|
121
|
-
if (!n || typeof n !== "object")
|
|
122
|
-
return;
|
|
123
|
-
if (Array.isArray(n)) {
|
|
124
|
-
n.forEach(walk);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (n.type === "VariableDeclarator" && n.init?.type === "CallExpression" && n.init.callee.name === "_resolveComponent") {
|
|
128
|
-
if (n.init.arguments[0]?.value === r.comp.name && n.init.arguments[1])
|
|
129
|
-
self.add(n.id.name);
|
|
130
|
-
}
|
|
131
|
-
if (n.type === "CallExpression" && n.callee.type === "Identifier" && n.callee.name === "_ssrRenderComponent") {
|
|
132
|
-
const target = n.arguments[0];
|
|
133
|
-
const child = target?.type === "MemberExpression" && target.object.name === "$setup"
|
|
134
|
-
? r.children.get(target.computed ? target.property.value : target.property.name)
|
|
135
|
-
: target?.type === "Identifier" && self.has(target.name)
|
|
136
|
-
? r.comp.name
|
|
137
|
-
: undefined;
|
|
138
|
-
const props = n.arguments[1];
|
|
139
|
-
const parts = props?.type === "CallExpression" && props.callee.name === "_mergeProps" ? props.arguments : props ? [props] : [];
|
|
140
|
-
if (child) {
|
|
141
|
-
calls.push({
|
|
142
|
-
child,
|
|
143
|
-
keys: parts.filter((p) => p.type === "ObjectExpression").flatMap((p) => p.properties.filter((q) => q.type === "ObjectProperty" && !q.computed).map((q) => q.key.name ?? String(q.key.value))),
|
|
144
|
-
passesAttrs: parts.some((p) => p.type === "Identifier" && p.name === "_attrs"),
|
|
145
|
-
passesDollar: parts.some((p) => isDollarAttrs(p, r.attrsBindings)),
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
for (const [key, v] of Object.entries(n))
|
|
150
|
-
if (key !== "loc" && key !== "start" && key !== "end")
|
|
151
|
-
walk(v);
|
|
152
|
-
};
|
|
153
|
-
walk(parseJs(r.ssr, { sourceType: "module" }).program);
|
|
154
|
-
return { comp: r.comp, calls };
|
|
155
|
-
});
|
|
156
|
-
for (let changed = true; changed;) {
|
|
157
|
-
changed = false;
|
|
158
|
-
for (const f of flows) {
|
|
159
|
-
for (const c of f.calls) {
|
|
160
|
-
const child = byName.get(c.child);
|
|
161
|
-
if (!child)
|
|
162
|
-
continue;
|
|
163
|
-
const names = c.keys.filter((k) => passedKey(child, k));
|
|
164
|
-
if ((c.passesAttrs && f.comp.inheritAttrs) || c.passesDollar)
|
|
165
|
-
names.push(...f.comp.attrNames);
|
|
166
|
-
for (const name of names) {
|
|
167
|
-
if (declares(child, name) || child.attrNames.has(name))
|
|
168
|
-
continue;
|
|
169
|
-
child.attrNames.add(name);
|
|
170
|
-
changed = true;
|
|
171
|
-
}
|
|
172
|
-
if (c.passesAttrs && f.comp.inherits && !child.idsInAttrs)
|
|
173
|
-
child.idsInAttrs = changed = true;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
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;
|