UNPKG

edge.js

Version:
1,073 lines (1,072 loc) 40 kB
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 };