Socket
Socket
Sign inDemoInstall

edge.js

Package Overview
Dependencies
Maintainers
2
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

edge.js - npm Package Compare versions

Comparing version 6.0.0-0 to 6.0.0-1

340

build/index.d.ts

@@ -1,9 +0,339 @@

import { Edge } from './src/edge/index.js';
import { safeValue } from './src/template/index.js';
import { GLOBALS } from './src/edge/globals/index.js';
import { Parser } from 'edge-parser';
import { TagToken, Token } from 'edge-lexer/types';
import { ProcessorContract, TemplateContract, CacheManagerContract, LoaderTemplate, CompilerContract, LoaderContract, TagsContract, CompilerOptions, EdgeContract, TagContract, EdgeOptions, EdgeRendererContract } from './src/types.js';
import { ClaimTagFn } from 'edge-parser/types';
/**
* Exposes the API to register a set of handlers to process the
* templates output at different stages
*/
declare class Processor implements ProcessorContract {
#private;
/**
* Execute tag handler
*/
executeTag(data: {
tag: TagToken;
path: string;
}): void;
/**
* Execute raw handlers
*/
executeRaw(data: {
raw: string;
path: string;
}): string;
/**
* Execute compiled handlers
*/
executeCompiled(data: {
compiled: string;
path: string;
}): string;
/**
* Execute output handlers
*/
executeOutput(data: {
output: string;
template: TemplateContract;
state: Record<string, any>;
}): string;
/**
* Define a processor function
*/
process(event: 'raw', handler: (data: {
raw: string;
path: string;
}) => string | void): this;
process(event: 'tag', handler: (data: {
tag: TagToken;
path: string;
}) => void): this;
process(event: 'compiled', handler: (data: {
compiled: string;
path: string;
}) => string | void): this;
process(event: 'output', handler: (data: {
output: string;
template: TemplateContract;
state: Record<string, any>;
}) => string | void): this;
}
/**
* In memory cache manager to cache pre-compiled templates.
*/
declare class CacheManager implements CacheManagerContract {
#private;
enabled: boolean;
constructor(enabled: boolean);
/**
* Returns a boolean to tell if a template has already been cached
* or not.
*/
has(absPath: string): boolean;
/**
* Returns the template from the cache. If caching is disabled,
* then it will return undefined.
*/
get(absPath: string): undefined | LoaderTemplate;
/**
* Set's the template path and the payload to the cache. If
* cache is disabled, then this function results in a noop.
*/
set(absPath: string, payload: LoaderTemplate): void;
/**
* Delete template from the compiled cache
*/
delete(absPath: string): void;
}
/**
* Compiler is to used to compile templates using the `edge-parser`. Along with that
* it natively merges the contents of a layout with a parent template.
*/
declare class Compiler implements CompilerContract {
#private;
/**
* Caches compiled templates
*/
cacheManager: CacheManager;
/**
* Know if compiler is compiling for the async mode or not
*/
async: boolean;
constructor(loader: LoaderContract, tags: TagsContract, processor: Processor, options?: CompilerOptions);
/**
* Define a function to claim tags
*/
claimTag(fn: ClaimTagFn): 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: string, parser?: Parser): Token[];
/**
* Tokenize a raw template
*/
tokenizeRaw(contents: string, templatePath?: string, parser?: Parser): Token[];
/**
* 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: string, localVariables?: string[], skipCache?: boolean): LoaderTemplate;
/**
* 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')
* ```
*/
compileRaw(contents: string, templatePath?: string): LoaderTemplate;
}
/**
* Exposes the API to render templates, register custom tags and globals
*/
declare class Edge implements EdgeContract {
#private;
/**
* Reference to the registered processor handlers
*/
processor: Processor;
/**
* Globals are shared with all rendered templates
*/
GLOBALS: {
[key: string]: any;
};
/**
* List of registered tags. Adding new tags will only impact
* this list
*/
tags: {
[name: string]: TagContract;
};
/**
* The loader to load templates. A loader can read and return
* templates from anywhere. The default loader reads files
* from the disk
*/
loader: LoaderContract;
/**
* The underlying compiler in use
*/
compiler: Compiler;
/**
* The underlying compiler in use
*/
asyncCompiler: Compiler;
constructor(options?: EdgeOptions);
/**
* Execute plugins. Since plugins are meant to be called only
* once we empty out the array after first call
*/
private executePlugins;
/**
* Register a plugin. Plugin functions are called once just before
* an attempt to render a view is made.
*/
use<T extends any>(pluginFn: (edge: this, firstRun: boolean, options: T) => void, options?: T): this;
/**
* Mount named directory to use views. Later you can reference
* the views from a named disk as follows.
*
* ```
* edge.mount('admin', join(__dirname, 'admin'))
*
* edge.render('admin::filename')
* ```
*/
mount(diskName: string, dirPath?: string): this;
/**
* Un Mount a disk from the loader.
*
* ```js
* edge.unmount('admin')
* ```
*/
unmount(diskName: string): this;
/**
* Add a new global to the edge globals. The globals are available
* to all the templates.
*
* ```js
* edge.global('username', 'virk')
* edge.global('time', () => new Date().getTime())
* ```
*/
global(name: string, value: any): this;
/**
* Add a new tag to the tags list.
*
* ```ts
* edge.registerTag('svg', {
* block: false,
* seekable: true,
*
* compile (parser, buffer, token) {
* const fileName = token.properties.jsArg.trim()
* buffer.writeRaw(fs.readFileSync(__dirname, 'assets', `${fileName}.svg`), 'utf-8')
* }
* })
* ```
*/
registerTag(tag: TagContract): this;
/**
* Register an in-memory template.
*
* ```ts
* edge.registerTemplate('button', {
* template: `<button class="{{ this.type || 'primary' }}">
* @!yield($slots.main())
* </button>`,
* })
* ```
*
* Later you can use this template
*
* ```edge
* @component('button', type = 'primary')
* Get started
* @endcomponent
* ```
*/
registerTemplate(templatePath: string, contents: LoaderTemplate): this;
/**
* Remove the template registered using the "registerTemplate" method
*/
removeTemplate(templatePath: string): this;
/**
* Get access to the underlying template renderer. Each render call
* to edge results in creating an isolated renderer instance.
*/
onRender(callback: (renderer: EdgeRendererContract) => void): this;
/**
* Returns a new instance of edge. The instance
* can be used to define locals.
*/
getRenderer(): EdgeRendererContract;
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
render(templatePath: string, state?: any): Promise<string>;
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderSync(templatePath: string, state?: any): string;
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRaw(contents: string, state?: any, templatePath?: string): Promise<string>;
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRawSync(templatePath: string, state?: any): string;
/**
* Share locals with the current view context.
*
* ```js
* const view = edge.getRenderer()
*
* // local state for the current render
* view.share({ foo: 'bar' })
*
* view.render('welcome')
* ```
*/
share(data: any): EdgeRendererContract;
}
/**
* An instance of this class passed to the escape
* method ensures that underlying value is never
* escaped.
*/
declare class SafeValue {
value: any;
constructor(value: any);
}
/**
* Mark value as safe and not to be escaped
*/
declare function safeValue(value: string): SafeValue;
declare const GLOBALS: Record<string, Function>;
/**
* Default export
*/
declare const edge: Edge;
export default edge;
export { Edge, safeValue, GLOBALS };
export { Edge, GLOBALS, edge as default, safeValue };

@@ -1,18 +0,2038 @@

/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Edge } from './src/edge/index.js';
import { safeValue } from './src/template/index.js';
import { GLOBALS } from './src/edge/globals/index.js';
/**
* Default export
*/
const edge = new Edge();
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/tags/index.ts
var tags_exports = {};
__export(tags_exports, {
component: () => componentTag,
debugger: () => debuggerTag,
each: () => eachTag,
else: () => elseTag,
elseif: () => elseIfTag,
if: () => ifTag,
include: () => includeTag,
includeIf: () => includeIfTag,
inject: () => injectTag,
layout: () => layoutTag,
newError: () => newErrorTag,
section: () => sectionTag,
set: () => setTag,
slot: () => slotTag,
super: () => superTag,
unless: () => unlessTag
});
// src/tags/if.ts
import { expressions } from "edge-parser";
// src/utils/index.ts
import { EdgeError } from "edge-error";
function unallowedExpression(message, filename, loc) {
throw new EdgeError(message, "E_UNALLOWED_EXPRESSION", {
line: loc.line,
col: loc.col,
filename
});
}
function isSubsetOf(expression, expressions11, errorCallback) {
if (!expressions11.includes(expression.type)) {
errorCallback();
}
}
function isNotSubsetOf(expression, expressions11, errorCallback) {
if (expressions11.includes(expression.type)) {
errorCallback();
}
}
function parseJsArg(parser, token) {
return parser.utils.transformAst(
parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename),
token.filename,
parser
);
}
function each(collection, iteratee) {
if (Array.isArray(collection)) {
for (let [key, value] of collection.entries()) {
iteratee(value, key);
}
return;
}
if (typeof collection === "string") {
let index = 0;
for (let value of collection) {
iteratee(value, index++);
}
return;
}
if (collection && typeof collection === "object") {
for (let [key, value] of Object.entries(collection)) {
iteratee(value, key);
}
}
}
async function asyncEach(collection, iteratee) {
if (Array.isArray(collection)) {
for (let [key, value] of collection.entries()) {
await iteratee(value, key);
}
return;
}
if (typeof collection === "string") {
let index = 0;
for (let value of collection) {
await iteratee(value, index++);
}
return;
}
if (collection && typeof collection === "object") {
for (let [key, value] of Object.entries(collection)) {
await iteratee(value, key);
}
}
}
// src/tags/if.ts
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/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/else_if.ts
import { expressions as expressions2 } 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, [expressions2.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/include.ts
import { expressions as expressions3 } from "edge-parser";
var ALLOWED_EXPRESSION = [
expressions3.Identifier,
expressions3.Literal,
expressions3.LogicalExpression,
expressions3.MemberExpression,
expressions3.ConditionalExpression,
expressions3.CallExpression,
expressions3.TemplateLiteral
];
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/include_if.ts
import { EdgeError as EdgeError2 } from "edge-error";
import { expressions as expressions4 } 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, [expressions4.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 EdgeError2("@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, [expressions4.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/each.ts
import lodash from "@poppinss/utils/lodash";
import * as lexerUtils from "edge-lexer/utils";
import { expressions as expressions5 } 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, [expressions5.SequenceExpression, expressions5.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], [expressions5.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], [expressions5.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, [expressions5.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", lodash.size);
}
};
// src/tags/component.ts
import { EdgeError as EdgeError3 } from "edge-error";
import * as lexerUtils2 from "edge-lexer/utils";
import { expressions as expressions6 } from "edge-parser";
// src/stringified_object/index.ts
var StringifiedObject = class {
#obj = "";
addSpread(key) {
this.#obj += this.#obj.length ? `, ${key}` : `${key}`;
}
/**
* Add key/value pair to the object.
*
* ```js
* stringifiedObject.add('username', `'virk'`)
* ```
*/
add(key, value, isComputed = false) {
key = isComputed ? `[${key}]` : key;
this.#obj += this.#obj.length ? `, ${key}: ${value}` : `${key}: ${value}`;
}
/**
* Returns the object alike string back.
*
* ```js
* stringifiedObject.flush()
*
* // returns
* `{ username: 'virk' }`
* ```
*/
flush() {
const obj = `{ ${this.#obj} }`;
this.#obj = "";
return obj;
}
/**
* Parses an array of expressions to form an object. Each expression inside the array must
* be `ObjectExpression` or an `AssignmentExpression`, otherwise it will be ignored.
*
* ```js
* (title = 'hello')
* // returns { title: 'hello' }
*
* ({ title: 'hello' })
* // returns { title: 'hello' }
*
* ({ title: 'hello' }, username = 'virk')
* // returns { title: 'hello', username: 'virk' }
* ```
*/
static fromAcornExpressions(expressions11, parser) {
if (!Array.isArray(expressions11)) {
throw new Error('"fromAcornExpressions" expects an array of acorn ast expressions');
}
const objectifyString = new this();
expressions11.forEach((arg) => {
if (arg.type === "ObjectExpression") {
arg.properties.forEach((prop) => {
if (prop.type === "SpreadElement") {
objectifyString.addSpread(parser.utils.stringify(prop));
} else {
const key = parser.utils.stringify(prop.key);
const value = parser.utils.stringify(prop.value);
objectifyString.add(key, value, prop.computed);
}
});
}
if (arg.type === "AssignmentExpression") {
objectifyString.add(arg.left.name, parser.utils.stringify(arg.right));
}
});
return objectifyString.flush();
}
};
// src/tags/component.ts
var ALLOWED_EXPRESSION_FOR_COMPONENT_NAME = [
expressions6.Identifier,
expressions6.Literal,
expressions6.LogicalExpression,
expressions6.MemberExpression,
expressions6.ConditionalExpression,
expressions6.CallExpression,
expressions6.TemplateLiteral
];
function getComponentNameAndProps(expression, parser, filename) {
let name;
if (expression.type === expressions6.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 === expressions6.SequenceExpression) {
const firstSequenceExpression = expression.expressions[0];
if (firstSequenceExpression && [expressions6.ObjectExpression, expressions6.AssignmentExpression].includes(
firstSequenceExpression.type
)) {
return [
parser.utils.stringify(name),
StringifiedObject.fromAcornExpressions([firstSequenceExpression], parser)
];
}
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, [expressions6.Literal, expressions6.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 === expressions6.SequenceExpression) {
name = parsed.expressions[0];
} else {
name = parsed;
}
isSubsetOf(name, [expressions6.Literal], () => {
unallowedExpression(
"slot name must be a valid string literal",
token.filename,
parser.utils.getExpressionLoc(name)
);
});
if (parsed.type === expressions6.Literal) {
return [name.raw, null];
}
if (parsed.expressions.length > 2) {
throw new EdgeError3("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], [expressions6.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(expressions6.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/slot.ts
import { EdgeError as EdgeError4 } from "edge-error";
var slotTag = {
block: true,
seekable: true,
tagName: "slot",
noNewLine: true,
compile(_, __, token) {
throw new EdgeError4(
"@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/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/set.ts
import { EdgeError as EdgeError5 } from "edge-error";
import { expressions as expressions7 } from "edge-parser";
import lodash2 from "@poppinss/utils/lodash";
var setTag = {
block: false,
seekable: true,
tagName: "set",
noNewLine: true,
/**
* Compiles else block node to Javascript else statement
*/
compile(parser, buffer, token) {
const parsed = parseJsArg(parser, token);
isSubsetOf(parsed, [expressions7.SequenceExpression], () => {
throw unallowedExpression(
`"${token.properties.jsArg}" is not a valid key-value pair for the @slot tag`,
token.filename,
parser.utils.getExpressionLoc(parsed)
);
});
if (parsed.expressions.length < 2 || parsed.expressions.length > 3) {
throw new EdgeError5(
"@set tag accepts a minimum of 2 or maximum or 3 arguments",
"E_INVALID_ARGUMENTS_COUNT",
{
line: parsed.loc.start.line,
col: parsed.loc.start.column,
filename: token.filename
}
);
}
let collection;
let key;
let value;
if (parsed.expressions.length === 3) {
collection = parsed.expressions[0];
key = parsed.expressions[1];
value = parsed.expressions[2];
} else {
key = parsed.expressions[0];
value = parsed.expressions[1];
}
isSubsetOf(key, [expressions7.Literal], () => {
throw unallowedExpression(
`The ${collection ? "second" : "first"} argument for @set tag must be a string literal`,
token.filename,
parser.utils.getExpressionLoc(key)
);
});
if (collection) {
buffer.writeExpression(
`template.setValue(${parser.utils.stringify(collection)}, '${key.value}', ${parser.utils.stringify(value)})`,
token.filename,
token.loc.start.line
);
return;
}
const expression = parser.stack.has(key.value) ? `${key.value} = ${parser.utils.stringify(value)}` : `let ${key.value} = ${parser.utils.stringify(value)}`;
buffer.writeExpression(expression, token.filename, token.loc.start.line);
parser.stack.defineVariable(key.value);
},
/**
* Add methods to the template for running the loop
*/
boot(template) {
template.macro("setValue", lodash2.set);
}
};
// src/tags/unless.ts
import { expressions as expressions8 } 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, [expressions8.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/layout.ts
var layoutTag = {
block: false,
seekable: true,
tagName: "layout",
noNewLine: true,
compile() {
}
};
// src/tags/section.ts
var sectionTag = {
block: true,
seekable: true,
tagName: "section",
compile(parser, buffer, token) {
token.children.forEach((child) => parser.processToken(child, buffer));
}
};
// src/tags/super.ts
import { EdgeError as EdgeError6 } from "edge-error";
var superTag = {
block: false,
seekable: false,
tagName: "super",
compile(_, __, token) {
throw new EdgeError6(
"@super tag must appear as top level tag inside the @section tag",
"E_ORPHAN_SUPER_TAG",
{
line: token.loc.start.line,
col: token.loc.start.col,
filename: token.filename
}
);
}
};
// src/tags/inject.ts
import { expressions as expressions9 } 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, [expressions9.ObjectExpression, expressions9.Identifier], () => {
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/new_error.ts
import { expressions as expressions10 } 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 === expressions10.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/loader/index.ts
import { readFileSync } from "node:fs";
import { join, isAbsolute } from "node:path";
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;
}
}
}
/**
* 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");
if (template.indexOf(".") > -1) {
process.emitWarning(
"DeprecationWarning",
'edge: dot "." based path seperators are depreciated. We recommend using "/" instead'
);
template = template.replace(/\./g, "/");
}
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: '/users/virk/code/app/form/label' }
* }
* ```
*/
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, 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);
}
};
// src/compiler/index.ts
import { EdgeError as EdgeError7 } from "edge-error";
import { Parser as Parser4, EdgeBuffer as EdgeBuffer2, Stack } from "edge-parser";
import * as lexerUtils3 from "edge-lexer/utils";
// src/cache_manager/index.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/index.ts
var Compiler = class {
#claimTagFn;
#loader;
#tags;
#processor;
/**
* Caches compiled templates
*/
cacheManager;
/**
* Know if compiler is compiling for the async mode or not
*/
async;
constructor(loader, tags, processor, options = {
cache: true,
async: false
}) {
this.#processor = processor;
this.#loader = loader;
this.#tags = tags;
this.async = !!options.async;
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 });
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: ["$filename", "state", "$context"],
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"]
});
}
/**
* 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, skipCache = false) {
const absPath = this.#loader.makePath(templatePath);
let cachedResponse = skipCache ? 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 template2 = buffer.flush();
if (!skipCache) {
this.cacheManager.set(absPath, { template: template2 });
}
cachedResponse = { template: template2 };
}
const template = this.#processor.executeCompiled({
path: absPath,
compiled: cachedResponse.template
});
return { template };
}
/**
* 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')
* ```
*/
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 { template };
}
};
// src/template/index.ts
import Macroable from "@poppinss/macroable";
import { EdgeError as EdgeError8 } from "edge-error";
import lodash4 from "@poppinss/utils/lodash";
import he from "he";
// src/component/props.ts
import lodash3 from "@poppinss/utils/lodash";
import stringifyAttributes from "stringify-attributes";
var Props = class {
constructor(props) {
this[Symbol.for("options")] = { props };
Object.assign(this, props);
}
/**
* Merges the className attribute with the class attribute
*/
#mergeClassAttributes(props) {
if (props.className) {
if (!props.class) {
props.class = [];
}
if (!Array.isArray(props.class)) {
props.class = [props.class];
}
props.class = props.class.concat(props.className);
props.className = false;
}
return props;
}
/**
* Find if a key exists inside the props
*/
has(key) {
const value = this.get(key);
return value !== void 0 && value !== null;
}
/**
* Get value for a given key
*/
get(key, defaultValue) {
return lodash3.get(this.all(), key, defaultValue);
}
/**
* Returns all the props
*/
all() {
return this[Symbol.for("options")].props;
}
/**
* Validate prop value
*/
validate(key, validateFn) {
const value = this.get(key);
validateFn(key, value);
}
/**
* Return values for only the given keys
*/
only(keys) {
return lodash3.pick(this.all(), keys);
}
/**
* Return values except the given keys
*/
except(keys) {
return lodash3.omit(this.all(), keys);
}
/**
* Serialize all props to a string of HTML attributes
*/
serialize(mergeProps, prioritizeInline = true) {
const attributes = prioritizeInline ? lodash3.merge({}, this.all(), mergeProps) : lodash3.merge({}, mergeProps, this.all());
return safeValue(stringifyAttributes(this.#mergeClassAttributes(attributes)));
}
/**
* Serialize only the given keys to a string of HTML attributes
*/
serializeOnly(keys, mergeProps, prioritizeInline = true) {
const attributes = prioritizeInline ? lodash3.merge({}, this.only(keys), mergeProps) : lodash3.merge({}, mergeProps, this.only(keys));
return safeValue(stringifyAttributes(this.#mergeClassAttributes(attributes)));
}
/**
* Serialize except the given keys to a string of HTML attributes
*/
serializeExcept(keys, mergeProps, prioritizeInline = true) {
const attributes = prioritizeInline ? lodash3.merge({}, this.except(keys), mergeProps) : lodash3.merge({}, mergeProps, this.except(keys));
return safeValue(stringifyAttributes(this.#mergeClassAttributes(attributes)));
}
};
// src/template/index.ts
var SafeValue = class {
constructor(value) {
this.value = value;
}
};
function escape(input) {
return input instanceof SafeValue ? input.value : he.escape(String(input));
}
function safeValue(value) {
return new SafeValue(value);
}
var Template = class extends Macroable {
constructor(compiler, globals, locals, processor) {
super();
this.compiler = compiler;
this.processor = processor;
this.sharedState = lodash4.merge({}, globals, locals);
}
/**
* Required by Macroable
*/
static macros = {};
static getters = {};
/**
* The shared state is used to hold the globals and locals,
* since it is shared with components too.
*/
sharedState;
/**
* Wraps template to a function
*/
wrapToFunction(template, ...localVariables) {
const args = ["template", "state", "$context"].concat(localVariables);
if (this.compiler.async) {
return new Function(
"",
`return async function template (${args.join(",")}) { ${template} }`
)();
}
return new Function("", `return function template (${args.join(",")}) { ${template} }`)();
}
/**
* Trims top and bottom new lines from the content
*/
trimTopBottomNewLines(value) {
return value.replace(/^\n|^\r\n/, "").replace(/\n$|\r\n$/, "");
}
/**
* Render a compiled template with state
*/
renderCompiled(compiledTemplate, state) {
const templateState = Object.assign({}, this.sharedState, state);
const $context = {};
if (this.compiler.async) {
return this.wrapToFunction(compiledTemplate)(this, templateState, $context).then(
(output2) => {
output2 = this.trimTopBottomNewLines(output2);
return this.processor.executeOutput({ output: output2, template: this, state: templateState });
}
);
}
const output = this.trimTopBottomNewLines(
this.wrapToFunction(compiledTemplate)(this, templateState, $context)
);
return this.processor.executeOutput({ output, template: this, state: templateState });
}
/**
* Render a partial
*
* ```js
* const partialFn = template.compilePartial('includes/user')
*
* // render and use output
* partialFn(template, state, ctx)
* ```
*/
compilePartial(templatePath, ...localVariables) {
const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables, true);
return this.wrapToFunction(compiledTemplate, ...localVariables);
}
/**
* Render a component
*
* ```js
* const componentFn = template.compileComponent('components/button')
*
* // render and use output
* componentFn(template, template.getComponentState(props, slots, caller), ctx)
* ```
*/
compileComponent(templatePath, ...localVariables) {
const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables);
return this.wrapToFunction(compiledTemplate, ...localVariables);
}
/**
* Returns the isolated state for a given component
*/
getComponentState(props, slots, caller) {
return Object.assign({}, this.sharedState, props, {
$slots: slots,
$caller: caller,
$props: new Props(props)
});
}
/**
* Render a template with it's state.
*
* ```js
* template.render('welcome', { key: 'value' })
* ```
*/
render(template, state) {
let { template: compiledTemplate } = this.compiler.compile(template);
return this.renderCompiled(compiledTemplate, state);
}
/**
* Render template from a raw string
*
* ```js
* template.renderRaw('Hello {{ username }}', { username: 'virk' })
* ```
*/
renderRaw(contents, state, templatePath) {
let { template: compiledTemplate } = this.compiler.compileRaw(contents, templatePath);
return this.renderCompiled(compiledTemplate, state);
}
/**
* Escapes the value to be HTML safe. Only strings are escaped
* and rest all values will be returned as it is.
*/
escape(input) {
return escape(input);
}
/**
* Raise an error
*/
newError(errorMessage, filename, lineNumber, column) {
throw new EdgeError8(errorMessage, "E_RUNTIME_EXCEPTION", {
filename,
line: lineNumber,
col: column
});
}
/**
* Rethrows the runtime exception by re-constructing the error message
* to point back to the original filename
*/
reThrow(error, filename, lineNumber) {
if (error instanceof EdgeError8) {
throw error;
}
const message = error.message.replace(/state\./, "");
throw new EdgeError8(message, "E_RUNTIME_EXCEPTION", {
filename,
line: lineNumber,
col: 0
});
}
};
// src/processor/index.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/renderer/index.ts
import lodash5 from "@poppinss/utils/lodash";
var EdgeRenderer = class {
#locals = {};
#compiler;
#asyncCompiler;
#globals;
#processor;
constructor(compiler, asyncCompiler, globals, processor) {
this.#compiler = compiler;
this.#asyncCompiler = asyncCompiler;
this.#globals = globals;
this.#processor = processor;
}
/**
* Share local variables with the template. They will overwrite the
* globals
*/
share(data) {
lodash5.merge(this.#locals, data);
return this;
}
/**
* 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/edge/index.ts
var Edge = class {
#executedPlugins = false;
/**
* An array of registered plugins
*/
#plugins = [];
/**
* Array of registered renderer hooks
*/
#renderCallbacks = [];
/**
* Reference to the registered processor handlers
*/
processor = new Processor();
/**
* Globals are shared with all rendered templates
*/
GLOBALS = {};
/**
* List of registered tags. Adding new tags will only impact
* this list
*/
tags = {};
/**
* The loader to load templates. A loader can read and return
* templates from anywhere. The default loader reads files
* from the disk
*/
loader;
/**
* The underlying compiler in use
*/
compiler;
/**
* The underlying compiler in use
*/
asyncCompiler;
constructor(options = {}) {
this.loader = options.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
});
Object.keys(tags_exports).forEach((name) => this.registerTag(tags_exports[name]));
}
/**
* Execute plugins. Since plugins are meant to be called only
* once we empty out the array after first call
*/
executePlugins() {
if (this.#executedPlugins) {
this.#plugins.forEach(({ fn, options }) => {
if (options && options.recurring) {
fn(this, false, options);
}
});
} else {
this.#executedPlugins = true;
this.#plugins.forEach(({ fn, options }) => {
fn(this, true, options);
});
}
}
/**
* Register a plugin. Plugin functions are called once just before
* an attempt to render a view is made.
*/
use(pluginFn, options) {
this.#plugins.push({
fn: pluginFn,
options
});
return this;
}
/**
* Mount named directory to use views. Later you can reference
* the views from a named disk as follows.
*
* ```
* edge.mount('admin', join(__dirname, 'admin'))
*
* edge.render('admin::filename')
* ```
*/
mount(diskName, dirPath) {
if (!dirPath) {
dirPath = diskName;
diskName = "default";
}
this.loader.mount(diskName, dirPath);
return this;
}
/**
* Un Mount a disk from the loader.
*
* ```js
* edge.unmount('admin')
* ```
*/
unmount(diskName) {
this.loader.unmount(diskName);
return this;
}
/**
* Add a new global to the edge globals. The globals are available
* to all the templates.
*
* ```js
* edge.global('username', 'virk')
* edge.global('time', () => new Date().getTime())
* ```
*/
global(name, value) {
this.GLOBALS[name] = value;
return this;
}
/**
* Add a new tag to the tags list.
*
* ```ts
* edge.registerTag('svg', {
* block: false,
* seekable: true,
*
* compile (parser, buffer, token) {
* const fileName = token.properties.jsArg.trim()
* buffer.writeRaw(fs.readFileSync(__dirname, 'assets', `${fileName}.svg`), 'utf-8')
* }
* })
* ```
*/
registerTag(tag) {
if (typeof tag.boot === "function") {
tag.boot(Template);
}
this.tags[tag.tagName] = tag;
return this;
}
/**
* Register an in-memory template.
*
* ```ts
* edge.registerTemplate('button', {
* template: `<button class="{{ this.type || 'primary' }}">
* @!yield($slots.main())
* </button>`,
* })
* ```
*
* Later you can use this template
*
* ```edge
* @component('button', type = 'primary')
* Get started
* @endcomponent
* ```
*/
registerTemplate(templatePath, contents) {
this.loader.register(templatePath, contents);
return this;
}
/**
* Remove the template registered using the "registerTemplate" method
*/
removeTemplate(templatePath) {
this.loader.remove(templatePath);
this.compiler.cacheManager.delete(templatePath);
this.asyncCompiler.cacheManager.delete(templatePath);
return this;
}
/**
* Get access to the underlying template renderer. Each render call
* to edge results in creating an isolated renderer instance.
*/
onRender(callback) {
this.#renderCallbacks.push(callback);
return this;
}
/**
* Returns a new instance of edge. The instance
* can be used to define locals.
*/
getRenderer() {
this.executePlugins();
const renderer = new EdgeRenderer(
this.compiler,
this.asyncCompiler,
this.GLOBALS,
this.processor
);
this.#renderCallbacks.forEach((callback) => callback(renderer));
return renderer;
}
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
render(templatePath, state) {
return this.getRenderer().render(templatePath, state);
}
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderSync(templatePath, state) {
return this.getRenderer().renderSync(templatePath, state);
}
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRaw(contents, state, templatePath) {
return this.getRenderer().renderRaw(contents, state, templatePath);
}
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRawSync(templatePath, state) {
return this.getRenderer().renderRawSync(templatePath, state);
}
/**
* Share locals with the current view context.
*
* ```js
* const view = edge.getRenderer()
*
* // local state for the current render
* view.share({ foo: 'bar' })
*
* view.render('welcome')
* ```
*/
share(data) {
return this.getRenderer().share(data);
}
};
// src/edge/globals/index.ts
import { EdgeError as EdgeError9 } from "edge-error";
import stringify from "js-stringify";
import inspect from "@poppinss/inspect";
import string from "@poppinss/utils/string";
var { string: prettyPrintHtml } = inspect;
var GLOBALS = {
/**
* 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 safeValue(prettyPrintHtml.html(value));
},
/**
* Truncate a sentence
*/
truncate: (value, length = 20, options) => {
options = options || {};
return string.truncate(value, length, {
completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict,
suffix: options.suffix
});
},
/**
* Raise an exception
*/
raise: (message, options) => {
if (!options) {
throw new Error(message);
} else {
throw new EdgeError9(message, "E_RUNTIME_EXCEPTION", options);
}
},
/**
* Generate an excerpt
*/
excerpt: (value, length = 20, options) => {
options = options || {};
return string.excerpt(value, length, {
completeWords: options.completeWords !== void 0 ? options.completeWords : !options.strict,
suffix: options.suffix
});
},
/**
* Using `"e"` because, `escape` is a global function in the
* Node.js global namespace and edge parser gives priority
* to it
*/
e: escape,
/**
* Convert javascript data structures to a string. The method is a little
* better over JSON.stringify in handling certain data structures. For
* example: In JSON.stringify, the date is converted to an ISO string
* whereas this method converts it to an actual instance of date
*/
stringify,
safe: safeValue,
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
};
// index.ts
var edge = new Edge();
Object.keys(GLOBALS).forEach((key) => edge.global(key, GLOBALS[key]));
export default edge;
export { Edge, safeValue, GLOBALS };
var edge_default = edge;
export {
Edge,
GLOBALS,
edge_default as default,
safeValue
};

46

build/src/types.d.ts

@@ -0,1 +1,6 @@

import { Token, TagToken } from 'edge-lexer/types';
import { Parser, EdgeBuffer } from 'edge-parser';
import { ParserTagDefinitionContract, ClaimTagFn } from 'edge-parser/types';
export { ClaimTagFn } from 'edge-parser/types';
/**

@@ -9,9 +14,7 @@ * edge

*/
import type { Token, TagToken } from 'edge-lexer/types';
import type { Parser, EdgeBuffer } from 'edge-parser';
import type { ParserTagDefinitionContract, ClaimTagFn } from 'edge-parser/types';
/**
* The shape in which the loader must resolve the template
*/
export type LoaderTemplate = {
type LoaderTemplate = {
template: string;

@@ -22,3 +25,3 @@ };

*/
export interface LoaderContract {
interface LoaderContract {
/**

@@ -64,3 +67,3 @@ * List of mounted disks

*/
export interface TemplateConstructorContract {
interface TemplateConstructorContract {
macro: (this: any, name: string, value: (...args: any[]) => any) => void;

@@ -74,3 +77,3 @@ getter: (this: any, name: string, accumulator: () => any, singleton?: boolean) => void;

*/
export interface TagContract extends ParserTagDefinitionContract {
interface TagContract extends ParserTagDefinitionContract {
tagName: string;

@@ -82,3 +85,3 @@ boot?(template: TemplateConstructorContract): void;

*/
export type TagsContract = {
type TagsContract = {
[tagName: string]: TagContract;

@@ -89,3 +92,3 @@ };

*/
export interface CacheManagerContract {
interface CacheManagerContract {
enabled: boolean;

@@ -100,3 +103,3 @@ get(templatePath: string): undefined | LoaderTemplate;

*/
export type CompilerOptions = {
type CompilerOptions = {
cache?: boolean;

@@ -108,3 +111,3 @@ async?: boolean;

*/
export interface CompilerContract {
interface CompilerContract {
cacheManager: CacheManagerContract;

@@ -127,3 +130,3 @@ async: boolean;

*/
export interface PropsContract {
interface PropsContract {
/**

@@ -167,3 +170,3 @@ * Find if a key exists inside the props

*/
export interface TemplateContract {
interface TemplateContract {
/**

@@ -218,3 +221,3 @@ * Compiles partial

*/
export interface EdgeRendererContract {
interface EdgeRendererContract {
/**

@@ -239,3 +242,3 @@ * Share state with the template and its partials and component

*/
export interface ProcessorContract {
interface ProcessorContract {
/**

@@ -281,3 +284,3 @@ * Hook into the raw text to modify its contents. Make sure to return the

*/
export type EdgeOptions = {
type EdgeOptions = {
loader?: LoaderContract;

@@ -289,3 +292,3 @@ cache?: boolean;

*/
export interface EdgeContract {
interface EdgeContract {
/**

@@ -382,5 +385,6 @@ * Loader for loading templates. You can also define a custom loader when creating

*/
export type EdgeBufferContract = EdgeBuffer;
export type ParserContract = Parser;
export type TagTokenContract = TagToken;
export type { ClaimTagFn };
type EdgeBufferContract = EdgeBuffer;
type ParserContract = Parser;
type TagTokenContract = TagToken;
export { CacheManagerContract, CompilerContract, CompilerOptions, EdgeBufferContract, EdgeContract, EdgeOptions, EdgeRendererContract, LoaderContract, LoaderTemplate, ParserContract, ProcessorContract, PropsContract, TagContract, TagTokenContract, TagsContract, TemplateConstructorContract, TemplateContract };
{
"name": "edge.js",
"description": "Template engine",
"version": "6.0.0-0",
"version": "6.0.0-1",
"engines": {

@@ -11,5 +11,3 @@ "node": ">=18.16.0"

"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
"build"
],

@@ -25,3 +23,3 @@ "exports": {

"typecheck": "tsc --noEmit",
"compile": "npm run lint && npm run clean && tsc",
"compile": "npm run lint && npm run clean && tsup-node",
"build": "npm run compile",

@@ -56,2 +54,3 @@ "prepublishOnly": "npm run build",

"ts-node": "^10.9.1",
"tsup": "^7.1.0",
"typescript": "^5.1.6"

@@ -62,6 +61,6 @@ },

"@poppinss/macroable": "1.0.0-7",
"@poppinss/utils": "6.5.0-3",
"@poppinss/utils": "6.5.0-5",
"edge-error": "^4.0.0-0",
"edge-lexer": "^6.0.0-0",
"edge-parser": "^9.0.0-0",
"edge-lexer": "^6.0.0-1",
"edge-parser": "^9.0.0-1",
"he": "^1.2.0",

@@ -122,3 +121,14 @@ "js-stringify": "^1.0.2",

]
},
"tsup": {
"entry": [
"./index.ts",
"./src/types.ts"
],
"outDir": "./build",
"clean": true,
"format": "esm",
"dts": true,
"target": "esnext"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc