UNPKG

edge.js

Version:
1,689 lines (1,661 loc) 56.5 kB
import { StringifiedObject, Template, __export, asyncEach, each, escape, htmlSafe, isNotSubsetOf, isSubsetOf, nanoid, parseJsArg, stringifyAttributes, unallowedExpression } from "./chunk-EKKK4GME.js"; // src/loader.ts import { slash } from "@poppinss/utils"; import { fileURLToPath } from "url"; import string from "@poppinss/utils/string"; import { join, isAbsolute } from "path"; import readdirSync from "fs-readdir-recursive"; import { existsSync, readFileSync } from "fs"; var Loader = class { /** * List of mounted directories */ #mountedDirs = /* @__PURE__ */ new Map(); /** * List of pre-registered (in-memory) templates */ #preRegistered = /* @__PURE__ */ new Map(); /** * Reads the content of a template from the disk. An exception is raised * when file is missing or if `readFileSync` returns an error. */ #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; } } } /** * Returns a list of components for a given disk */ #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 }; }) : []; if (existsSync(join(diskBasePath, componentsDirName))) { files = files.concat( readdirSync(join(diskBasePath, componentsDirName)).filter((file) => file.endsWith(".edge")).map((template) => { const fileName = slash(template).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 }; }); } /** * Returns a list of templates for a given disk */ #getDiskTemplates(diskName) { const diskBasePath = this.#mountedDirs.get(diskName); let files = diskName === "default" ? Array.from(this.#preRegistered.keys()) : []; if (existsSync(diskBasePath)) { files = files.concat(readdirSync(join(diskBasePath)).filter((file) => file.endsWith(".edge"))); } return files.map((file) => { const fileName = slash(file).replace(/\.edge$/, ""); return diskName !== "default" ? `${diskName}::${fileName}` : fileName; }); } /** * Extracts the disk name and the template name from the template * path expression. * * If `diskName` is missing, it will be set to `default`. * * ``` * extractDiskAndTemplateName('users::list') * // returns ['users', 'list.edge'] * * extractDiskAndTemplateName('list') * // returns ['default', 'list.edge'] * ``` */ #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"}`]; } /** * Returns an object of mounted directories with their public * names. * * ```js * loader.mounted * // output * * { * default: '/users/virk/code/app/views', * foo: '/users/virk/code/app/foo', * } * ``` */ get mounted() { return Array.from(this.#mountedDirs).reduce( (obj, [key, value]) => { obj[key] = value; return obj; }, {} ); } /** * Returns an object of templates registered as a raw string * * ```js * loader.templates * // output * * { * 'form.label': { template: 'Template contents' } * } * ``` */ get templates() { return Array.from(this.#preRegistered).reduce( (obj, [key, value]) => { obj[key] = value; return obj; }, {} ); } /** * Mount a directory with a name for resolving views. If name is set * to `default`, then you can resolve views without prefixing the * disk name. * * ```js * loader.mount('default', join(__dirname, 'views')) * * // mount a named disk * loader.mount('admin', join(__dirname, 'admin/views')) * ``` */ mount(diskName, dirPath) { this.#mountedDirs.set(diskName, typeof dirPath === "string" ? dirPath : fileURLToPath(dirPath)); } /** * Remove the previously mounted dir. * * ```js * loader.unmount('default') * ``` */ unmount(diskName) { this.#mountedDirs.delete(diskName); } /** * Make path to a given template. The paths are resolved from the root * of the mounted directory. * * ```js * loader.makePath('welcome') // returns {diskRootPath}/welcome.edge * loader.makePath('admin::welcome') // returns {adminRootPath}/welcome.edge * loader.makePath('users.list') // returns {diskRootPath}/users/list.edge * ``` * * @throws Error if disk is not mounted and attempting to make path for it. */ 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); } /** * Resolves the template by reading its contents from the disk * * ```js * loader.resolve('welcome', true) * * // output * { * template: `<h1> Template content </h1>`, * } * ``` */ 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 in memory template for a given path. This is super helpful * when distributing components. * * ```js * loader.register('welcome', { * template: '<h1> Template content </h1>', * Presenter: class Presenter { * constructor (state) { * this.state = state * } * } * }) * ``` * * @throws Error if template content is empty. */ 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 registered template */ remove(templatePath) { this.#preRegistered.delete(templatePath); } /** * Returns a list of components from all the disks. We assume * the components are stored within the components directory. * * Also, we treat all in-memory templates as components. * * The return path is same the path you will pass to the `@component` * tag. */ listComponents() { const diskNames = [...this.#mountedDirs.keys()]; return diskNames.map((diskName) => { return { diskName, components: this.#getDiskComponents(diskName) }; }); } /** * Returns a list of templates from all the disks and in-memory * templates as well */ listTemplates() { const diskNames = [...this.#mountedDirs.keys()]; return diskNames.map((diskName) => { return { diskName, templates: this.#getDiskTemplates(diskName) }; }); } }; // src/tags/main.ts var main_exports = {}; __export(main_exports, { 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 }); // src/tags/if.ts import { expressions } from "edge-parser"; var ifTag = { block: true, seekable: true, tagName: "if", /** * Compiles the if block node to a Javascript if statement */ 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); } }; // src/tags/let.ts import { expressions as expressions2 } from "edge-parser"; import lodash from "@poppinss/utils/lodash"; var letTag = { block: false, seekable: true, tagName: "let", noNewLine: true, /** * Compiles else block node to Javascript else statement */ 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", expressions2.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); }, /** * Add methods to the template for running the loop */ boot(template) { template.macro("setValue", lodash.set); } }; // src/tags/each.ts import lodash2 from "@poppinss/utils/lodash"; import * as lexerUtils from "edge-lexer/utils"; import { expressions as expressions3 } from "edge-parser"; function getLoopList(rhsExpression, parser, filename) { return parser.utils.stringify(parser.utils.transformAst(rhsExpression, filename, parser)); } function getLoopItemAndIndex(lhsExpression, parser, filename) { isSubsetOf(lhsExpression, [expressions3.SequenceExpression, expressions3.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], [expressions3.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], [expressions3.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]; } var eachTag = { block: true, seekable: true, tagName: "each", /** * Compile the template */ 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, [expressions3.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); } }, /** * Add methods to the template for running the loop */ boot(template) { template.macro("loopAsync", asyncEach); template.macro("loop", each); template.macro("size", lodash2.size); } }; // src/tags/slot.ts import { EdgeError } from "edge-error"; var slotTag = { block: true, seekable: true, tagName: "slot", noNewLine: true, compile(_, __, token) { throw new EdgeError( "@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 } ); } }; // src/tags/else.ts var elseTag = { block: false, seekable: false, tagName: "else", /** * Compiles else block node to Javascript else statement */ compile(_, buffer, token) { buffer.writeStatement("} else {", token.filename, -1); } }; // src/tags/eval.ts var evalTag = { block: false, seekable: true, tagName: "eval", noNewLine: true, /** * Compiles the tag AST */ compile(parser, buffer, token) { const parsed = parseJsArg(parser, token); buffer.writeExpression(parser.utils.stringify(parsed), token.filename, token.loc.start.line); } }; // src/tags/stack.ts import { EdgeError as EdgeError2 } from "edge-error"; import { expressions as expressions4 } from "edge-parser"; var 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 (expressions4.SequenceExpression.includes(parsed.type)) { throw new EdgeError2( `"${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 ); } }; // src/tags/assign.ts import { expressions as expressions5 } from "edge-parser"; import lodash3 from "@poppinss/utils/lodash"; var assignTag = { block: false, seekable: true, tagName: "assign", noNewLine: true, /** * Compiles else block node to Javascript else statement */ compile(parser, buffer, token) { const parsed = parseJsArg(parser, token); isSubsetOf(parsed, [expressions5.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); }, /** * Add methods to the template for running the loop */ boot(template) { template.macro("setValue", lodash3.set); } }; // src/tags/inject.ts import { expressions as expressions6 } from "edge-parser"; var 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, [expressions6.ObjectExpression, expressions6.Identifier, expressions6.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 ); } }; // src/tags/unless.ts import { expressions as expressions7 } from "edge-parser"; var unlessTag = { block: true, seekable: true, tagName: "unless", /** * Compiles the if block node to a Javascript if statement */ compile(parser, buffer, token) { const parsed = parseJsArg(parser, token); isNotSubsetOf(parsed, [expressions7.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); } }; // src/tags/else_if.ts import { expressions as expressions8 } from "edge-parser"; var elseIfTag = { block: false, seekable: true, tagName: "elseif", /** * Compiles the else if block node to a Javascript if statement */ compile(parser, buffer, token) { const parsed = parseJsArg(parser, token); isNotSubsetOf(parsed, [expressions8.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 ); } }; // src/tags/push_to.ts import { EdgeError as EdgeError3 } from "edge-error"; import { expressions as expressions9 } from "edge-parser"; var 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 (expressions9.SequenceExpression.includes(parsed.type)) { throw new EdgeError3( `"${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 ); } }; // src/tags/include.ts import { expressions as expressions10 } from "edge-parser"; var ALLOWED_EXPRESSION = [ expressions10.Literal, expressions10.Identifier, expressions10.CallExpression, expressions10.TemplateLiteral, expressions10.MemberExpression, expressions10.LogicalExpression, expressions10.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(",")})`; } var includeTag = { block: false, seekable: true, tagName: "include", /** * Compiles else block node to Javascript else statement */ 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 ); } }; // src/tags/debugger.ts var debuggerTag = { block: false, seekable: false, tagName: "debugger", noNewLine: true, /** * Compiles `@debugger` tags */ compile(_, buffer, token) { buffer.writeExpression("debugger", token.filename, token.loc.start.line); } }; // src/tags/new_error.ts import { expressions as expressions11 } from "edge-parser"; var 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 === expressions11.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 ); } }; // src/tags/component.ts import { EdgeError as EdgeError4 } from "edge-error"; import * as lexerUtils2 from "edge-lexer/utils"; import { expressions as expressions12 } from "edge-parser"; var ALLOWED_EXPRESSION_FOR_COMPONENT_NAME = [ expressions12.Identifier, expressions12.Literal, expressions12.LogicalExpression, expressions12.MemberExpression, expressions12.ConditionalExpression, expressions12.CallExpression, expressions12.TemplateLiteral ]; function getComponentNameAndProps(expression, parser, filename) { let name; if (expression.type === expressions12.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 === expressions12.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, [expressions12.Literal, expressions12.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 === expressions12.SequenceExpression) { name = parsed.expressions[0]; } else { name = parsed; } isSubsetOf(name, [expressions12.Literal], () => { unallowedExpression( "slot name must be a valid string literal", token.filename, parser.utils.getExpressionLoc(name) ); }); if (parsed.type === expressions12.Literal) { return [name.raw, null]; } if (parsed.expressions.length > 2) { throw new EdgeError4("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], [expressions12.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]; } var 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(expressions12.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 (!lexerUtils2.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 ); } }; // src/tags/include_if.ts import { EdgeError as EdgeError5 } from "edge-error"; import { expressions as expressions13 } from "edge-parser"; var includeIfTag = { block: false, seekable: true, tagName: "includeIf", /** * Compiles else block node to Javascript else statement */ compile(parser, buffer, token) { const awaitKeyword = parser.asyncMode ? "await " : ""; const parsed = parseJsArg(parser, token); isSubsetOf(parsed, [expressions13.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 EdgeError5("@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, [expressions13.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); } }; // src/tags/push_once_to.ts import { EdgeError as EdgeError6 } from "edge-error"; import { expressions as expressions14 } from "edge-parser"; var 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 (expressions14.SequenceExpression.includes(parsed.type)) { throw new EdgeError6( `"${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 normalizedFileName = token.filename.replace(/\\|\//g, "_"); const sourceId = `${normalizedFileName}-${line}-${col}`; buffer.writeExpression( `template.stacks.pushOnceTo(${parser.utils.stringify(parsed)}, '${sourceId}', ${stackId})`, token.filename, line ); } }; // src/compiler.ts import { EdgeError as EdgeError7 } from "edge-error"; import * as lexerUtils3 from "edge-lexer/utils"; import { Parser as Parser4, EdgeBuffer as EdgeBuffer2, Stack } from "edge-parser"; // src/cache_manager.ts var CacheManager = class { constructor(enabled) { this.enabled = enabled; } #cacheStore = /* @__PURE__ */ new Map(); /** * Returns a boolean to tell if a template has already been cached * or not. */ has(absPath) { return this.#cacheStore.has(absPath); } /** * Returns the template from the cache. If caching is disabled, * then it will return undefined. */ get(absPath) { if (!this.enabled) { return; } return this.#cacheStore.get(absPath); } /** * Set's the template path and the payload to the cache. If * cache is disabled, then this function results in a noop. */ set(absPath, payload) { if (!this.enabled) { return; } this.#cacheStore.set(absPath, payload); } /** * Delete template from the compiled cache */ delete(absPath) { if (!this.enabled) { return; } this.#cacheStore.delete(absPath); } }; // src/compiler.ts var AsyncFunction = Object.getPrototypeOf(async function() { }).constructor; var Compiler = class { /** * The variables someone can access inside templates. All other * variables will get prefixed with `state` property name */ #inlineVariables = ["$filename", "state", "$context"]; /** * A fixed set of params to pass to the template every time. */ #templateParams = ["template", "state", "$context"]; #claimTagFn; #loader; #tags; #processor; /** * Caches compiled templates */ cacheManager; /** * A boolean to know if compat mode is enabled */ compat; /** * Know if compiler is compiling in the async mode or not */ 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); } /** * Merges sections of base template and parent template tokens */ #mergeSections(base, extended) { const extendedSections = {}; const extendedSetCalls = []; extended.forEach((node) => { if (lexerUtils3.isTag(node, "layout") || node.type === "newline" || node.type === "raw" && !node.value.trim() || node.type === "comment") { return; } if (lexerUtils3.isTag(node, "section")) { extendedSections[node.properties.jsArg.trim()] = node; return; } if (lexerUtils3.isTag(node, "set")) { extendedSetCalls.push(node); return; } const [line, col] = lexerUtils3.getLineAndColumn(node); throw new EdgeError7( '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 (!lexerUtils3.isTag(node, "section")) { return node; } const sectionName = node.properties.jsArg.trim(); const extendedNode = extendedSections[sectionName]; if (!extendedNode) { return node; } if (extendedNode.children.length) { if (lexerUtils3.isTag(extendedNode.children[0], "super")) { extendedNode.children.shift(); extendedNode.children = node.children.concat(extendedNode.children); } else if (lexerUtils3.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); } /** * Generates an array of lexer tokens from the template string. Further tokens * are checked for layouts and if layouts are used, their sections will be * merged together. */ #templateContentToTokens(content, parser, absPath) { let templateTokens = parser.tokenize(content, { filename: absPath }); if (this.compat) { const firstToken = templateTokens[0]; if (lexerUtils3.isTag(firstToken, "layout")) { const layoutName = firstToken.properties.jsArg.replace(/'|"/g, ""); templateTokens = this.#mergeSections(this.tokenize(layoutName, parser), templateTokens); } } return templateTokens; } /** * Returns the parser instance for a given template */ #getParserFor(templatePath, localVariables) { const parser = new Parser4(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; } /** * Returns the parser instance for a given template */ #getBufferFor(templatePath) { return new EdgeBuffer2(templatePath, { outputVar: "out", rethrowCallPath: ["template", "reThrow"] }); } /** * Wraps template output to a function along with local variables */ #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); } /** * Define a function to claim tags */ claimTag(fn) { this.#claimTagFn = fn; return this; } /** * Converts the template content to an array of lexer tokens. The method is * same as the `parser.tokenize`, but it also handles layouts natively. * * ``` * compiler.tokenize('<template-path>') * ``` */ tokenize(templatePath, parser) { const absPath = this.#loader.makePath(templatePath); let { template } = this.#loader.resolve(absPath); return this.tokenizeRaw(template, absPath, parser); } /** * Tokenize a raw template */ tokenizeRaw(contents, templatePath = "eval.edge", parser) { contents = this.#processor.executeRaw({ path: templatePath, raw: contents }); return this.#templateContentToTokens( contents, parser || this.#getParserFor(templatePath), templatePath ); } /** * Compiles the template contents to string. The output is same as the `edge-parser`, * it's just that the compiler uses the loader to load the templates and also * handles layouts. * * ```js * compiler.compile('welcome') * ``` */ 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); const templateTokens = this.tokenize(absPath, parser); templateTokens.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; } /** * Compiles the template contents to string. The output is same as the `edge-parser`, * it's just that the compiler uses the loader to load the templates and also * handles layouts. * * ```js * compiler.compileRaw('welcome') * ``` */ compileRaw(contents, templatePath = "eval.edge") { const parser = this.#getParserFor(templatePath); const buffer = this.#getBufferFor(templatePath); const templateTokens = this.tokenizeRaw(contents, templatePath, parser); templateTokens.forEach((token) => parser.processToken(token, buffer)); const template = this.#processor.executeCompiled({ path: templatePath, compiled: buffer.flush() }); return this.#wrapToFunction(template); } }; // src/edge/globals.ts import stringify from "js-stringify"; import classNames from "classnames"; import inspect from "@poppinss/inspect"; import string2 from "@poppinss/utils/string"; var edgeGlobals = { /** * Converts new lines to break */ nl2br: (value) => { if (!value) { return; } return String(value).replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, "$1<br>"); }, /** * Inspect state */ inspect: (value) => { return htmlSafe(inspect.string.html(value)); }, /** * Truncate a sentence */ truncate: (value, length = 20, options) => { options = options || {}; return string2.truncate(value, length, { completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict, suffix: options.suffix }); }, /** * Generate an excerpt */ excerpt: (value, length = 20, options) => { options = options || {}; return string2.excerpt(value, length, { completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict, suffix: options.suffix }); }, /** * Helpers related to HTML */ html: { escape, safe: htmlSafe, classNames, attrs: (values) => { return htmlSafe(stringifyAttributes(values)); } }, /** * Helpers related to JavaScript */ js: { stringify }, camelCase: string2.camelCase, snakeCase: string2.snakeCase, dashCase: string2.dashCase, pascalCase: string2.pascalCase, capitalCase: string2.capitalCase, sentenceCase: string2.sentenceCase, dotCase: string2.dotCase, noCase: string2.noCase, titleCase: string2.titleCase, pluralize: string2.pluralize, sentence: string2.sentence, prettyMs: string2.milliseconds.format, toMs: string2.milliseconds.parse, prettyBytes: string2.bytes.format, toBytes: string2.bytes.parse, ordinal: string2.ordinal }; // src/processor.ts var Processor = class { #handlers = /* @__PURE__ */ new Map(); /** * Execute tag handler */ executeTag(data) { const handlers = this.#handlers.get("tag"); if (!handlers) { return; } handlers.forEach((handler) => { handler(data); }); } /** * Execute raw handlers */ 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; } /** * Execute compiled handlers */ 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; } /** * Execute output handlers */ 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; } }; // src/edge/renderer.ts import lodash4 from "@poppinss/utils/lodash"; var EdgeRenderer = class _EdgeRenderer { #compiler; #processor; #asyncCompiler; /** * Global state */ #locals = {}; #globals; constructor(compiler, asyncCompiler, processor, globals) { this.#compiler = compiler; this.#asyncCompiler = asyncCompiler; this.#processor = processor; this.#globals = globals; } /** * Clone renderer instance with shared data */ clone() { const renderer = new _EdgeRenderer( this.#compiler, this.#asyncCompiler, this.#processor, this.#globals ); return renderer.share(this.#locals); } /** * Share local variables with the template. They will overwrite the * globals */ share(data) { lodash4.merge(this.#locals, data); return this; } /** * Retrieves the local and global variables, used for testing things that * share variables */ getState() { return { ...this.#globals, ...this.#locals }; } /** * Render the template */ async render(templatePath, state = {}) { return new Template(this.#asyncCompiler, this.#globals, this.#locals, this.#processor).render( templatePath, state ); } /** * Render the template */ renderSync(templatePath, state = {}) { return new Template( this.#compiler, this.#globals, this.#locals, this.#processor ).render(templatePath, state); } /** * Render the template from a raw string */ async renderRaw(contents, state = {}, templatePath) { return new Template( this.#asyncCompiler, this.#globals, this.#locals, this.#processor ).renderRaw(contents, state, templatePath); } /** * Render the template from a raw string */ renderRawSync(contents, state = {}, templatePath) { return new Template(this.#compiler, this.#globals, this.#locals, this.#processor).renderRaw( contents, state, templatePath ); } }; // src/plugins/supercharged.ts var SuperChargedComponents = class { #edge; #components = {}; constructor(edge2) { this.#edge = edge2; this.#claimTags(); this.#transformTags(); } /** * Refreshes the list of components */ refreshComponents() { this.#components = this.#edge.loader.listComponents().reduce((result, { components }) => { components.forEach((component) => { result[component.tagName] = component.componentName; }); return result; }, {}); } /** * Registers hook to claim self processing of tags that * are references to components */ #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; }); } /** * Transforms tags to component calls */ #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}`; } }); } }; var superCharged; var pluginSuperCharged = (edge2, firstRun) => { if (firstRun) { superCharged = new SuperChargedComponents(edge2); } superCharged.refreshComponents(); }; // src/edge/main.ts var Edge = class _Edge { /** * Create an