release: Vs Code New Extension with layout Support

This commit is contained in:
2026-07-14 15:37:02 +05:30
parent 56027531ef
commit e27c92e3b6
11 changed files with 1034 additions and 30 deletions
+103 -26
View File
@@ -274,10 +274,10 @@ function parse(source) {
};
try {
const opener = lx.next();
if (opener.type !== "ident" || opener.value !== "page" && opener.value !== "component") {
throw new ParseError(`Expected 'page' or 'component' but got '${opener.value || opener.type}' at offset ${opener.pos}`);
if (opener.type !== "ident" || !["page", "component", "layout"].includes(opener.value)) {
throw new ParseError(`Expected 'page', 'component', or 'layout' but got '${opener.value || opener.type}' at offset ${opener.pos}`);
}
const kind = opener.value === "component" ? "component" : "page";
const kind = opener.value;
const name = expect("ident").value;
expect("lbrace");
let layout;
@@ -448,12 +448,12 @@ function parse(source) {
function parseHtmlView(src, pos) {
let i = pos;
const isNameStart = (c) => /[A-Za-z_]/.test(c);
const isNamePart = (c) => /[A-Za-z0-9_:.[\]%-]/.test(c);
const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c);
const isAttributeNamePart = (c, next) => {
if (c === "/") {
return next !== ">";
}
return isNamePart(c);
return /[A-Za-z0-9_$:.[\]%-]/.test(c);
};
const isWs2 = (c) => c === " " || c === "\t" || c === `
` || c === "\r";
@@ -491,9 +491,21 @@ function parseHtmlView(src, pos) {
i++;
return value;
};
const readName = () => {
if (i >= src.length || !isNameStart(src[i]))
return fail("Expected a tag or attribute name");
const readTagName = () => {
if (i >= src.length || !isNameStart(src[i])) {
return fail("Expected a tag name");
}
const start = i++;
while (i < src.length && isTagNamePart(src[i])) {
i++;
}
return src.slice(start, i);
};
const readAttributeName = () => {
const first = src[i];
if (i >= src.length || !isNameStart(first) && first !== ":" && first !== "$") {
return fail("Expected an attribute name");
}
const start = i++;
while (i < src.length && isAttributeNamePart(src[i], src[i + 1])) {
i++;
@@ -502,7 +514,7 @@ function parseHtmlView(src, pos) {
};
const parseTag = () => {
i++;
const tag = readName();
const tag = readTagName();
const attrs = [];
for (;; ) {
skipWs();
@@ -519,7 +531,7 @@ function parseHtmlView(src, pos) {
}
if (c === "@") {
i++;
const name2 = readName();
const name2 = readAttributeName();
skipWs();
if (src[i] !== "=")
return fail(`Expected '=' after @${name2}`);
@@ -528,7 +540,7 @@ function parseHtmlView(src, pos) {
attrs.push({ name: name2, value: readQuoted(), event: true });
continue;
}
const name = readName();
const name = readAttributeName();
skipWs();
if (src[i] === "=") {
i++;
@@ -546,7 +558,7 @@ function parseHtmlView(src, pos) {
return fail(`Expected </${tag}>`);
i += 2;
skipWs();
const close = readName();
const close = readTagName();
if (close !== tag)
return fail(`Mismatched </${close}>, expected </${tag}>`);
skipWs();
@@ -674,6 +686,9 @@ function parseHtmlView(src, pos) {
}
// ../../packages/compiler/src/codegen.ts
function isComponentTag(tag) {
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
}
function attrEscape(value) {
return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
@@ -804,21 +819,30 @@ function bakeLoopAttr(raw) {
return out + escLit(attrEscape(raw.slice(last)));
}
function renderLoopBody(node) {
if (node.type === "text")
if (node.type === "text") {
return bakeLoopText(node.value);
if (node.type === "each")
}
if (node.type === "each") {
return compileEachExpr(node);
if (node.type === "if")
}
if (node.type === "if") {
return compileIfExpr(node);
const attrs = node.attrs.map((a) => {
const name = a.event ? eventAttribute(a.name) : a.name;
if (a.boolean)
}
const componentTag = isComponentTag(node.tag);
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => {
const name = attr.event ? eventAttribute(attr.name) : attr.name;
if (attr.boolean) {
return escLit(` ${name}`);
return escLit(` ${name}="`) + bakeLoopAttr(a.value) + escLit(`"`);
}
return escLit(` ${name}="`) + bakeLoopAttr(attr.value) + escLit(`"`);
}).join("");
if (VOID_ELEMENTS.has(node.tag.toLowerCase()))
return escLit(`<${node.tag}`) + attrs + escLit(">");
const inner = node.children.map(renderLoopBody).join("");
if (componentTag) {
return escLit(`<div data-component="${attrEscape(node.tag)}"`) + attrs + escLit(">") + inner + escLit("</div>");
}
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return escLit(`<${node.tag}`) + attrs + escLit(">");
}
return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(`</${node.tag}>`);
}
function compileEachExpr(node) {
@@ -862,6 +886,9 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive
loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node));
return `\x00WRNEACH${loops.length - 1}\x00`;
}
if (isComponentTag(node.tag)) {
return renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive);
}
const apiName = attrValue(node.attrs, "api");
const apiBinding = apiName ? apiBindings.get(apiName) : undefined;
if (apiName && !apiBinding) {
@@ -888,6 +915,35 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive
}) : node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}</${node.tag}>`;
}
function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) {
const attrs = node.attrs.filter((attr) => attr.name !== "data-component");
const inner = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
return `<div data-component="${attrEscape(node.tag)}"${renderAttrs(attrs, undefined, reactive)}>${inner}</div>`;
}
function renderNestedComponentInvocation(node, ctx) {
let bindIndex = 0;
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => {
if (attr.event) {
return ` ${eventAttribute(attr.name)}="${compileAttrValue(attr.value, ctx)}"`;
}
if (attr.boolean) {
return ` ${attr.name}`;
}
const rendered = ` ${attr.name}="` + `${compileAttrValue(attr.value, ctx)}"`;
if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
}).join("");
const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? {
...ctx,
loopVars: new Set([...ctx.loopVars ?? [], ...loops])
} : ctx;
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
return `<div data-component="${attrEscape(node.tag)}"${attrs}>${inner}</div>`;
}
function ssrMarker(bindings, binding) {
const marker = `<!--wrnexus-ssr:${bindings.length}-->`;
bindings.push({ marker, ...binding });
@@ -1000,8 +1056,9 @@ async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<strin
}`;
}
function generate(ast) {
if (ast.kind === "component")
if (ast.kind === "component" || ast.kind === "layout") {
return generateComponent(ast);
}
const out = [];
const ssrBindings = [];
const csrBindings = [];
@@ -1230,6 +1287,8 @@ function compileText(raw, ctx) {
out += escLit(`{${expr}}`);
} else if (exprRefsState(expr, ctx.stateNames)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
}
@@ -1262,6 +1321,9 @@ function renderComponentNode(node, ctx) {
if (node.type === "each" || node.type === "if") {
throw new Error("Server `{#each}` / `{#if}` blocks are supported in pages, not components. Move them into a page (or use data-for / data-show on the client).");
}
if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx);
}
let bindIndex = 0;
const staticClasses = [];
const conditionalClasses = [];
@@ -1309,10 +1371,18 @@ function renderComponentNode(node, ctx) {
}
function generateComponent(ast) {
const out = [];
const effectiveProps = ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content") ? [
{
name: "content",
default: '""'
},
...ast.props
] : ast.props;
const stateNames = new Set(ast.states.map((s) => s.name));
const nameRefs = new Map;
for (const p of ast.props)
for (const p of effectiveProps) {
nameRefs.set(p.name, safeRef(p.name));
}
for (const s of ast.states)
nameRefs.set(s.name, safeRef(s.name));
const resolveExpr = (expr) => {
@@ -1331,9 +1401,12 @@ ${styles.map(styleEscape).join(`
`)}
</style>`) : "";
const needsScope = ast.states.length > 0 || viewHasEvents(ast.view);
const scopeKeys = [...ast.props.map((p) => p.name), ...ast.states.map((s) => s.name)];
const scopeKeys = [
...effectiveProps.map((prop) => prop.name),
...ast.states.map((state) => state.name)
];
const decls = [];
for (const prop of ast.props) {
for (const prop of effectiveProps) {
decls.push(` const ${nameRefs.get(prop.name)} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}));`);
}
for (const state of ast.states) {
@@ -1343,7 +1416,11 @@ ${styles.map(styleEscape).join(`
const scopeLine = needsScope && scopeKeys.length > 0 ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys.map((k) => `${JSON.stringify(k)}: ${nameRefs.get(k)}`).join(", ")} });
` : needsScope ? ` const __scope = "";
` : "";
out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`);
if (ast.kind === "layout") {
out.push(`export const __wrnexusLayout = ${JSON.stringify(ast.name)};`);
} else {
out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`);
}
out.push(`function __coerce(v: any, def: any): any {
if (v === undefined || v === null) return def;
if (typeof def === "number") return Number(v);