ferrovue 0.5.0 → 0.7.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 +306 -62
- package/dist/args.d.ts +28 -0
- package/dist/args.js +69 -0
- package/dist/attrs.d.ts +2 -1
- package/dist/attrs.js +10 -4
- package/dist/calls.js +5 -2
- package/dist/children.d.ts +2 -2
- package/dist/children.js +84 -16
- package/dist/classes.d.ts +4 -0
- package/dist/classes.js +23 -1
- package/dist/cli.js +80 -81
- package/dist/client-render.d.ts +23 -0
- package/dist/client-render.js +143 -0
- package/dist/client.d.ts +74 -10
- package/dist/client.js +373 -42
- package/dist/compiler.d.ts +9 -5
- package/dist/compiler.js +80 -18
- package/dist/component.d.ts +7 -3
- package/dist/component.js +38 -27
- package/dist/conformance.d.ts +85 -0
- package/dist/conformance.js +327 -0
- package/dist/constants.js +4 -2
- package/dist/context.d.ts +66 -10
- package/dist/context.js +176 -5
- package/dist/diagnostics.d.ts +6 -4
- package/dist/diagnostics.js +7 -3
- package/dist/dynamic.d.ts +18 -0
- package/dist/dynamic.js +576 -0
- package/dist/editor.d.ts +18 -0
- package/dist/editor.js +59 -0
- package/dist/errors.d.ts +213 -4
- package/dist/errors.js +129 -4
- package/dist/expr.d.ts +2 -0
- package/dist/expr.js +91 -12
- package/dist/fallthrough.d.ts +1 -0
- package/dist/fallthrough.js +10 -2
- package/dist/file-routes.d.ts +33 -0
- package/dist/file-routes.js +224 -0
- package/dist/files.d.ts +13 -0
- package/dist/files.js +49 -0
- package/dist/fixture.d.ts +16 -0
- package/dist/fixture.js +22 -7
- package/dist/hydration.d.ts +12 -1
- package/dist/hydration.js +47 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/inputs.d.ts +24 -0
- package/dist/inputs.js +40 -0
- package/dist/link-router.d.ts +7 -3
- package/dist/link-router.js +8 -4
- package/dist/lists.d.ts +16 -1
- package/dist/lists.js +78 -4
- package/dist/loops.js +4 -4
- package/dist/model.d.ts +20 -1
- package/dist/model.js +46 -1
- package/dist/numbers.js +3 -2
- package/dist/page-routes.d.ts +10 -0
- package/dist/page-routes.js +10 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +7 -0
- package/dist/plugin.d.ts +15 -1
- package/dist/plugin.js +3 -0
- package/dist/plugins/client-only.js +3 -0
- package/dist/plugins/css-modules.d.ts +39 -0
- package/dist/plugins/css-modules.js +135 -0
- package/dist/plugins/head.d.ts +15 -0
- package/dist/plugins/head.js +385 -0
- package/dist/plugins/i18n.js +5 -12
- package/dist/plugins/index.js +4 -1
- package/dist/plugins/provide.d.ts +44 -0
- package/dist/plugins/provide.js +530 -0
- package/dist/plugins/router-link.js +7 -3
- package/dist/plugins/router.d.ts +4 -1
- package/dist/plugins/router.js +58 -11
- package/dist/plugins/scoped.d.ts +10 -0
- package/dist/plugins/scoped.js +17 -4
- package/dist/plugins/shared-types.js +2 -1
- package/dist/plugins/stores.js +26 -15
- package/dist/routes.d.ts +5 -2
- package/dist/routes.js +8 -2
- package/dist/rust.d.ts +9 -1
- package/dist/rust.js +83 -32
- package/dist/schema.d.ts +25 -0
- package/dist/schema.js +73 -0
- package/dist/script.js +31 -8
- package/dist/settle.d.ts +2 -0
- package/dist/settle.js +5 -0
- package/dist/slots.js +20 -4
- package/dist/ssr.d.ts +3 -0
- package/dist/ssr.js +67 -0
- package/dist/strings.d.ts +3 -0
- package/dist/strings.js +16 -2
- package/dist/styles.d.ts +5 -1
- package/dist/styles.js +131 -4
- package/dist/template.js +92 -11
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +3 -30
- package/dist/types.d.ts +5 -0
- package/dist/typescript.d.ts +11 -1
- package/dist/typescript.js +131 -36
- package/dist/vhtml.d.ts +4 -0
- package/dist/vhtml.js +63 -0
- package/dist/vite.d.ts +25 -11
- package/dist/vite.js +166 -51
- package/dist/volar.d.ts +15 -0
- package/dist/volar.js +87 -0
- package/package.json +41 -7
- package/schema.json +333 -0
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { GenError } from "./model.ts";
|
|
2
|
+
import type { Deprecation } from "./schema.ts";
|
|
3
|
+
/** A command-line option: its names, short one first, the value it takes, and its line in `--help`. */
|
|
4
|
+
export interface Flag {
|
|
5
|
+
names: readonly string[];
|
|
6
|
+
value?: string;
|
|
7
|
+
help: string;
|
|
8
|
+
deprecated?: Deprecation;
|
|
9
|
+
}
|
|
10
|
+
/** The commands `ferrovue` takes, with their lines in `--help`. */
|
|
11
|
+
export declare const COMMANDS: Readonly<Record<string, string>>;
|
|
12
|
+
/** The options `ferrovue` takes. */
|
|
13
|
+
export declare const FLAGS: readonly Flag[];
|
|
14
|
+
/** What the command line asks for: the command, and each option given by its long name, with its
|
|
15
|
+
* value or `true`. */
|
|
16
|
+
export interface Args {
|
|
17
|
+
command: string | null;
|
|
18
|
+
options: Map<string, string | true>;
|
|
19
|
+
/** The name each option was given by, as an error about it quotes it. */
|
|
20
|
+
spelt: Map<string, string>;
|
|
21
|
+
warnings: GenError[];
|
|
22
|
+
}
|
|
23
|
+
/** `ferrovue --help`. */
|
|
24
|
+
export declare function helpText(flags?: readonly Flag[], commands?: Readonly<Record<string, string>>): string;
|
|
25
|
+
/** Read the command line, or say what is wrong with it. A deprecated option gives a warning. */
|
|
26
|
+
export declare function parseArgs(argv: readonly string[], flags?: readonly Flag[], commands?: Readonly<Record<string, string>>): Args | {
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { GenError } from "./model.js";
|
|
2
|
+
/** The commands `ferrovue` takes, with their lines in `--help`. */
|
|
3
|
+
export const COMMANDS = {
|
|
4
|
+
init: "scaffold starter ferrovue.config.json and components",
|
|
5
|
+
};
|
|
6
|
+
/** The options `ferrovue` takes. */
|
|
7
|
+
export const FLAGS = [
|
|
8
|
+
{ names: ["--check"], help: "check that generated files match committed files without writing" },
|
|
9
|
+
{ names: ["-d", "--diff"], help: "show unified diff of changes when checking (use with --check)" },
|
|
10
|
+
{ names: ["--format"], value: "<format>", help: "how to write errors and results: human (default) or json" },
|
|
11
|
+
{ names: ["--watch"], help: "watch components, stores, routes and config for changes and regenerate" },
|
|
12
|
+
{ names: ["-c", "--config"], value: "<path>", help: "path to configuration file (default: ferrovue.config.json)" },
|
|
13
|
+
{ names: ["-v", "--version"], help: "print the version and exit" },
|
|
14
|
+
{ names: ["-h", "--help"], help: "print this help and exit" },
|
|
15
|
+
];
|
|
16
|
+
const longName = (flag) => flag.names.at(-1);
|
|
17
|
+
/** `ferrovue --help`. */
|
|
18
|
+
export function helpText(flags = FLAGS, commands = COMMANDS) {
|
|
19
|
+
const line = (left, help) => ` ${left.padEnd(23)} ${help}`;
|
|
20
|
+
const option = (flag) => {
|
|
21
|
+
const [short, long] = flag.names.length > 1 ? [`${flag.names[0]}, `, longName(flag)] : [" ", longName(flag)];
|
|
22
|
+
const help = flag.deprecated ? `${flag.help} (deprecated: use ${flag.deprecated.use})` : flag.help;
|
|
23
|
+
return line(`${short}${long}${flag.value ? ` ${flag.value}` : ""}`, help);
|
|
24
|
+
};
|
|
25
|
+
return `Usage: ferrovue [command] [options]
|
|
26
|
+
|
|
27
|
+
Compile Vue components to Rust render functions, run from the project root.
|
|
28
|
+
|
|
29
|
+
Commands:
|
|
30
|
+
${Object.entries(commands).map(([name, help]) => line(name, help)).join("\n")}
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
${flags.map(option).join("\n")}`;
|
|
34
|
+
}
|
|
35
|
+
/** Read the command line, or say what is wrong with it. A deprecated option gives a warning. */
|
|
36
|
+
export function parseArgs(argv, flags = FLAGS, commands = COMMANDS) {
|
|
37
|
+
const args = { command: null, options: new Map(), spelt: new Map(), warnings: [] };
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const arg = argv[i];
|
|
40
|
+
const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
41
|
+
const name = eq === -1 ? arg : arg.slice(0, eq);
|
|
42
|
+
const flag = flags.find((f) => f.names.includes(name));
|
|
43
|
+
if (!flag) {
|
|
44
|
+
if (arg in commands && args.command === null) {
|
|
45
|
+
args.command = arg;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
return { error: `unknown option or command '${arg}'\n\nRun \`ferrovue --help\` for usage.` };
|
|
49
|
+
}
|
|
50
|
+
let value = true;
|
|
51
|
+
if (flag.value) {
|
|
52
|
+
if (eq !== -1)
|
|
53
|
+
value = arg.slice(eq + 1);
|
|
54
|
+
else if (i + 1 < argv.length && !argv[i + 1].startsWith("-"))
|
|
55
|
+
value = argv[++i];
|
|
56
|
+
else
|
|
57
|
+
return { error: `option '${name}' requires an argument` };
|
|
58
|
+
}
|
|
59
|
+
else if (eq !== -1) {
|
|
60
|
+
return { error: `option '${name}' takes no value` };
|
|
61
|
+
}
|
|
62
|
+
if (flag.deprecated) {
|
|
63
|
+
args.warnings.push(new GenError("FV1118", `'${name}' is deprecated since ${flag.deprecated.since}, and goes in the next major release: use ${flag.deprecated.use}`));
|
|
64
|
+
}
|
|
65
|
+
args.options.set(longName(flag), value);
|
|
66
|
+
args.spelt.set(longName(flag), name);
|
|
67
|
+
}
|
|
68
|
+
return args;
|
|
69
|
+
}
|
package/dist/attrs.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export declare function display(e: Emitter, v: Val): boolean;
|
|
|
4
4
|
export declare function interpolate(s: Scope, e: Emitter, v: Val, n: N): void;
|
|
5
5
|
export declare function attrValue(e: Emitter, v: Val): void;
|
|
6
6
|
export declare function renderAttr(s: Scope, e: Emitter, key: string, v: Val, n: N): void;
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function keepsAttrCase(tag: string): boolean;
|
|
8
|
+
export declare function renderDynamicAttr(s: Scope, e: Emitter, key: string, v: Val, n: N, tag?: string | null): void;
|
|
8
9
|
export declare const IGNORED_PROPS: Set<string>;
|
|
9
10
|
export declare function directiveProps(s: Scope, n: N): boolean;
|
|
10
11
|
export declare function isAttrs(n: N): boolean;
|
package/dist/attrs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, propsToAttrMap } from "@vue/shared";
|
|
1
|
+
import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, isSVGTag, propsToAttrMap } from "@vue/shared";
|
|
2
2
|
import { fail, nothing, GenError, rustStr } from "./model.js";
|
|
3
3
|
import { CONFIG_FILE, ctx } from "./context.js";
|
|
4
4
|
import { describeTy, expr } from "./expr.js";
|
|
@@ -75,8 +75,11 @@ export function renderAttr(s, e, key, v, n) {
|
|
|
75
75
|
attrValue(e, v);
|
|
76
76
|
e.lit(`"`);
|
|
77
77
|
}
|
|
78
|
-
export function
|
|
79
|
-
|
|
78
|
+
export function keepsAttrCase(tag) {
|
|
79
|
+
return tag.indexOf("-") > 0 || isSVGTag(tag);
|
|
80
|
+
}
|
|
81
|
+
export function renderDynamicAttr(s, e, key, v, n, tag = null) {
|
|
82
|
+
const name = tag !== null && keepsAttrCase(tag) ? key : (propsToAttrMap[key] ?? key.toLowerCase());
|
|
80
83
|
if (!isSSRSafeAttrName(name))
|
|
81
84
|
fail(s.comp, "FV0404", `unsafe attribute name \`${name}\``, n);
|
|
82
85
|
renderable(s, key, v, n);
|
|
@@ -91,7 +94,7 @@ export function renderDynamicAttr(s, e, key, v, n) {
|
|
|
91
94
|
}
|
|
92
95
|
if (v.ty.k === "opt") {
|
|
93
96
|
e.open(`if let Some(v) = ${v.code}`);
|
|
94
|
-
renderDynamicAttr(s, e, key, { code: "v", ty: v.ty.of }, n);
|
|
97
|
+
renderDynamicAttr(s, e, key, { code: "v", ty: v.ty.of }, n, tag);
|
|
95
98
|
e.close();
|
|
96
99
|
return;
|
|
97
100
|
}
|
|
@@ -335,7 +338,10 @@ export function valueAttr(s, v, n) {
|
|
|
335
338
|
const make = of === "str" ? "fv::Attr::str" : of === "int" ? "fv::Attr::Int" : of === "float" ? "fv::Attr::Float" : of === "bool" ? "fv::Attr::Bool" : null;
|
|
336
339
|
if (make !== null)
|
|
337
340
|
return `${atom(v.code)}.map_or(fv::Attr::Undefined, ${make})`;
|
|
341
|
+
break;
|
|
338
342
|
}
|
|
343
|
+
default:
|
|
344
|
+
break;
|
|
339
345
|
}
|
|
340
346
|
return fail(s.comp, "FV0412", "an attribute that may fall through is a string, a number or a boolean", n);
|
|
341
347
|
}
|
package/dist/calls.js
CHANGED
|
@@ -2,7 +2,7 @@ import { BOOL, fail, FLOAT, INT, sameTy, STR } from "./model.js";
|
|
|
2
2
|
import { ctx } from "./context.js";
|
|
3
3
|
import { claim } from "./plugin.js";
|
|
4
4
|
import { atom, bare, binary, condition, operand, receiver, strArg, UNARY } from "./parens.js";
|
|
5
|
-
import { computedListMethod, items, listMethod, objectCall } from "./lists.js";
|
|
5
|
+
import { computedListMethod, isBoolean, items, listMethod, objectCall, presentLiteral } from "./lists.js";
|
|
6
6
|
import { lonely, loneOf, meet, stringMethod, stringsEqual } from "./strings.js";
|
|
7
7
|
import { asF64, intFromF64, isNumber } from "./numbers.js";
|
|
8
8
|
import { boolOf, truthy } from "./narrowing.js";
|
|
@@ -41,6 +41,8 @@ export function call(s, n) {
|
|
|
41
41
|
const helper = s.helpers.get(callee.name);
|
|
42
42
|
if (helper)
|
|
43
43
|
return helperCall(s, helper, args, n);
|
|
44
|
+
if (isBoolean(s, callee) && args.length === 1)
|
|
45
|
+
return boolOf(truthy(expr(s, args[0])));
|
|
44
46
|
if (callee.name === "String" && args.length === 1) {
|
|
45
47
|
const a = expr(s, args[0]);
|
|
46
48
|
if (a.ty.k === "str")
|
|
@@ -121,7 +123,8 @@ export function call(s, n) {
|
|
|
121
123
|
}
|
|
122
124
|
return fail(comp, "FV0704", `\`Math.${method}()\` is supported on numbers as \`max\`, \`min\`, \`abs\`, \`round\`, \`floor\`, \`ceil\` and \`trunc\``, n);
|
|
123
125
|
}
|
|
124
|
-
const
|
|
126
|
+
const literal = method === "filter" && args.length === 1 && isBoolean(s, args[0]) && callee.object.type === "ArrayExpression";
|
|
127
|
+
const target = (literal ? presentLiteral(s, callee.object) : null) ?? expr(s, callee.object);
|
|
125
128
|
if (target.ty.k === "str") {
|
|
126
129
|
const lone = loneOf(target);
|
|
127
130
|
switch (args.length === 0 ? method : "") {
|
package/dist/children.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare function renderChild(s: Scope, e: Emitter, n: N, twin?: TwinCall)
|
|
|
10
10
|
export declare function array(key: string): boolean;
|
|
11
11
|
export declare function fieldInit(name: string, value: string): string;
|
|
12
12
|
export declare function takesSlots(c: Component): boolean;
|
|
13
|
-
export declare function extraParams(c: Component): string;
|
|
14
|
-
export declare function callChild(s: Scope, e: Emitter, child: Component, propsCode: string, slots: N, attrs: string | null): void;
|
|
13
|
+
export declare function extraParams(c: Component, unread?: (name: string) => boolean): string;
|
|
14
|
+
export declare function callChild(s: Scope, e: Emitter, child: Component, propsCode: string, slots: N, attrs: string | null, later?: boolean): void;
|
|
15
15
|
export declare function callWith(s: Scope, e: Emitter, child: Component, m: string, head: string, slotsTy: string, trailing: string, slots: N, vnode?: boolean): void;
|
|
16
16
|
export declare function ownInto(comp: Component, v: Val, want: Ty, node: N): string;
|
package/dist/children.js
CHANGED
|
@@ -6,11 +6,12 @@ import { expr, fieldVal, holdsNothing } from "./expr.js";
|
|
|
6
6
|
import { asF64 } from "./numbers.js";
|
|
7
7
|
import { atom, bare } from "./parens.js";
|
|
8
8
|
import { Emitter } from "./emitter.js";
|
|
9
|
-
import { attrOf, dollarAttrs,
|
|
10
|
-
import { passedKey } from "./fallthrough.js";
|
|
11
|
-
import { claim, paramsOf, slotFieldsOf } from "./plugin.js";
|
|
9
|
+
import { attrOf, dollarAttrs, isAttrs, mergedParts } from "./attrs.js";
|
|
10
|
+
import { isListener, passedKey } from "./fallthrough.js";
|
|
11
|
+
import { claim, paramsOf, slotContextOf, slotFieldsOf } from "./plugin.js";
|
|
12
12
|
import { statements } from "./template.js";
|
|
13
13
|
import { slotContent, slotFieldBorrows, staticallyFilled } from "./slots.js";
|
|
14
|
+
import { slotAsVnodes } from "./dynamic.js";
|
|
14
15
|
export function renderChild(s, e, n, twin) {
|
|
15
16
|
const [target, rawProps, slots, , slotScopeId] = n.arguments;
|
|
16
17
|
let local = null;
|
|
@@ -22,13 +23,24 @@ export function renderChild(s, e, n, twin) {
|
|
|
22
23
|
const child = twin?.comp ?? (childName ? s.components.get(childName) : undefined);
|
|
23
24
|
if (!child)
|
|
24
25
|
fail(s.comp, "FV0501", `a child component must be imported from a \`.vue\` file among the components compiled, in ${ctx.componentsDir.replace(/\/$/, "")}`, n);
|
|
26
|
+
const later = !twin && local !== null && s.loadedLater.has(local);
|
|
25
27
|
for (const p of ctx.plugins)
|
|
26
28
|
p.child?.(s, child, n);
|
|
27
29
|
const parts = !rawProps || rawProps.type === "NullLiteral" ? [] : mergedParts(rawProps);
|
|
28
30
|
const merges = parts.length > 0 && parts[0] !== rawProps;
|
|
29
31
|
const passesAttrs = parts.some(isAttrs);
|
|
30
32
|
const passed = (p) => s.fallthrough !== null && ((isAttrs(p) && s.comp.inheritAttrs) || dollarAttrs(s, p));
|
|
31
|
-
const
|
|
33
|
+
const handlers = (p) => {
|
|
34
|
+
const dynamic = p.type === "ObjectProperty" && p.computed && isCall(p.key, "_toHandlerKey");
|
|
35
|
+
if (!isCall(p, "_toHandlers") && !dynamic)
|
|
36
|
+
return false;
|
|
37
|
+
const declared = child.props.fields.find((f) => isListener(f.js));
|
|
38
|
+
if (declared)
|
|
39
|
+
fail(s.comp, "FV0514", `a listener whose name is not known before rendering, given to ${child.name}, which declares \`${declared.js}\` as a prop`, p);
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
const silent = (p) => p.type === "ObjectProperty" && !p.computed && !passedKey(child, p.key.name ?? String(p.key.value));
|
|
43
|
+
const marker = (p) => handlers(p) || (p.type === "ObjectExpression" && p.properties.length > 0 && p.properties.every((q) => silent(q) || handlers(q)));
|
|
32
44
|
const objects = parts.filter((p) => !isAttrs(p) && !dollarAttrs(s, p) && !marker(p));
|
|
33
45
|
if (parts.some(passed)) {
|
|
34
46
|
for (const name of s.comp.attrNames) {
|
|
@@ -44,7 +56,7 @@ export function renderChild(s, e, n, twin) {
|
|
|
44
56
|
const v = expr(s, objects[0]);
|
|
45
57
|
const own = child.name === s.comp.name && v.ty.k === "struct" && v.ty.name === "Props";
|
|
46
58
|
if (own || (v.ty.k === "child" && v.ty.name === child.name)) {
|
|
47
|
-
callChild(s, e, child, v.code, slots, childAttrsArg(s, child, parts, merges, null, ids, n));
|
|
59
|
+
callChild(s, e, child, v.code, slots, childAttrsArg(s, child, parts, merges, null, ids, n), later);
|
|
48
60
|
return;
|
|
49
61
|
}
|
|
50
62
|
fail(s.comp, "FV0503", `child props must be an object literal, or \`v-bind\` of ${child.name}'s own \`Props\``, n);
|
|
@@ -54,9 +66,12 @@ export function renderChild(s, e, n, twin) {
|
|
|
54
66
|
fail(s.comp, "FV0503", `child props must be an object literal, or \`v-bind\` of ${child.name}'s own \`Props\``, p);
|
|
55
67
|
}
|
|
56
68
|
const given = new Map();
|
|
57
|
-
const fallthrough = new
|
|
69
|
+
const fallthrough = new Map();
|
|
70
|
+
const loaded = !merges && local !== null && s.loadedLater.has(local);
|
|
58
71
|
for (const obj of objects) {
|
|
59
72
|
for (const p of obj.properties) {
|
|
73
|
+
if (handlers(p))
|
|
74
|
+
continue;
|
|
60
75
|
if (p.type !== "ObjectProperty" || p.computed)
|
|
61
76
|
fail(s.comp, "FV0504", "child props hold plain keys", p);
|
|
62
77
|
const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
|
|
@@ -72,7 +87,7 @@ export function renderChild(s, e, n, twin) {
|
|
|
72
87
|
const name = propsToAttrMap[key] ?? key.toLowerCase();
|
|
73
88
|
if (key !== "class" && key !== "style" && !isSSRSafeAttrName(name))
|
|
74
89
|
fail(s.comp, "FV0404", `unsafe attribute name \`${name}\``, p);
|
|
75
|
-
fallthrough.
|
|
90
|
+
fallthrough.set(p, loaded && key === "class" ? loadedClass(s, p.value) : p.value);
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
if (twin) {
|
|
@@ -92,11 +107,41 @@ export function renderChild(s, e, n, twin) {
|
|
|
92
107
|
const v = expr(s, node);
|
|
93
108
|
return fieldInit(f.rust, ownInto(s.comp, { ...v, ty: markHome(v.ty, s.comp.name) }, markHome(f.ty, child.name), node));
|
|
94
109
|
});
|
|
95
|
-
callChild(s, e, child, `&super::${child.module}::Props { ${inits.join(", ")} }`, slots, childAttrsArg(s, child, parts, merges, fallthrough, ids, n));
|
|
110
|
+
callChild(s, e, child, `&super::${child.module}::Props { ${inits.join(", ")} }`, slots, childAttrsArg(s, child, parts, merges, fallthrough, ids, n), later);
|
|
111
|
+
}
|
|
112
|
+
function isCall(n, callee) {
|
|
113
|
+
return n?.type === "CallExpression" && n.callee.type === "Identifier" && n.callee.name === callee;
|
|
96
114
|
}
|
|
97
115
|
export function array(key) {
|
|
98
116
|
return /^(0|[1-9]\d*)$/.test(key) && Number(key) < 2 ** 32 - 1;
|
|
99
117
|
}
|
|
118
|
+
/** A string `:class` given to a component `defineAsyncComponent` loads, in props no `mergeProps`
|
|
119
|
+
* normalised: Vue's wrapper hands on the class once as written and once trimmed, so a class with
|
|
120
|
+
* whitespace around it is written twice. */
|
|
121
|
+
const lengthOf = (n) => ({ type: "MemberExpression", object: n, property: { type: "Identifier", name: "length" }, computed: false });
|
|
122
|
+
function holdsClassLiteral(n) {
|
|
123
|
+
if (n.type === "ObjectExpression" || n.type === "ArrayExpression")
|
|
124
|
+
return true;
|
|
125
|
+
if (n.type === "ConditionalExpression")
|
|
126
|
+
return holdsClassLiteral(n.consequent) || holdsClassLiteral(n.alternate);
|
|
127
|
+
if (n.type === "LogicalExpression")
|
|
128
|
+
return holdsClassLiteral(n.left) || holdsClassLiteral(n.right);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
function loadedClass(s, value) {
|
|
132
|
+
if (value.type === "ConditionalExpression" && (holdsClassLiteral(value.consequent) || holdsClassLiteral(value.alternate))) {
|
|
133
|
+
return { ...value, consequent: loadedClass(s, value.consequent), alternate: loadedClass(s, value.alternate) };
|
|
134
|
+
}
|
|
135
|
+
if (holdsClassLiteral(value) || expr(s, value).ty.k !== "str")
|
|
136
|
+
return value;
|
|
137
|
+
const trimmed = { type: "CallExpression", callee: { type: "MemberExpression", object: value, property: { type: "Identifier", name: "trim" }, computed: false }, arguments: [] };
|
|
138
|
+
return {
|
|
139
|
+
type: "ConditionalExpression",
|
|
140
|
+
test: { type: "BinaryExpression", operator: "===", left: lengthOf(trimmed), right: lengthOf(value) },
|
|
141
|
+
consequent: value,
|
|
142
|
+
alternate: { type: "ArrayExpression", elements: [trimmed, value] },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
100
145
|
function childAttrsArg(s, child, parts, merges, fallthrough, ids, n) {
|
|
101
146
|
const sources = [];
|
|
102
147
|
for (const p of parts) {
|
|
@@ -106,7 +151,7 @@ function childAttrsArg(s, child, parts, merges, fallthrough, ids, n) {
|
|
|
106
151
|
else if (p.type === "ObjectExpression" && fallthrough !== null) {
|
|
107
152
|
const entries = p.properties.filter((q) => fallthrough.has(q)).map((q) => {
|
|
108
153
|
const key = q.key.type === "Identifier" ? q.key.name : String(q.key.value);
|
|
109
|
-
return `(${rustStr(key)}, ${attrOf(s, key, q
|
|
154
|
+
return `(${rustStr(key)}, ${attrOf(s, key, fallthrough.get(q), "vnode")})`;
|
|
110
155
|
});
|
|
111
156
|
if (entries.length)
|
|
112
157
|
sources.push(`&[${entries.join(", ")}]`);
|
|
@@ -129,15 +174,23 @@ export function fieldInit(name, value) {
|
|
|
129
174
|
export function takesSlots(c) {
|
|
130
175
|
return c.slotNames.length > 0 || slotFieldsOf(c).length > 0;
|
|
131
176
|
}
|
|
132
|
-
export function extraParams(c) {
|
|
133
|
-
return (takesSlots(c) ? ", fv_slots: Slots<'_>" : "") + paramsOf(c).map((p) => `, ${p.name}: ${p.ty}`).join("");
|
|
177
|
+
export function extraParams(c, unread = () => false) {
|
|
178
|
+
return (takesSlots(c) ? ", fv_slots: Slots<'_>" : "") + paramsOf(c).map((p) => `, ${unread(p.name) ? "_" : ""}${p.name}: ${p.ty}`).join("");
|
|
134
179
|
}
|
|
135
|
-
export function callChild(s, e, child, propsCode, slots, attrs) {
|
|
180
|
+
export function callChild(s, e, child, propsCode, slots, attrs, later = false) {
|
|
136
181
|
const m = `super::${child.module}`;
|
|
137
182
|
const scoped = child.inherits || takesAttrs(child);
|
|
138
183
|
const trailing = paramsOf(child).map((p) => `, ${p.name}`).join("") + (scoped ? `, ${attrs ?? (takesAttrs(child) ? "&fv::Attrs::NONE" : '""')}` : "");
|
|
139
184
|
const render = scoped ? "render_scoped" : "render";
|
|
185
|
+
const around = later ? ctx.plugins.flatMap((p) => p.loadedLater?.(child) ?? []) : [];
|
|
186
|
+
if (around.length) {
|
|
187
|
+
e.open("");
|
|
188
|
+
for (const line of around)
|
|
189
|
+
e.stmt(line);
|
|
190
|
+
}
|
|
140
191
|
callWith(s, e, child, m, `${m}::${render}(out, ${propsCode}`, `${m}::Slots`, trailing, slots);
|
|
192
|
+
if (around.length)
|
|
193
|
+
e.close();
|
|
141
194
|
}
|
|
142
195
|
export function callWith(s, e, child, m, head, slotsTy, trailing, slots, vnode = false) {
|
|
143
196
|
const given = new Map();
|
|
@@ -160,6 +213,9 @@ export function callWith(s, e, child, m, head, slotsTy, trailing, slots, vnode =
|
|
|
160
213
|
return;
|
|
161
214
|
}
|
|
162
215
|
e.open(`${head}, ${slotsTy}`);
|
|
216
|
+
const context = slotContextOf(child);
|
|
217
|
+
const contextParams = context.map((p) => `, ${p.name}: ${p.ty}`).join("");
|
|
218
|
+
const opaque = vnode ? child.name : s.opaque;
|
|
163
219
|
for (const name of child.slotNames) {
|
|
164
220
|
const field = snake(name);
|
|
165
221
|
const value = given.get(name);
|
|
@@ -168,6 +224,7 @@ export function callWith(s, e, child, m, head, slotsTy, trailing, slots, vnode =
|
|
|
168
224
|
continue;
|
|
169
225
|
}
|
|
170
226
|
const { body, param } = slotContent(s, value);
|
|
227
|
+
const asVnodes = vnode || s.vnode || slotAsVnodes(child, name);
|
|
171
228
|
const shape = child.slotShapes.get(name);
|
|
172
229
|
const takesNone = param?.type === "Identifier" && param.name === "_";
|
|
173
230
|
const sid = child.passesSlotIds ? `fv_sid${++ctx.narrowCount}` : null;
|
|
@@ -205,18 +262,29 @@ export function callWith(s, e, child, m, head, slotsTy, trailing, slots, vnode =
|
|
|
205
262
|
else if (!takesNone)
|
|
206
263
|
fail(s.comp, "FV0905", "slot props are a name or an object pattern", param);
|
|
207
264
|
const life = shape.fields.some((f) => slotFieldBorrows(f.ty)) ? "<'_>" : "";
|
|
208
|
-
e.open(`${field}: Some(&|out: &mut String, ${sp}: &${m}::${shape.name}${life}${sidParam}| -> bool`);
|
|
265
|
+
e.open(`${field}: Some(&|out: &mut String, ${sp}: &${m}::${shape.name}${life}${sidParam}${contextParams}| -> bool`);
|
|
209
266
|
const opened = e.lines.length - 1;
|
|
210
|
-
content({ ...s, locals, sid, vnode });
|
|
267
|
+
content({ ...s, locals, sid, vnode: asVnodes, opaque });
|
|
211
268
|
unread(opened, sp);
|
|
212
269
|
unread(opened, sid);
|
|
270
|
+
for (const p of context)
|
|
271
|
+
unread(opened, p.name);
|
|
213
272
|
e.close("),");
|
|
214
273
|
continue;
|
|
215
274
|
}
|
|
216
275
|
if (!takesNone)
|
|
217
276
|
fail(s.comp, "FV0906", `\`<slot${name === "default" ? "" : ` name="${name}"`}>\` in ${child.name} passes no props`, param);
|
|
218
|
-
const inner = { ...s, sid, vnode };
|
|
219
|
-
if (
|
|
277
|
+
const inner = { ...s, sid, vnode: asVnodes, opaque };
|
|
278
|
+
if (context.length) {
|
|
279
|
+
e.open(`${field}: Some(&|out: &mut String${sidParam}${contextParams}| -> bool`);
|
|
280
|
+
const opened = e.lines.length - 1;
|
|
281
|
+
content(inner);
|
|
282
|
+
unread(opened, sid);
|
|
283
|
+
for (const p of context)
|
|
284
|
+
unread(opened, p.name);
|
|
285
|
+
e.close("),");
|
|
286
|
+
}
|
|
287
|
+
else if (sid !== null) {
|
|
220
288
|
e.open(`${field}: Some(fv::Slot::slotted(&|out: &mut String${sidParam}| -> bool`);
|
|
221
289
|
const opened = e.lines.length - 1;
|
|
222
290
|
content(inner);
|
package/dist/classes.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export type ClassItem = {
|
|
|
7
7
|
};
|
|
8
8
|
export declare function classItems(s: Scope, n: N): ClassItem[];
|
|
9
9
|
export declare function renderClass(s: Scope, e: Emitter, n: N, after?: boolean): void;
|
|
10
|
+
/** The class list of an element in content Vue renders from virtual nodes, in the order Vue's
|
|
11
|
+
* client compiler merges a static `class` written before `:class`: the static names first, at the
|
|
12
|
+
* place of the static `class`. */
|
|
13
|
+
export declare function vnodeClass(s: Scope, n: N): N;
|
|
10
14
|
export declare function classPresent(s: Scope, n: N): string;
|
|
11
15
|
export declare function maybeEqual(s: Scope, n: N): boolean;
|
|
12
16
|
export declare function literalClass(n: N): string | null;
|
package/dist/classes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { escapeHtml } from "@vue/shared";
|
|
2
|
-
import { fail, nothing, rustStr, STR } from "./model.js";
|
|
2
|
+
import { directiveOwner, fail, nothing, rustStr, STR } from "./model.js";
|
|
3
3
|
import { expr } from "./expr.js";
|
|
4
4
|
import { cond, known, truthy } from "./narrowing.js";
|
|
5
5
|
import { meet } from "./strings.js";
|
|
@@ -102,6 +102,28 @@ export function renderClass(s, e, n, after = false) {
|
|
|
102
102
|
e.stmt(`fv::class_into(out, ${wrote}, &[${rest.join(", ")}]);`);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
const UNWRITTEN = new Set(["on", "if", "else-if", "else", "for", "slot", "once", "memo", "cloak", "pre"]);
|
|
106
|
+
/** The class list of an element in content Vue renders from virtual nodes, in the order Vue's
|
|
107
|
+
* client compiler merges a static `class` written before `:class`: the static names first, at the
|
|
108
|
+
* place of the static `class`. */
|
|
109
|
+
export function vnodeClass(s, n) {
|
|
110
|
+
const last = n.type === "ArrayExpression" ? n.elements.at(-1) : null;
|
|
111
|
+
if (n.fvMerged || last?.type !== "StringLiteral" || n.elements.length < 2)
|
|
112
|
+
return n;
|
|
113
|
+
const el = directiveOwner(s.comp, n.elements[0]);
|
|
114
|
+
if (!el)
|
|
115
|
+
return n;
|
|
116
|
+
const own = el.props.findIndex((p) => p.type === 6 && p.name === "class");
|
|
117
|
+
const bound = el.props.findIndex((p) => p.type === 7 && p.name === "bind" && p.arg?.content === "class");
|
|
118
|
+
if (own < 0 || bound < own)
|
|
119
|
+
return n;
|
|
120
|
+
const between = el.props.slice(own + 1, bound).find((p) => p.type === 6 || !UNWRITTEN.has(p.name));
|
|
121
|
+
if (between) {
|
|
122
|
+
const at = { type: "VueTemplate", loc: { start: { line: between.loc.start.line, column: between.loc.start.column - 1 } }, __fv: "source" };
|
|
123
|
+
fail(s.comp, "FV1013", "in content Vue renders from virtual nodes, a `class` written before `:class` is written where the `class` stands, and Vue's server compiler moves it to where `:class` stands; write the two next to each other, or `:class` first", at);
|
|
124
|
+
}
|
|
125
|
+
return { ...n, elements: [last, ...n.elements.slice(0, -1)] };
|
|
126
|
+
}
|
|
105
127
|
export function classPresent(s, n) {
|
|
106
128
|
if (n.fvMerged)
|
|
107
129
|
return n.elements.reduce((acc, el) => logical(acc, "||", classPresent(s, el)), "false");
|