UNPKG

@macro-plugin/qwik

Version:

A simpler way of creating components for qwik by using macros

729 lines (719 loc) 29.8 kB
import { createMacro, dummySpan, createLabeledBlock, createLabeledMacro, markedNode, walk, unMarkNode } from '@macro-plugin/core'; function handleFunc(ast) { let label; if (!ast.body || ast.body.type !== "BlockStatement") return; for (let i = 0; i < ast.body.stmts.length; i++) { if (ast.body.stmts[i].type === "LabeledStatement") { label = ast.body.stmts[i]; if (label.label.value === "qwik") { if (label.body.type === "ExpressionStatement" && label.body.expression.type === "BooleanLiteral") { ast.body.stmts = ast.body.stmts.filter((_, index) => index !== i); const isDecl = "identifier" in ast && ast.identifier != null; if (label.body.expression.value) { this.import("component$", "@builder.io/qwik"); const init = { type: "CallExpression", span: dummySpan, callee: { type: "Identifier", span: dummySpan, value: "component$", optional: false }, arguments: [ { expression: isDecl ? { type: "ArrowFunctionExpression", span: dummySpan, params: ast.params.map(i => i.pat), body: ast.body, async: false, generator: false, } : ast } ], }; if (!isDecl) return init; return { type: "VariableDeclaration", span: dummySpan, kind: "const", declare: false, declarations: [ { type: "VariableDeclarator", span: dummySpan, id: ast.identifier, init, definite: false } ] }; } } else { throw new Error("Expect an boolean in qwik macro."); } } } } } const qwik = createMacro({ FunctionDeclaration(ast) { return handleFunc.apply(this, [ast]); }, FunctionExpression(ast) { return handleFunc.apply(this, [ast]); }, ArrowFunctionExpression(ast) { return handleFunc.apply(this, [ast]); } }); const task = createLabeledBlock("task", "useTask$", "@builder.io/qwik"); const vtask = createLabeledBlock("vtask", "useVisibleTask$", "@builder.io/qwik", true); const varToReturn = (body) => { let ident; body = body.map(i => { if (i.type === "VariableDeclaration" && i.kind === "var") { if (i.declarations[0].id.type === "Identifier") { ident = i.declarations[0].id; } else { throw new Error("Expect an Identifier"); } return { type: "ReturnStatement", argument: i.declarations[0].init, span: dummySpan }; } return i; }); return [ident, body]; }; const resource = createLabeledMacro("resource", function (stmt) { this.import("useResource$", "@builder.io/qwik"); if (stmt.type === "BlockStatement") { const [id, stmts] = varToReturn(stmt.stmts); return { type: "VariableDeclaration", declarations: [ { type: "VariableDeclarator", definite: false, span: dummySpan, id, init: { type: "CallExpression", callee: { type: "Identifier", optional: false, value: "useResource$", span: dummySpan }, span: dummySpan, arguments: [ { expression: { type: "ArrowFunctionExpression", generator: false, async: false, params: [], span: dummySpan, body: { type: "BlockStatement", stmts, span: dummySpan, } } } ] } } ], span: dummySpan, declare: false, kind: "const" }; } else if (stmt.type === "ExpressionStatement" && stmt.expression.type === "ArrowFunctionExpression") { let id, stmts; if (stmt.expression.body.type === "BlockStatement") { [id, stmts] = varToReturn(stmt.expression.body.stmts); stmt.expression.body.stmts = stmts; } else { throw new Error("expect an arrow block inside resource."); } return { type: "VariableDeclaration", declarations: [ { type: "VariableDeclarator", definite: false, span: dummySpan, id, init: { type: "CallExpression", callee: { type: "Identifier", value: "useResource$", span: dummySpan, optional: false }, arguments: [ { expression: stmt.expression } ], span: dummySpan, } } ], kind: "const", span: dummySpan, declare: false, }; } else { throw new Error("this macro only accept a ArrowFunction or BlockStatement"); } }); const store = createLabeledMacro("store", function (stmt) { if (stmt.type !== "BlockStatement") return; const signals = {}; let name; for (const i of stmt.stmts) { if (i.type === "VariableDeclaration" && i.kind === "var") { for (const d of i.declarations) { if (d.id.type === "Identifier") { name = d.id.value; signals[name] = { value: d.init }; } else { throw new Error("Expect Identifier in store"); } } } else { throw new Error("Expect a `var` kind VariableDeclaration node in store block"); } } if (Object.keys(signals).length > 0) { this.import("useStore", "@builder.io/qwik"); return Object.entries(signals).map(([k, v]) => ({ type: "VariableDeclaration", kind: "const", declare: false, declarations: [ { type: "VariableDeclarator", id: markedNode("qwikStore", { type: "Identifier", value: k, optional: false, span: dummySpan }), init: { type: "CallExpression", callee: { type: "Identifier", value: "useStore", optional: false, span: dummySpan }, arguments: [ { expression: v.value } ], span: dummySpan }, definite: false, span: dummySpan } ], span: dummySpan })); } }); const signal = createMacro({ LabeledStatement(ast) { if (ast.body.type !== "BlockStatement" || ast.label.value !== "signal") return; const signals = {}; let name; for (const i of ast.body.stmts) { if (i.type === "VariableDeclaration" && i.kind === "var") { for (const d of i.declarations) { if (d.id.type === "Identifier") { name = d.id.value; signals[name] = { value: d.init }; } else { throw new Error("Expect Identifier in signal"); } } } else { throw new Error("Expect a `var` kind VariableDeclaration node in signal block"); } } if (Object.keys(signals).length > 0) { this.import("useSignal", "@builder.io/qwik"); return Object.entries(signals).map(([k, v]) => ({ type: "VariableDeclaration", kind: "const", declare: false, declarations: [ { type: "VariableDeclarator", id: markedNode("qwikSignal", { type: "Identifier", value: k, optional: false, span: dummySpan }), init: { type: "CallExpression", callee: { type: "Identifier", value: "useSignal", optional: false, span: dummySpan }, arguments: [ { expression: v.value } ], span: dummySpan }, definite: false, span: dummySpan } ], span: dummySpan })); } }, Identifier(ast, parent) { var _a; if ((parent === null || parent === void 0 ? void 0 : parent.type) !== "VariableDeclarator" && ((_a = this.track(ast.value)) === null || _a === void 0 ? void 0 : _a.marker) === "qwikSignal") { this.replace({ type: "MemberExpression", object: ast, property: { type: "Identifier", value: "value", optional: false, span: dummySpan }, span: dummySpan }); this.skip(); } } }); const events = createMacro({ LabeledStatement: { enter(ast) { var _a; if (ast.label.value === "document" && ast.body.type === "BlockStatement") { this.set("QwikInDocument", true); return; } if (ast.label.value === "window" && ast.body.type === "BlockStatement") { this.set("QwikInWindow", true); return; } const inDoc = this.get("QwikInDocument", false); const inWin = this.get("QwikInWindow", false); if (!ast.label.value.startsWith("on") || !["BlockStatement", "ExpressionStatement"].includes(ast.body.type)) return; let expression; let name = inDoc ? "useOnDocument" : inWin ? "useOnWindow" : "useOn"; this.import("$", "@builder.io/qwik"); if (ast.body.type === "BlockStatement") { const firstLabel = ast.body.stmts[0]; if (firstLabel && firstLabel.type === "LabeledStatement") { if (firstLabel.label.value === "document") { ast.body.stmts = ast.body.stmts.slice(1); name = "useOnDocument"; } else if (firstLabel.label.value === "window") { ast.body.stmts = ast.body.stmts.slice(1); name = "useOnWindow"; } } expression = { type: "ArrowFunctionExpression", span: dummySpan, params: [], body: ast.body, async: false, generator: false, }; } else if (ast.body.type === "ExpressionStatement") { expression = ast.body.expression; if (ast.body.expression.type === "FunctionExpression" || ast.body.expression.type === "ArrowFunctionExpression") { if (((_a = ast.body.expression.body) === null || _a === void 0 ? void 0 : _a.type) === "BlockStatement") { const firstLabel = ast.body.expression.body.stmts[0]; if (firstLabel && firstLabel.type === "LabeledStatement") { if (firstLabel.label.value === "document") { ast.body.expression.body.stmts = ast.body.expression.body.stmts.slice(1); name = "useOnDocument"; } else if (firstLabel.label.value === "window") { ast.body.expression.body.stmts = ast.body.expression.body.stmts.slice(1); name = "useOnWindow"; } } } } } this.import(name, "@builder.io/qwik"); return { type: "ExpressionStatement", span: dummySpan, expression: { type: "CallExpression", span: dummySpan, callee: { type: "Identifier", span: dummySpan, value: name, optional: false }, arguments: [ { expression: { type: "StringLiteral", span: dummySpan, value: ast.label.value.slice(2), raw: `'${ast.label.value.slice(2)}'` } }, { expression: { type: "CallExpression", span: dummySpan, callee: { type: "Identifier", span: dummySpan, value: "$", optional: false }, arguments: [ { expression } ], } } ], } }; }, leave(ast) { if (ast.label.value === "document" && ast.body.type === "BlockStatement") { this.replace(ast.body.stmts); this.set("QwikInDocument", false); } if (ast.label.value === "window" && ast.body.type === "BlockStatement") { this.replace(ast.body.stmts); this.set("QwikInWindow", false); } } } }); const computed = createLabeledMacro("computed", function (stmt) { if (stmt.type !== "BlockStatement") return; const computeds = {}; const signals = []; let name; for (const i of stmt.stmts) { if (i.type === "VariableDeclaration" && i.kind === "var") { for (const d of i.declarations) { if (d.id.type === "Identifier") { name = d.id.value; if (d.init) { computeds[name] = { value: JSON.parse(JSON.stringify(d.init)), computed: d.init }; const isSignal = (name) => { var _a; return ((_a = this.track(name)) === null || _a === void 0 ? void 0 : _a.marker) === "qwikSignal"; }; walk(d.init, { enter(node) { if (node.type === "Identifier") { const name = node.value; if (isSignal(name)) { unMarkNode(node); node.value = "__" + name; if (!signals.includes(name)) signals.push(name); } } }, }); } } else { throw new Error("Expect an Identifier"); } } } else { throw new Error("Expect a `var` kind VariableDeclaration node in signal block"); } } if (Object.keys(computeds).length > 0) { this.import(["useSignal", "useTask$"], "@builder.io/qwik"); return [...Object.entries(computeds).map(([k, v]) => ({ type: "VariableDeclaration", kind: "const", declarations: [ { type: "VariableDeclarator", id: markedNode("qwikSignal", { type: "Identifier", value: k, span: dummySpan, optional: false }), init: { type: "CallExpression", callee: { type: "Identifier", value: "useSignal", span: dummySpan, optional: false }, arguments: [ { expression: v.value } ], span: dummySpan }, span: dummySpan } ], declare: false, span: dummySpan })), { type: "ExpressionStatement", span: dummySpan, expression: { type: "CallExpression", callee: { type: "Identifier", value: "useTask$", optional: false, span: dummySpan }, span: dummySpan, arguments: [ { expression: { type: "ArrowFunctionExpression", async: false, generator: false, params: [ { type: "ObjectPattern", optional: false, span: dummySpan, properties: [ { type: "AssignmentPatternProperty", span: dummySpan, key: { type: "Identifier", value: "track", optional: false, span: dummySpan, }, } ] } ], body: { type: "BlockStatement", span: dummySpan, stmts: [ ...signals.map(name => ({ type: "VariableDeclaration", kind: "const", declarations: [ { type: "VariableDeclarator", definite: false, span: dummySpan, id: { type: "Identifier", value: "__" + name, span: dummySpan, optional: false }, init: { type: "CallExpression", callee: { type: "Identifier", value: "track", span: dummySpan, optional: false }, span: dummySpan, arguments: [ { expression: { type: "ArrowFunctionExpression", async: false, generator: false, params: [], body: { type: "Identifier", value: name, optional: false, span: dummySpan }, span: dummySpan } } ] } } ], declare: false, span: dummySpan })), ...Object.entries(computeds).map(([k, v]) => ({ type: "ExpressionStatement", expression: { type: "AssignmentExpression", operator: "=", left: markedNode("qwikSignal", { type: "Identifier", value: k, optional: false, span: dummySpan }), right: v.computed, span: dummySpan }, span: dummySpan })) ] }, span: dummySpan } } ] } }]; } }); const css = createLabeledMacro("css", function (stmt) { if (stmt.type === "ExpressionStatement") { const specifier = this.get("QwikScoped", false) ? "useStyleScoped$" : "useStyle$"; this.import(specifier, "@builder.io/qwik"); stmt.expression = { type: "CallExpression", span: dummySpan, callee: { type: "Identifier", span: dummySpan, value: specifier, optional: false }, arguments: [ { expression: stmt.expression } ], }; return stmt; } else { throw new Error("this macro only accept an Expression"); } }); const link = createLabeledMacro("link", function (stmt) { if (stmt.type !== "ExpressionStatement") return; let name; let linkCount = this.get("QwikLinkCount", 0); const scoped = this.get("QwikScoped", false); const specifier = scoped ? "useStyleScoped$" : "useStyles$"; const links = []; if (stmt.expression.type === "StringLiteral") { name = "__link" + linkCount; links.push(name); this.import(name, stmt.expression.value, true); linkCount += 1; } else if (stmt.expression.type === "ArrayExpression") { for (const i of stmt.expression.elements) { if ((i === null || i === void 0 ? void 0 : i.expression.type) === "StringLiteral") { name = "__link" + linkCount; links.push(name); this.import(name, i.expression.value, true); linkCount += 1; } else { throw new Error("Expect a StringLiteral"); } } } else { throw new Error("Only support StringLiteral or ArrayExpression"); } this.set("QwikLinkCount", linkCount); this.import(specifier, "@builder.io/qwik"); return links.map(i => ({ type: "ExpressionStatement", span: dummySpan, expression: { type: "CallExpression", span: dummySpan, callee: { type: "Identifier", span: dummySpan, value: specifier, optional: false }, arguments: [ { expression: { type: "Identifier", span: dummySpan, value: i, optional: false } } ], } })); }); const scoped = createLabeledMacro("scoped", { enter(stmt) { if (stmt.type === "BlockStatement") { this.set("QwikScoped", true); } else { throw new Error("Only accept BlockStatement in scoped macro."); } }, leave(stmt) { if (stmt.type === "BlockStatement") { this.replace(stmt.stmts); this.set("QwikScoped", false); } } }); export { computed, css, events, link, qwik, resource, scoped, signal, store, task, vtask };