edge.js
Version:
Template engine
1,073 lines (1,072 loc) • 40 kB
JavaScript
import { a as StringifiedObject, c as isNotSubsetOf, d as parseJsArg, f as stringifyAttributes, g as __toESM, h as __export, i as htmlSafe, l as isSubsetOf, m as require_classnames, n as Template, o as asyncEach, p as unallowedExpression, r as escape, s as each, t as require_js_stringify, u as nanoid } from "./js-stringify-IDe7srW5.js";
import { fileURLToPath } from "node:url";
import string from "@poppinss/utils/string";
import { isAbsolute, join, relative } from "node:path";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { EdgeBuffer, Parser, Stack, expressions } from "edge-parser";
import { EdgeError, EdgeError as EdgeError$1 } from "edge-error";
import lodash from "@poppinss/utils/lodash";
import * as lexerUtils from "edge-lexer/utils";
import inspect from "@poppinss/inspect";
var Loader = class {
#mountedDirs = /* @__PURE__ */ new Map();
#preRegistered = /* @__PURE__ */ new Map();
#readTemplateContents(absPath) {
try {
return readFileSync(absPath, "utf-8");
} catch (error) {
if (error.code === "ENOENT") throw new Error(`Cannot resolve "${absPath}". Make sure the file exists`);
else throw error;
}
}
#getDiskComponents(diskName) {
const componentsDirName = "components";
const diskBasePath = this.#mountedDirs.get(diskName);
let files = diskName === "default" ? Array.from(this.#preRegistered.keys()).map((template) => {
return {
fileName: template,
componentPath: template
};
}) : [];
const componentsPath = join(diskBasePath, componentsDirName);
if (existsSync(componentsPath)) files = files.concat(readdirSync(join(componentsPath), {
recursive: true,
withFileTypes: true
}).filter((file) => {
return file.isFile() && file.name.endsWith(".edge");
}).map((file) => {
const fileName = string.toUnixSlash(relative(componentsPath, join(file.parentPath, file.name))).replace(/\.edge$/, "");
return {
fileName,
componentPath: `${componentsDirName}/${fileName}`
};
}));
return files.map(({ fileName, componentPath }) => {
const tagName = fileName.split("/").filter((segment, index) => {
return index === 0 || segment !== "index";
}).map((segment) => string.camelCase(segment)).join(".");
return {
componentName: diskName !== "default" ? `${diskName}::${componentPath}` : componentPath,
tagName: diskName !== "default" ? `${diskName}.${tagName}` : tagName
};
});
}
#getDiskTemplates(diskName) {
const diskBasePath = this.#mountedDirs.get(diskName);
let files = diskName === "default" ? Array.from(this.#preRegistered.keys()) : [];
if (existsSync(diskBasePath)) files = files.concat(readdirSync(diskBasePath, {
recursive: true,
withFileTypes: true
}).filter((file) => {
return file.isFile() && file.name.endsWith(".edge");
}).map((file) => {
return relative(diskBasePath, join(file.parentPath, file.name)).replace(/\.edge$/, "");
}));
return files.map((file) => {
const fileName = string.toUnixSlash(file).replace(/\.edge$/, "");
return diskName !== "default" ? `${diskName}::${fileName}` : fileName;
});
}
#extractDiskAndTemplateName(templatePath) {
let [disk, ...rest] = templatePath.split("::");
if (!rest.length) {
rest = [disk];
disk = "default";
}
let [template, ext] = rest.join("::").split(".edge");
return [disk, `${template}.${ext || "edge"}`];
}
get mounted() {
return Array.from(this.#mountedDirs).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
}
get templates() {
return Array.from(this.#preRegistered).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
}
mount(diskName, dirPath) {
this.#mountedDirs.set(diskName, typeof dirPath === "string" ? dirPath : fileURLToPath(dirPath));
}
unmount(diskName) {
this.#mountedDirs.delete(diskName);
}
makePath(templatePath) {
if (this.#preRegistered.has(templatePath)) return templatePath;
if (isAbsolute(templatePath)) return templatePath;
const [diskName, template] = this.#extractDiskAndTemplateName(templatePath);
const mountedDir = this.#mountedDirs.get(diskName);
if (!mountedDir) throw new Error(`"${diskName}" namespace is not mounted`);
return join(mountedDir, template);
}
resolve(templatePath) {
if (this.#preRegistered.has(templatePath)) return this.#preRegistered.get(templatePath);
templatePath = isAbsolute(templatePath) ? templatePath : this.makePath(templatePath);
return { template: this.#readTemplateContents(templatePath) };
}
register(templatePath, contents) {
if (typeof contents.template !== "string") throw new Error("Make sure to define the template content as a string");
if (this.#preRegistered.has(templatePath)) throw new Error(`Cannot override previously registered "${templatePath}" template`);
this.#preRegistered.set(templatePath, contents);
}
remove(templatePath) {
this.#preRegistered.delete(templatePath);
}
listComponents() {
return [...this.#mountedDirs.keys()].map((diskName) => {
return {
diskName,
components: this.#getDiskComponents(diskName)
};
});
}
listTemplates() {
return [...this.#mountedDirs.keys()].map((diskName) => {
return {
diskName,
templates: this.#getDiskTemplates(diskName)
};
});
}
};
const ifTag = {
block: true,
seekable: true,
tagName: "if",
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
isNotSubsetOf(parsed, [expressions.SequenceExpression], () => {
unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @if tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.writeStatement(`if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
token.children.forEach((child) => parser.processToken(child, buffer));
buffer.writeStatement("}", token.filename, -1);
}
};
const letTag = {
block: false,
seekable: true,
tagName: "let",
noNewLine: true,
compile(parser, buffer, token) {
const parsed = parser.utils.generateAST(`let ${token.properties.jsArg}`, token.loc, token.filename).declarations[0];
const key = parsed.id;
const value = parsed.init;
isSubsetOf(key, [
"ObjectPattern",
expressions.Identifier,
"ArrayPattern"
], () => {
throw unallowedExpression(`Invalid variable name for the @let tag`, token.filename, parser.utils.getExpressionLoc(key));
});
if (key.type === "Identifier") parser.stack.defineVariable(key.name);
else if (key.type === "ObjectPattern") key.properties.forEach((property) => {
parser.stack.defineVariable(property.argument ? property.argument.name : property.value.name);
});
else if (key.type === "ArrayPattern") key.elements.forEach((element) => {
parser.stack.defineVariable(element.argument ? element.argument.name : element.name);
});
const expression = `let ${parser.utils.stringify(key)} = ${parser.utils.stringify(parser.utils.transformAst(value, token.filename, parser))}`;
buffer.writeExpression(expression, token.filename, token.loc.start.line);
},
boot(template) {
template.macro("setValue", lodash.set);
}
};
function getLoopList(rhsExpression, parser, filename) {
return parser.utils.stringify(parser.utils.transformAst(rhsExpression, filename, parser));
}
function getLoopItemAndIndex(lhsExpression, parser, filename) {
isSubsetOf(lhsExpression, [expressions.SequenceExpression, expressions.Identifier], () => {
unallowedExpression(`invalid left hand side "${lhsExpression.type}" expression for the @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression));
});
if (lhsExpression.type === "SequenceExpression") {
isSubsetOf(lhsExpression.expressions[0], [expressions.Identifier], () => {
unallowedExpression(`"${lhsExpression.expressions[0]}.type" is not allowed as value identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[0]));
});
isSubsetOf(lhsExpression.expressions[1], [expressions.Identifier], () => {
unallowedExpression(`"${lhsExpression.expressions[1]}.type" is not allowed as key identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[1]));
});
return [lhsExpression.expressions[0].name, lhsExpression.expressions[1].name];
}
return [lhsExpression.name];
}
const eachTag = {
block: true,
seekable: true,
tagName: "each",
compile(parser, buffer, token) {
const awaitKeyword = parser.asyncMode ? "await " : "";
const loopFunctionName = parser.asyncMode ? "loopAsync" : "loop";
const asyncKeyword = parser.asyncMode ? "async " : "";
const { expression } = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename);
isSubsetOf(expression, [expressions.BinaryExpression], () => {
unallowedExpression(`"${token.properties.jsArg}" is not valid expression for the @each tag`, token.filename, parser.utils.getExpressionLoc(expression));
});
const elseIndex = token.children.findIndex((child) => lexerUtils.isTag(child, "else"));
const elseChildren = elseIndex > -1 ? token.children.splice(elseIndex) : [];
const list = getLoopList(expression.right, parser, token.filename);
const [item, index] = getLoopItemAndIndex(expression.left, parser, token.filename);
if (elseIndex > -1) buffer.writeStatement(`if(template.size(${list})) {`, token.filename, token.loc.start.line);
const loopCallbackArgs = (index ? [item, index] : [item]).join(",");
buffer.writeStatement(`${awaitKeyword}template.${loopFunctionName}(${list}, ${asyncKeyword}function (${loopCallbackArgs}) {`, token.filename, token.loc.start.line);
parser.stack.defineScope();
parser.stack.defineVariable(item);
index && parser.stack.defineVariable(index);
token.children.forEach((child) => parser.processToken(child, buffer));
parser.stack.clearScope();
buffer.writeExpression("})", token.filename, -1);
if (elseIndex > -1) {
elseChildren.forEach((elseChild) => parser.processToken(elseChild, buffer));
buffer.writeStatement("}", token.filename, -1);
}
},
boot(template) {
template.macro("loopAsync", asyncEach);
template.macro("loop", each);
template.macro("size", lodash.size);
}
};
const slotTag = {
block: true,
seekable: true,
tagName: "slot",
noNewLine: true,
compile(_, __, token) {
throw new EdgeError$1("@slot tag must appear as top level tag inside the @component tag", "E_ORPHAN_SLOT_TAG", {
line: token.loc.start.line,
col: token.loc.start.col,
filename: token.filename
});
}
};
const elseTag = {
block: false,
seekable: false,
tagName: "else",
compile(_, buffer, token) {
buffer.writeStatement("} else {", token.filename, -1);
}
};
const evalTag = {
block: false,
seekable: true,
tagName: "eval",
noNewLine: true,
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
buffer.writeExpression(parser.utils.stringify(parsed), token.filename, token.loc.start.line);
}
};
const stackTag = {
tagName: "stack",
block: false,
seekable: true,
noNewLine: false,
compile(parser, buffer, token) {
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
if (expressions.SequenceExpression.includes(parsed.type)) throw new EdgeError$1(`"${token.properties.jsArg}" is not a valid argument type for the "@stack" tag`, "E_UNALLOWED_EXPRESSION", {
...parser.utils.getExpressionLoc(parsed),
filename: token.filename
});
buffer.outputExpression(`template.stacks.create(${parser.utils.stringify(parsed)})`, token.filename, token.loc.start.line, false);
}
};
const assignTag = {
block: false,
seekable: true,
tagName: "assign",
noNewLine: true,
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
isSubsetOf(parsed, [expressions.AssignmentExpression], () => {
throw unallowedExpression(`Invalid expression for the @assign tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.writeExpression(parser.utils.stringify(parsed), token.filename, token.loc.start.line);
},
boot(template) {
template.macro("setValue", lodash.set);
}
};
const injectTag = {
block: false,
seekable: true,
tagName: "inject",
noNewLine: true,
compile(parser, buffer, token) {
token.properties.jsArg = `(${token.properties.jsArg})`;
const parsed = parseJsArg(parser, token);
isSubsetOf(parsed, [
expressions.ObjectExpression,
expressions.Identifier,
expressions.CallExpression
], () => {
throw unallowedExpression(`"${token.properties.jsArg}" is not a valid key-value pair for the @inject tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.writeStatement("if (!state.$slots || !state.$slots.$context) {", token.filename, token.loc.start.line);
buffer.writeExpression(`throw new Error('Cannot use "@inject" outside of a component scope')`, token.filename, token.loc.start.line);
buffer.writeStatement("}", token.filename, token.loc.start.line);
buffer.writeExpression(`Object.assign(state.$slots.$context, ${parser.utils.stringify(parsed)})`, token.filename, token.loc.start.line);
}
};
const unlessTag = {
block: true,
seekable: true,
tagName: "unless",
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
isNotSubsetOf(parsed, [expressions.SequenceExpression], () => {
unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @unless tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.writeStatement(`if (!${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
token.children.forEach((child) => parser.processToken(child, buffer));
buffer.writeStatement("}", token.filename, -1);
}
};
const elseIfTag = {
block: false,
seekable: true,
tagName: "elseif",
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
isNotSubsetOf(parsed, [expressions.SequenceExpression], () => {
unallowedExpression(`{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.writeStatement(`} else if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
}
};
const pushToTag = {
tagName: "pushTo",
block: true,
seekable: true,
noNewLine: true,
generateId() {
return `stack_${nanoid()}`;
},
compile(parser, buffer, token) {
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
if (expressions.SequenceExpression.includes(parsed.type)) throw new EdgeError$1(`"${token.properties.jsArg}" is not a valid argument type for the "@pushTo" tag`, "E_UNALLOWED_EXPRESSION", {
...parser.utils.getExpressionLoc(parsed),
filename: token.filename
});
const stackId = this.generateId();
const stackBuffer = buffer.create(token.filename, { outputVar: stackId });
for (let child of token.children) parser.processToken(child, stackBuffer);
buffer.writeStatement(stackBuffer.disableFileAndLineVariables().disableReturnStatement().disableTryCatchBlock().flush(), token.filename, token.loc.start.line);
buffer.writeExpression(`template.stacks.pushTo(${parser.utils.stringify(parsed)}, ${stackId})`, token.filename, token.loc.start.line);
}
};
const ALLOWED_EXPRESSION = [
expressions.Literal,
expressions.Identifier,
expressions.CallExpression,
expressions.TemplateLiteral,
expressions.MemberExpression,
expressions.LogicalExpression,
expressions.ConditionalExpression
];
function getRenderExpression(parser, parsedExpression) {
const localVariables = parser.stack.list();
const renderArgs = localVariables.length ? [parser.utils.stringify(parsedExpression), localVariables.map((localVar) => `"${localVar}"`).join(",")] : [parser.utils.stringify(parsedExpression)];
const callFnArgs = localVariables.length ? [
"template",
"state",
"$context",
localVariables.map((localVar) => localVar).join(",")
] : [
"template",
"state",
"$context"
];
return `template.compilePartial(${renderArgs.join(",")})(${callFnArgs.join(",")})`;
}
const includeTag = {
block: false,
seekable: true,
tagName: "include",
compile(parser, buffer, token) {
const awaitKeyword = parser.asyncMode ? "await " : "";
const parsed = parseJsArg(parser, token);
isSubsetOf(parsed, ALLOWED_EXPRESSION, () => {
unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @include tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.outputExpression(`${awaitKeyword}${getRenderExpression(parser, parsed)}`, token.filename, token.loc.start.line, false);
}
};
const debuggerTag = {
block: false,
seekable: false,
tagName: "debugger",
noNewLine: true,
compile(_, buffer, token) {
buffer.writeExpression("debugger", token.filename, token.loc.start.line);
}
};
const newErrorTag = {
block: false,
seekable: true,
tagName: "newError",
noNewLine: true,
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
let message = "";
let line = token.loc.start.line;
let col = token.loc.start.col;
let filename = "$filename";
if (parsed.type === expressions.SequenceExpression) {
message = parser.utils.stringify(parsed.expressions[0]);
filename = parsed.expressions[1] ? parser.utils.stringify(parsed.expressions[1]) : "$filename";
line = parsed.expressions[2] ? parser.utils.stringify(parsed.expressions[2]) : token.loc.start.line;
col = parsed.expressions[3] ? parser.utils.stringify(parsed.expressions[3]) : token.loc.start.col;
} else message = parser.utils.stringify(parsed);
buffer.writeStatement(`template.newError(${message}, ${filename}, ${line}, ${col})`, token.filename, token.loc.start.line);
}
};
const ALLOWED_EXPRESSION_FOR_COMPONENT_NAME = [
expressions.Identifier,
expressions.Literal,
expressions.LogicalExpression,
expressions.MemberExpression,
expressions.ConditionalExpression,
expressions.CallExpression,
expressions.TemplateLiteral
];
function getComponentNameAndProps(expression, parser, filename) {
let name;
if (expression.type === expressions.SequenceExpression) name = expression.expressions.shift();
else name = expression;
isSubsetOf(name, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME, () => {
unallowedExpression(`"${parser.utils.stringify(name)}" is not a valid argument for component name`, filename, parser.utils.getExpressionLoc(name));
});
if (expression.type === expressions.SequenceExpression) {
const firstSequenceExpression = expression.expressions[0];
return [parser.utils.stringify(name), parser.utils.stringify(firstSequenceExpression)];
}
return [parser.utils.stringify(name), "{}"];
}
function getSlotNameAndProps(token, parser) {
const parsed = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename).expression;
isSubsetOf(parsed, [expressions.Literal, expressions.SequenceExpression], () => {
unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
let name;
if (parsed.type === expressions.SequenceExpression) name = parsed.expressions[0];
else name = parsed;
isSubsetOf(name, [expressions.Literal], () => {
unallowedExpression("slot name must be a valid string literal", token.filename, parser.utils.getExpressionLoc(name));
});
if (parsed.type === expressions.Literal) return [name.raw, null];
if (parsed.expressions.length > 2) throw new EdgeError$1("maximum of 2 arguments are allowed for @slot tag", "E_MAX_ARGUMENTS", {
line: parsed.loc.start.line,
col: parsed.loc.start.column,
filename: token.filename
});
isSubsetOf(parsed.expressions[1], [expressions.Identifier], () => {
unallowedExpression(`"${parser.utils.stringify(parsed.expressions[1])}" is not valid prop identifier for @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed.expressions[1]));
});
return [name.raw, parsed.expressions[1].name];
}
const componentTag = {
block: true,
seekable: true,
tagName: "component",
compile(parser, buffer, token) {
const asyncKeyword = parser.asyncMode ? "async " : "";
const awaitKeyword = parser.asyncMode ? "await " : "";
const parsed = parseJsArg(parser, token);
isSubsetOf(parsed, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME.concat(expressions.SequenceExpression), () => {
unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @component tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
const [name, props] = getComponentNameAndProps(parsed, parser, token.filename);
const slots = {};
const mainSlot = {
outputVar: "slot_main",
props: {},
buffer: buffer.create(token.filename, { outputVar: "slot_main" }),
line: -1,
filename: token.filename
};
let slotsCounter = 0;
token.children.forEach((child) => {
if (!lexerUtils.isTag(child, "slot")) {
if (mainSlot.buffer.size === 0 && child.type === "newline") return;
parser.processToken(child, mainSlot.buffer);
return;
}
const [slotName, slotProps] = getSlotNameAndProps(child, parser);
slotsCounter++;
if (!slots[slotName]) {
slots[slotName] = {
outputVar: `slot_${slotsCounter}`,
buffer: buffer.create(token.filename, { outputVar: `slot_${slotsCounter}` }),
props: slotProps,
line: -1,
filename: token.filename
};
if (slotProps) {
parser.stack.defineScope();
parser.stack.defineVariable(slotProps);
}
}
child.children.forEach((grandChildren) => {
parser.processToken(grandChildren, slots[slotName].buffer);
});
if (slotProps) parser.stack.clearScope();
});
const obj = new StringifiedObject();
obj.add("$context", "Object.assign({}, $context)");
if (!slots["main"]) if (mainSlot.buffer.size) {
mainSlot.buffer.wrap(`${asyncKeyword}function () { const $context = this.$context;`, "}");
obj.add("main", mainSlot.buffer.disableFileAndLineVariables().flush());
} else obj.add("main", "function () { return \"\" }");
Object.keys(slots).forEach((slotName) => {
if (slots[slotName].buffer.size) {
const fnCall = slots[slotName].props ? `${asyncKeyword}function (${slots[slotName].props}) { const $context = this.$context;` : `${asyncKeyword}function () { const $context = this.$context;`;
slots[slotName].buffer.wrap(fnCall, "}");
obj.add(slotName, slots[slotName].buffer.disableFileAndLineVariables().flush());
} else obj.add(slotName, "function () { return \"\" }");
});
const caller = new StringifiedObject();
caller.add("filename", "$filename");
caller.add("line", "$lineNumber");
caller.add("col", 0);
buffer.outputExpression(`${awaitKeyword}template.compileComponent(${name})(template, template.getComponentState(${props}, ${obj.flush()}, ${caller.flush()}), $context)`, token.filename, token.loc.start.line, false);
}
};
const includeIfTag = {
block: false,
seekable: true,
tagName: "includeIf",
compile(parser, buffer, token) {
const awaitKeyword = parser.asyncMode ? "await " : "";
const parsed = parseJsArg(parser, token);
isSubsetOf(parsed, [expressions.SequenceExpression], () => {
unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
if (parsed.expressions.length !== 2) throw new EdgeError$1("@includeIf expects a total of 2 arguments", "E_ARGUMENTS_MIS_MATCH", {
line: parsed.loc.start.line,
col: parsed.loc.start.column,
filename: token.filename
});
const [conditional, include] = parsed.expressions;
isNotSubsetOf(conditional, [expressions.SequenceExpression], () => {
unallowedExpression(`"${conditional.type}" is not a valid 1st argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(conditional));
});
isSubsetOf(include, ALLOWED_EXPRESSION, () => {
unallowedExpression(`"${include.type}" is not a valid 2nd argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(include));
});
buffer.writeStatement(`if (${parser.utils.stringify(conditional)}) {`, token.filename, token.loc.start.line);
buffer.outputExpression(`${awaitKeyword}${getRenderExpression(parser, include)}`, token.filename, token.loc.start.line, false);
buffer.writeStatement("}", token.filename, -1);
}
};
const pushOnceToTag = {
tagName: "pushOnceTo",
block: true,
seekable: true,
noNewLine: true,
generateId() {
return `stack_${nanoid()}`;
},
compile(parser, buffer, token) {
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
if (expressions.SequenceExpression.includes(parsed.type)) throw new EdgeError$1(`"${token.properties.jsArg}" is not a valid argument type for the "@pushOnceTo" tag`, "E_UNALLOWED_EXPRESSION", {
...parser.utils.getExpressionLoc(parsed),
filename: token.filename
});
const stackId = this.generateId();
const stackBuffer = buffer.create(token.filename, { outputVar: stackId });
for (let child of token.children) parser.processToken(child, stackBuffer);
buffer.writeStatement(stackBuffer.disableFileAndLineVariables().disableReturnStatement().disableTryCatchBlock().flush(), token.filename, token.loc.start.line);
const { line, col } = token.loc.start;
const sourceId = `${token.filename.replace(/\\|\//g, "_")}-${line}-${col}`;
buffer.writeExpression(`template.stacks.pushOnceTo(${parser.utils.stringify(parsed)}, '${sourceId}', ${stackId})`, token.filename, line);
}
};
var main_exports = /* @__PURE__ */ __export({
assign: () => assignTag,
component: () => componentTag,
debugger: () => debuggerTag,
each: () => eachTag,
else: () => elseTag,
elseif: () => elseIfTag,
eval: () => evalTag,
if: () => ifTag,
include: () => includeTag,
includeIf: () => includeIfTag,
inject: () => injectTag,
let: () => letTag,
newError: () => newErrorTag,
pushOnceTo: () => pushOnceToTag,
pushTo: () => pushToTag,
slot: () => slotTag,
stack: () => stackTag,
unless: () => unlessTag
});
var CacheManager = class {
#cacheStore = /* @__PURE__ */ new Map();
constructor(enabled) {
this.enabled = enabled;
}
has(absPath) {
return this.#cacheStore.has(absPath);
}
get(absPath) {
if (!this.enabled) return;
return this.#cacheStore.get(absPath);
}
set(absPath, payload) {
if (!this.enabled) return;
this.#cacheStore.set(absPath, payload);
}
delete(absPath) {
if (!this.enabled) return;
this.#cacheStore.delete(absPath);
}
};
const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
var Compiler = class {
#inlineVariables = [
"$filename",
"state",
"$context"
];
#templateParams = [
"template",
"state",
"$context"
];
#claimTagFn;
#loader;
#tags;
#processor;
cacheManager;
compat;
async;
constructor(loader, tags, processor, options = {
cache: true,
async: false,
compat: false
}) {
this.#processor = processor;
this.#loader = loader;
this.#tags = tags;
this.async = !!options.async;
this.compat = options.compat === true;
this.cacheManager = new CacheManager(!!options.cache);
}
#mergeSections(base, extended) {
const extendedSections = {};
const extendedSetCalls = [];
extended.forEach((node) => {
if (lexerUtils.isTag(node, "layout") || node.type === "newline" || node.type === "raw" && !node.value.trim() || node.type === "comment") return;
if (lexerUtils.isTag(node, "section")) {
extendedSections[node.properties.jsArg.trim()] = node;
return;
}
if (lexerUtils.isTag(node, "set")) {
extendedSetCalls.push(node);
return;
}
const [line, col] = lexerUtils.getLineAndColumn(node);
throw new EdgeError$1("Template extending a layout can only use \"@section\" or \"@set\" tags as top level nodes", "E_UNALLOWED_EXPRESSION", {
line,
col,
filename: node.filename
});
});
const finalNodes = base.map((node) => {
if (!lexerUtils.isTag(node, "section")) return node;
const extendedNode = extendedSections[node.properties.jsArg.trim()];
if (!extendedNode) return node;
if (extendedNode.children.length) {
if (lexerUtils.isTag(extendedNode.children[0], "super")) {
extendedNode.children.shift();
extendedNode.children = node.children.concat(extendedNode.children);
} else if (lexerUtils.isTag(extendedNode.children[1], "super")) {
extendedNode.children.shift();
extendedNode.children.shift();
extendedNode.children = node.children.concat(extendedNode.children);
}
}
return extendedNode;
});
return [].concat(extendedSetCalls).concat(finalNodes);
}
#templateContentToTokens(content, parser, absPath) {
let templateTokens = parser.tokenize(content, { filename: absPath });
if (this.compat) {
const firstToken = templateTokens[0];
if (lexerUtils.isTag(firstToken, "layout")) {
const layoutName = firstToken.properties.jsArg.replace(/'|"/g, "");
templateTokens = this.#mergeSections(this.tokenize(layoutName, parser), templateTokens);
}
}
return templateTokens;
}
#getParserFor(templatePath, localVariables) {
const parser = new Parser(this.#tags, new Stack(), {
claimTag: this.#claimTagFn,
async: this.async,
statePropertyName: "state",
escapeCallPath: ["template", "escape"],
localVariables: this.#inlineVariables,
onTag: (tag) => this.#processor.executeTag({
tag,
path: templatePath
})
});
if (localVariables) localVariables.forEach((localVariable) => parser.stack.defineVariable(localVariable));
return parser;
}
#getBufferFor(templatePath) {
return new EdgeBuffer(templatePath, {
outputVar: "out",
rethrowCallPath: ["template", "reThrow"]
});
}
#wrapToFunction(template, localVariables) {
const args = localVariables ? this.#templateParams.concat(localVariables) : this.#templateParams;
if (this.async) return new AsyncFunction(...args, template);
return new Function(...args, template);
}
claimTag(fn) {
this.#claimTagFn = fn;
return this;
}
tokenize(templatePath, parser) {
const absPath = this.#loader.makePath(templatePath);
let { template } = this.#loader.resolve(absPath);
return this.tokenizeRaw(template, absPath, parser);
}
tokenizeRaw(contents, templatePath = "eval.edge", parser) {
contents = this.#processor.executeRaw({
path: templatePath,
raw: contents
});
return this.#templateContentToTokens(contents, parser || this.#getParserFor(templatePath), templatePath);
}
compile(templatePath, localVariables) {
const absPath = this.#loader.makePath(templatePath);
let cachedResponse = localVariables ? null : this.cacheManager.get(absPath);
if (!cachedResponse) {
const parser = this.#getParserFor(absPath, localVariables);
const buffer = this.#getBufferFor(absPath);
this.tokenize(absPath, parser).forEach((token) => parser.processToken(token, buffer));
const template = this.#processor.executeCompiled({
path: absPath,
compiled: buffer.flush()
});
const compiledTemplate = this.#wrapToFunction(template, localVariables);
if (!localVariables) this.cacheManager.set(absPath, compiledTemplate);
cachedResponse = compiledTemplate;
}
return cachedResponse;
}
compileRaw(contents, templatePath = "eval.edge") {
const parser = this.#getParserFor(templatePath);
const buffer = this.#getBufferFor(templatePath);
this.tokenizeRaw(contents, templatePath, parser).forEach((token) => parser.processToken(token, buffer));
const template = this.#processor.executeCompiled({
path: templatePath,
compiled: buffer.flush()
});
return this.#wrapToFunction(template);
}
};
var import_js_stringify = /* @__PURE__ */ __toESM(require_js_stringify(), 1);
const edgeGlobals = {
nl2br: (value) => {
if (!value) return;
return String(value).replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, "$1<br>");
},
inspect: (value) => {
return htmlSafe(inspect.string.html(value));
},
truncate: (value, length = 20, options) => {
options = options || {};
return string.truncate(value, length, {
completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict,
suffix: options.suffix
});
},
excerpt: (value, length = 20, options) => {
options = options || {};
return string.excerpt(value, length, {
completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict,
suffix: options.suffix
});
},
html: {
escape,
safe: htmlSafe,
classNames: (/* @__PURE__ */ __toESM(require_classnames(), 1)).default,
attrs: (values) => {
return htmlSafe(stringifyAttributes(values));
}
},
js: { stringify: import_js_stringify.default },
camelCase: string.camelCase,
snakeCase: string.snakeCase,
dashCase: string.dashCase,
pascalCase: string.pascalCase,
capitalCase: string.capitalCase,
sentenceCase: string.sentenceCase,
dotCase: string.dotCase,
noCase: string.noCase,
titleCase: string.titleCase,
pluralize: string.pluralize,
sentence: string.sentence,
prettyMs: string.milliseconds.format,
toMs: string.milliseconds.parse,
prettyBytes: string.bytes.format,
toBytes: string.bytes.parse,
ordinal: string.ordinal
};
var Processor = class {
#handlers = /* @__PURE__ */ new Map();
executeTag(data) {
const handlers = this.#handlers.get("tag");
if (!handlers) return;
handlers.forEach((handler) => {
handler(data);
});
}
executeRaw(data) {
const handlers = this.#handlers.get("raw");
if (!handlers) return data.raw;
handlers.forEach((handler) => {
const output = handler(data);
if (output !== void 0) data.raw = output;
});
return data.raw;
}
executeCompiled(data) {
const handlers = this.#handlers.get("compiled");
if (!handlers) return data.compiled;
handlers.forEach((handler) => {
const output = handler(data);
if (output !== void 0) data.compiled = output;
});
return data.compiled;
}
executeOutput(data) {
const handlers = this.#handlers.get("output");
if (!handlers) return data.output;
handlers.forEach((handler) => {
const output = handler(data);
if (output !== void 0) data.output = output;
});
return data.output;
}
process(event, handler) {
if (!this.#handlers.has(event)) this.#handlers.set(event, /* @__PURE__ */ new Set());
this.#handlers.get(event).add(handler);
return this;
}
};
var EdgeRenderer = class EdgeRenderer {
#compiler;
#processor;
#asyncCompiler;
#locals = {};
#globals;
constructor(compiler, asyncCompiler, processor, globals) {
this.#compiler = compiler;
this.#asyncCompiler = asyncCompiler;
this.#processor = processor;
this.#globals = globals;
}
clone() {
return new EdgeRenderer(this.#compiler, this.#asyncCompiler, this.#processor, this.#globals).share(this.#locals);
}
share(data) {
lodash.merge(this.#locals, data);
return this;
}
getState() {
return {
...this.#globals,
...this.#locals
};
}
async render(templatePath, state = {}) {
return new Template(this.#asyncCompiler, this.#globals, this.#locals, this.#processor).render(templatePath, state);
}
renderSync(templatePath, state = {}) {
return new Template(this.#compiler, this.#globals, this.#locals, this.#processor).render(templatePath, state);
}
async renderRaw(contents, state = {}, templatePath) {
return new Template(this.#asyncCompiler, this.#globals, this.#locals, this.#processor).renderRaw(contents, state, templatePath);
}
renderRawSync(contents, state = {}, templatePath) {
return new Template(this.#compiler, this.#globals, this.#locals, this.#processor).renderRaw(contents, state, templatePath);
}
};
var SuperChargedComponents = class {
#edge;
#components = {};
constructor(edge$1) {
this.#edge = edge$1;
this.#claimTags();
this.#transformTags();
}
refreshComponents() {
this.#components = this.#edge.loader.listComponents().reduce((result, { components }) => {
components.forEach((component) => {
result[component.tagName] = component.componentName;
});
return result;
}, {});
}
#claimTags() {
this.#edge.compiler.claimTag((name) => {
if (this.#components[name]) return {
seekable: true,
block: true
};
return null;
});
this.#edge.asyncCompiler.claimTag((name) => {
if (this.#components[name]) return {
seekable: true,
block: true
};
return null;
});
}
#transformTags() {
this.#edge.processor.process("tag", ({ tag }) => {
const component = this.#components[tag.properties.name];
if (!component) return;
tag.properties.name = "component";
if (tag.properties.jsArg.trim() === "") tag.properties.jsArg = `'${component}'`;
else tag.properties.jsArg = `'${component}',${tag.properties.jsArg}`;
});
}
};
let superCharged;
const pluginSuperCharged = (edge$1, firstRun) => {
if (firstRun) superCharged = new SuperChargedComponents(edge$1);
superCharged.refreshComponents();
};
var Edge = class Edge {
static create(options = {}) {
return new Edge(options);
}
#bundledPlugins = [];
#plugins = [];
#renderCallbacks = [];
processor = new Processor();
compat = false;
globals = { ...edgeGlobals };
tags = {};
constructor(options = {}) {
this.configure(options);
Object.keys(main_exports).forEach((name) => {
this.registerTag(main_exports[name]);
});
this.#bundledPlugins.push({
fn: pluginSuperCharged,
executed: false,
options: { recurring: !options.cache }
});
}
configure(options) {
if (options.loader) this.loader = options.loader;
else if (!this.loader) this.loader = new Loader();
this.compiler = new Compiler(this.loader, this.tags, this.processor, {
cache: !!options.cache,
async: false
});
this.asyncCompiler = new Compiler(this.loader, this.tags, this.processor, {
cache: !!options.cache,
async: true
});
}
#executePlugins() {
this.#plugins.filter(({ options, executed }) => {
if (options && options.recurring) return true;
return !executed;
}).forEach((plugin) => {
plugin.fn(this, !plugin.executed, plugin.options);
plugin.executed = true;
});
this.#bundledPlugins.filter(({ options, executed }) => {
if (options && options.recurring) return true;
return !executed;
}).forEach((plugin) => {
plugin.fn(this, !plugin.executed, plugin.options);
plugin.executed = true;
});
}
use(pluginFn, options) {
this.#plugins.push({
fn: pluginFn,
executed: false,
options
});
return this;
}
mount(diskName, viewsDirectory) {
if (!viewsDirectory) {
viewsDirectory = diskName;
diskName = "default";
}
this.loader.mount(diskName, viewsDirectory);
return this;
}
unmount(diskName) {
this.loader.unmount(diskName);
return this;
}
global(name, value) {
this.globals[name] = value;
return this;
}
registerTag(tag) {
if (typeof tag.boot === "function") tag.boot(Template);
this.tags[tag.tagName] = tag;
return this;
}
registerTemplate(templatePath, contents) {
this.loader.register(templatePath, contents);
return this;
}
removeTemplate(templatePath) {
this.loader.remove(templatePath);
this.compiler.cacheManager.delete(templatePath);
this.asyncCompiler.cacheManager.delete(templatePath);
return this;
}
onRender(callback) {
this.#renderCallbacks.push(callback);
return this;
}
createRenderer() {
this.#executePlugins();
const renderer = new EdgeRenderer(this.compiler, this.asyncCompiler, this.processor, this.globals);
this.#renderCallbacks.forEach((callback) => callback(renderer));
return renderer;
}
render(templatePath, state) {
return this.createRenderer().render(templatePath, state);
}
renderSync(templatePath, state) {
return this.createRenderer().renderSync(templatePath, state);
}
renderRaw(contents, state, templatePath) {
return this.createRenderer().renderRaw(contents, state, templatePath);
}
renderRawSync(templatePath, state) {
return this.createRenderer().renderRawSync(templatePath, state);
}
share(data) {
return this.createRenderer().share(data);
}
};
var edge_default = Edge.create();
export { Edge, EdgeError, Template, edge_default as default, edgeGlobals };