release: Vs Code New Extension with layout Support
This commit is contained in:
+103
-26
@@ -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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user