Comparing version 6.0.0-0 to 6.0.0-1
@@ -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 }; |
2054
build/index.js
@@ -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 | ||
}; |
@@ -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" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
3
85389
21
7
2709
+ Added@poppinss/utils@6.5.0-5(transitive)
+ Added@types/pluralize@0.0.30(transitive)
- Removed@poppinss/utils@6.5.0-3(transitive)
- Removed@types/pluralize@0.0.29(transitive)
Updated@poppinss/utils@6.5.0-5
Updatededge-lexer@^6.0.0-1
Updatededge-parser@^9.0.0-1