release: WRNexusJS 0.2.31
This commit is contained in:
@@ -78,8 +78,22 @@ function renderAttr(attr: Attr): string {
|
||||
}
|
||||
|
||||
function eventAttribute(name: string): string {
|
||||
if (name.startsWith("browser-")) return `data-on-wrnexus-browser-${name.slice(8)}`;
|
||||
if (name.startsWith("mobile-")) return `data-on-wrnexus-mobile-${name.slice(7)}`;
|
||||
if (name.startsWith("window:")) {
|
||||
return `data-on-window-${name.slice("window:".length)}`;
|
||||
}
|
||||
|
||||
if (name.startsWith("document:")) {
|
||||
return `data-on-document-${name.slice("document:".length)}`;
|
||||
}
|
||||
|
||||
if (name.startsWith("browser-")) {
|
||||
return `data-on-wrnexus-browser-${name.slice(8)}`;
|
||||
}
|
||||
|
||||
if (name.startsWith("mobile-")) {
|
||||
return `data-on-wrnexus-mobile-${name.slice(7)}`;
|
||||
}
|
||||
|
||||
return `data-on-${name}`;
|
||||
}
|
||||
|
||||
@@ -437,7 +451,9 @@ function renderNestedComponentInvocation(
|
||||
.filter((attr) => attr.name !== "data-component")
|
||||
.map((attr) => {
|
||||
if (attr.event) {
|
||||
return ` ${eventAttribute(attr.name)}="${compileAttrValue(attr.value, ctx)}"`;
|
||||
return (
|
||||
escLit(` ${eventAttribute(attr.name)}="`) + escLit(attrEscape(attr.value)) + escLit(`"`)
|
||||
);
|
||||
}
|
||||
|
||||
if (attr.boolean) {
|
||||
@@ -790,6 +806,19 @@ interface CompCtx {
|
||||
loopVars?: Set<string>;
|
||||
}
|
||||
|
||||
interface ComponentBehavior {
|
||||
functions: string;
|
||||
lifecycle: {
|
||||
mount?: string;
|
||||
update?: string;
|
||||
unmount?: string;
|
||||
};
|
||||
watches: Array<{
|
||||
state: string;
|
||||
body: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/** Parse a `data-for="item in list"` / `"item, i in list"` directive value. */
|
||||
export function parseForExpr(value: string): { item: string; index?: string; list: string } | null {
|
||||
const m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)\s*$/.exec(
|
||||
@@ -861,6 +890,44 @@ function escLit(s: string): string {
|
||||
return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
||||
}
|
||||
|
||||
function componentBehavior(ast: PageAst): ComponentBehavior | null {
|
||||
const functions = ast.functions
|
||||
.map((body) => body.trim())
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
|
||||
const lifecycle = {
|
||||
...(ast.lifecycle.mount?.trim() ? { mount: ast.lifecycle.mount.trim() } : {}),
|
||||
|
||||
...(ast.lifecycle.update?.trim() ? { update: ast.lifecycle.update.trim() } : {}),
|
||||
|
||||
...(ast.lifecycle.unmount?.trim() ? { unmount: ast.lifecycle.unmount.trim() } : {}),
|
||||
};
|
||||
|
||||
const watches = ast.watches.map((watch) => ({
|
||||
state: watch.state,
|
||||
body: watch.body.trim(),
|
||||
}));
|
||||
|
||||
if (!functions && Object.keys(lifecycle).length === 0 && watches.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
functions,
|
||||
lifecycle,
|
||||
watches,
|
||||
};
|
||||
}
|
||||
|
||||
function behaviorAttribute(behavior: ComponentBehavior | null): string {
|
||||
if (!behavior) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return ` data-wrn-behavior="${attrEscape(JSON.stringify(behavior))}"`;
|
||||
}
|
||||
|
||||
const INTERP_RE = /\{([^{}]+)\}/g;
|
||||
|
||||
function exprRefsState(expr: string, stateNames: Set<string>): boolean {
|
||||
@@ -971,7 +1038,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
.filter((a) => a.name !== "class" && !a.name.startsWith("class:"))
|
||||
.map((a) => {
|
||||
if (a.event) {
|
||||
return ` ${eventAttribute(a.name)}="${compileAttrValue(a.value, ctx)}"`;
|
||||
return ` ${eventAttribute(a.name)}="${escLit(attrEscape(a.value))}"`;
|
||||
}
|
||||
|
||||
if (a.boolean) {
|
||||
@@ -1066,12 +1133,17 @@ function generateComponent(ast: PageAst): string {
|
||||
// A component needs a reactive scope only when it has state or event handlers.
|
||||
// Prop-driven text/attributes are baked server-side, so static components ship
|
||||
// no JavaScript at all.
|
||||
const needsScope = ast.states.length > 0 || viewHasEvents(ast.view);
|
||||
const behavior = componentBehavior(ast);
|
||||
|
||||
const needsScope = ast.states.length > 0 || viewHasEvents(ast.view) || behavior !== null;
|
||||
|
||||
const scopeKeys = [
|
||||
...effectiveProps.map((prop) => prop.name),
|
||||
...ast.states.map((state) => state.name),
|
||||
];
|
||||
|
||||
const behaviorAttr = behaviorAttribute(behavior);
|
||||
|
||||
const decls: string[] = [];
|
||||
for (const prop of effectiveProps) {
|
||||
decls.push(
|
||||
@@ -1083,7 +1155,7 @@ function generateComponent(ast: PageAst): string {
|
||||
}
|
||||
|
||||
const returnExpr = needsScope
|
||||
? "`" + styleTag + '<div data-scope="${__scope}">' + viewCode + "</div>`"
|
||||
? "`" + styleTag + `<div data-scope="\${__scope}"${behaviorAttr}>` + viewCode + "</div>`"
|
||||
: "`" + styleTag + viewCode + "`";
|
||||
|
||||
const scopeLine =
|
||||
@@ -1098,6 +1170,11 @@ function generateComponent(ast: PageAst): string {
|
||||
} else {
|
||||
out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`);
|
||||
}
|
||||
|
||||
if (behavior) {
|
||||
out.push(`export const __wrnexusBehavior = ${JSON.stringify(behavior, null, 2)};`);
|
||||
}
|
||||
|
||||
out.push(`function __coerce(v: any, def: any): any {
|
||||
if (v === undefined || v === null) return def;
|
||||
if (typeof def === "number") return Number(v);
|
||||
|
||||
Reference in New Issue
Block a user