Comparing version 2.0.2 to 2.1.0
"use strict"; | ||
/* | ||
* 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. | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Edge_1 = require("./src/Edge"); | ||
exports.Edge = Edge_1.Edge; | ||
const globals_1 = __importDefault(require("./src/Edge/globals")); | ||
var utils_1 = require("./src/utils"); | ||
@@ -10,2 +22,3 @@ exports.disAllowExpressions = utils_1.disAllowExpressions; | ||
const edge = new Edge_1.Edge(); | ||
globals_1.default(edge); | ||
exports.default = edge; |
@@ -0,2 +1,8 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { LoaderTemplate } from '../Contracts'; | ||
/** | ||
* In memory cache manager to cache pre-compiled templates | ||
*/ | ||
export declare class CacheManager { | ||
@@ -6,4 +12,13 @@ private _enabled; | ||
constructor(_enabled: boolean); | ||
/** | ||
* Returns the template and the presenter class 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 returns in noop. | ||
*/ | ||
set(absPath: string, payload: LoaderTemplate): void; | ||
} |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* In memory cache manager to cache pre-compiled templates | ||
*/ | ||
class CacheManager { | ||
@@ -8,2 +14,7 @@ constructor(_enabled) { | ||
} | ||
/** | ||
* Returns the template and the presenter class from the | ||
* cache. If caching is disabled, then it will | ||
* return undefined. | ||
*/ | ||
get(absPath) { | ||
@@ -15,2 +26,6 @@ if (!this._enabled) { | ||
} | ||
/** | ||
* Set's the template path and the payload to the cache. If | ||
* cache is disabled, then this function returns in noop. | ||
*/ | ||
set(absPath, payload) { | ||
@@ -17,0 +32,0 @@ if (!this._enabled) { |
@@ -0,3 +1,14 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { ParserToken } from 'edge-parser'; | ||
import { LoaderContract, TagsContract, LoaderTemplate, CompilerContract } from '../Contracts'; | ||
/** | ||
* Compiler compiles the template to a function, which can be invoked at a later | ||
* stage using the [[Context]]. [edge-parser](https://npm.im/edge-parser) is | ||
* used under the hood to parse the templates. | ||
* | ||
* Also, the `layouts` are handled natively by the compiler. Before starting | ||
* the parsing process, it will recursively merge the layout sections. | ||
*/ | ||
export declare class Compiler implements CompilerContract { | ||
@@ -9,6 +20,44 @@ private _loader; | ||
constructor(_loader: LoaderContract, _tags: TagsContract, _cache?: boolean); | ||
/** | ||
* Merges sections of base template and parent template tokens | ||
*/ | ||
private _mergeSections; | ||
/** | ||
* 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. | ||
*/ | ||
private _templateContentToTokens; | ||
/** | ||
* Converts the template content to an [array of lexer tokens]. The method is | ||
* same as the `parser.generateLexerTokens`, plus it will handle the layouts | ||
* and it's sections. | ||
* | ||
* ``` | ||
* compiler.generateLexerTokens('<template-path>') | ||
* ``` | ||
*/ | ||
generateLexerTokens(templatePath: string): ParserToken[]; | ||
/** | ||
* Compiles the template contents to a function string, which can be invoked | ||
* later. | ||
* | ||
* When `inline` is set to true, the compiled output **will not have it's own scope** and | ||
* neither an attempt to load the presenter is made. The `inline` is mainly used for partials. | ||
* | ||
* ```js | ||
* compiler.compile('welcome', false) | ||
* // output | ||
* | ||
* { | ||
* template: `function (template, ctx) { | ||
* let out = '' | ||
* out += '' | ||
* return out | ||
* })(template, ctx)`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* ``` | ||
*/ | ||
compile(templatePath: string, inline: boolean): LoaderTemplate; | ||
} |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_error_1 = require("edge-error"); | ||
@@ -7,2 +18,10 @@ const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Compiler compiles the template to a function, which can be invoked at a later | ||
* stage using the [[Context]]. [edge-parser](https://npm.im/edge-parser) is | ||
* used under the hood to parse the templates. | ||
* | ||
* Also, the `layouts` are handled natively by the compiler. Before starting | ||
* the parsing process, it will recursively merge the layout sections. | ||
*/ | ||
class Compiler { | ||
@@ -15,7 +34,21 @@ constructor(_loader, _tags, _cache = true) { | ||
} | ||
/** | ||
* Merges sections of base template and parent template tokens | ||
*/ | ||
_mergeSections(base, extended, filename, layoutPath) { | ||
/** | ||
* Collection of all sections from the extended tokens | ||
*/ | ||
const extendedSections = {}; | ||
/** | ||
* Collection of extended set calls as top level nodes. Now since they are hanging | ||
* up in the air, they will be hoisted like `var` statements in Javascript | ||
*/ | ||
const extendedSetCalls = []; | ||
extended | ||
.forEach((node) => { | ||
/** | ||
* Ignore new lines, layout tag and empty raw nodes inside the parent | ||
* template | ||
*/ | ||
if (utils_1.isBlockToken(node, 'layout') || | ||
@@ -26,2 +59,5 @@ node.type === 'newline' || | ||
} | ||
/** | ||
* Collect parent template sections | ||
*/ | ||
if (utils_1.isBlockToken(node, 'section')) { | ||
@@ -31,2 +67,5 @@ extendedSections[node.properties.jsArg.trim()] = node; | ||
} | ||
/** | ||
* Collect set calls inside parent templates | ||
*/ | ||
if (utils_1.isBlockToken(node, 'set')) { | ||
@@ -36,5 +75,11 @@ extendedSetCalls.push(node); | ||
} | ||
/** | ||
* Everything else if not allowed as top level nodes | ||
*/ | ||
const [line, col] = utils_1.getLineAndColumnForToken(node); | ||
throw new edge_error_1.EdgeError(`Template extending the layout can only define @sections as top level nodes`, 'E_UNALLOWED_EXPRESSION', { line, col, filename }); | ||
}); | ||
/** | ||
* Replace/extend sections inside base tokens list | ||
*/ | ||
const finalNodes = base | ||
@@ -51,2 +96,5 @@ .map((node) => { | ||
} | ||
/** | ||
* Concat children when super was called | ||
*/ | ||
if (extendedNode.children.length && utils_1.isBlockToken(extendedNode.children[0], 'super')) { | ||
@@ -60,9 +108,25 @@ extendedNode.children = node.children.map((child) => { | ||
}); | ||
/** | ||
* Set calls are hoisted to the top | ||
*/ | ||
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, filename) { | ||
let templateTokens = parser.generateLexerTokens(content); | ||
const firstToken = templateTokens[0]; | ||
/** | ||
* The `layout` is inbuilt feature from core, where we merge the layout | ||
* and parent template sections together | ||
*/ | ||
if (utils_1.isBlockToken(firstToken, 'layout')) { | ||
const layoutName = firstToken.properties.jsArg.replace(/'/g, ''); | ||
/** | ||
* Making absolute path, so that lexer errors must point to the | ||
* absolute file path | ||
*/ | ||
const absPath = this._loader.makePath(layoutName); | ||
@@ -74,2 +138,11 @@ const layoutTokens = this.generateLexerTokens(absPath); | ||
} | ||
/** | ||
* Converts the template content to an [array of lexer tokens]. The method is | ||
* same as the `parser.generateLexerTokens`, plus it will handle the layouts | ||
* and it's sections. | ||
* | ||
* ``` | ||
* compiler.generateLexerTokens('<template-path>') | ||
* ``` | ||
*/ | ||
generateLexerTokens(templatePath) { | ||
@@ -80,4 +153,29 @@ const { template } = this._loader.resolve(templatePath, false); | ||
} | ||
/** | ||
* Compiles the template contents to a function string, which can be invoked | ||
* later. | ||
* | ||
* When `inline` is set to true, the compiled output **will not have it's own scope** and | ||
* neither an attempt to load the presenter is made. The `inline` is mainly used for partials. | ||
* | ||
* ```js | ||
* compiler.compile('welcome', false) | ||
* // output | ||
* | ||
* { | ||
* template: `function (template, ctx) { | ||
* let out = '' | ||
* out += '' | ||
* return out | ||
* })(template, ctx)`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* ``` | ||
*/ | ||
compile(templatePath, inline) { | ||
const absPath = this._loader.makePath(templatePath); | ||
/** | ||
* If template is in the cache, then return it without | ||
* further processing | ||
*/ | ||
const cachedResponse = this._cacheManager.get(absPath); | ||
@@ -87,10 +185,29 @@ if (cachedResponse) { | ||
} | ||
/** | ||
* Do not load presenter in inline mode | ||
*/ | ||
const loadPresenter = !inline; | ||
/** | ||
* Inline templates are not wrapped inside a function | ||
* call. They share the parent template scope | ||
*/ | ||
const wrapAsFunction = !inline; | ||
const parser = new edge_parser_1.Parser(this._tags, { | ||
filename: `${absPath.replace(/\.edge$/, '')}.edge`, | ||
}); | ||
/** | ||
* Get a new instance of the parser. | ||
*/ | ||
const parser = new edge_parser_1.Parser(this._tags, { filename: absPath }); | ||
/** | ||
* Resolve the template and Presenter using the given loader | ||
*/ | ||
const { template, Presenter } = this._loader.resolve(absPath, loadPresenter); | ||
/** | ||
* Convert template to AST. The AST will have the layout actions merged (if layout) | ||
* is used. | ||
*/ | ||
const templateTokens = this._templateContentToTokens(template, parser, absPath); | ||
/** | ||
* Finally process the ast | ||
*/ | ||
const buffer = new edge_parser_1.EdgeBuffer(); | ||
buffer.writeStatement(`ctx.set('$filename', '${templatePath.replace(/\.edge$/, '')}.edge');`); | ||
templateTokens.forEach((token) => parser.processLexerToken(token, buffer)); | ||
@@ -97,0 +214,0 @@ const payload = { |
@@ -0,17 +1,110 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { Macroable } from 'macroable'; | ||
import { ContextContract } from '../Contracts'; | ||
declare class SafeValue { | ||
value: any; | ||
constructor(value: any); | ||
} | ||
/** | ||
* Context is used at runtime to resolve values for a given | ||
* template. | ||
* | ||
* Also the context can be extended to add `getters` and `methods`. Checkout | ||
* [macroable](https://github.com/poppinss/macroable) for same. | ||
*/ | ||
export declare class Context extends Macroable implements ContextContract { | ||
presenter: any; | ||
sharedState: any; | ||
protected static _macros: {}; | ||
protected static _getters: {}; | ||
protected static macros: {}; | ||
protected static getters: {}; | ||
/** | ||
* Frames are used to define a inner scope in which values will | ||
* be resolved. The resolve function starts with the deepest | ||
* frame and then resolve the value up until the first | ||
* frame. | ||
*/ | ||
private _frames; | ||
constructor(presenter: any, sharedState: any); | ||
/** | ||
* Returns value for a key inside frames. Stops looking for it, | ||
* when value is found inside any frame. | ||
*/ | ||
private _getFromFrame; | ||
/** | ||
* Returns a merged copy of the current state. The objects are merged | ||
* in the same order as they are resolved. | ||
*/ | ||
private getCurrentState; | ||
/** | ||
* Creates a new frame scope. Think of a scope as a Javacript block | ||
* scope, where variables defined inside the scope are only available | ||
* to that scope. | ||
* | ||
* ```js | ||
* ctx.newFrame() | ||
* ``` | ||
*/ | ||
newFrame(): void; | ||
/** | ||
* Set key/value pair on the frame object. The value will only be available until | ||
* the `removeFrame` is not called. | ||
* | ||
* ```js | ||
* ctx.setOnFrame('username', 'virk') | ||
* | ||
* // nested values | ||
* ctx.setOnFrame('user.username', 'virk') | ||
* ``` | ||
* | ||
* @throws Error if no frame scopes exists. | ||
*/ | ||
setOnFrame(key: string, value: any): void; | ||
/** | ||
* Removes the most recent frame/scope. All values set inside the | ||
* frame via `setOnFrame` will be removed. | ||
*/ | ||
removeFrame(): void; | ||
/** | ||
* Mark output as safe | ||
*/ | ||
safe<T extends any>(value: T): SafeValue; | ||
/** | ||
* Escapes the value to be HTML safe. Only strings are escaped | ||
* and rest all values will be returned as it is. | ||
*/ | ||
escape<T>(input: T): T; | ||
/** | ||
* Resolves value for a given key. It will look for the value in different | ||
* locations and continues till the end if `undefined` is returned at | ||
* each step. | ||
* | ||
* The following steps are followed in the same order as defined. | ||
* | ||
* 1. Check for value inside frames. | ||
* 2. Then on the presenter instance. | ||
* 3. Then the presenter `state` object. | ||
* 4. Finally fallback to the sharedState. | ||
* | ||
* @example | ||
* ```js | ||
* ctx.resolve('username') | ||
* ``` | ||
*/ | ||
resolve(key: string): any; | ||
/** | ||
* Set/Update the value in the context. The value is defined in the following | ||
* order. | ||
* | ||
* 1. If the value already exists on the presenter state, then it will be updated | ||
* 2. If the scope is inside a frame, then will be created/updated on the frame. | ||
* 3. At last, the value is created on the presenter state. | ||
* | ||
* ```js | ||
* ctx.set('username', 'virk') | ||
* ``` | ||
*/ | ||
set(key: string, value: any): void; | ||
} | ||
export {}; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -6,5 +9,25 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* edge.js | ||
* | ||
* (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. | ||
*/ | ||
const he_1 = __importDefault(require("he")); | ||
const lodash_1 = require("lodash"); | ||
const macroable_1 = require("macroable"); | ||
class SafeValue { | ||
constructor(value) { | ||
this.value = value; | ||
} | ||
} | ||
/** | ||
* Context is used at runtime to resolve values for a given | ||
* template. | ||
* | ||
* Also the context can be extended to add `getters` and `methods`. Checkout | ||
* [macroable](https://github.com/poppinss/macroable) for same. | ||
*/ | ||
class Context extends macroable_1.Macroable { | ||
@@ -15,4 +38,14 @@ constructor(presenter, sharedState) { | ||
this.sharedState = sharedState; | ||
/** | ||
* Frames are used to define a inner scope in which values will | ||
* be resolved. The resolve function starts with the deepest | ||
* frame and then resolve the value up until the first | ||
* frame. | ||
*/ | ||
this._frames = []; | ||
} | ||
/** | ||
* Returns value for a key inside frames. Stops looking for it, | ||
* when value is found inside any frame. | ||
*/ | ||
_getFromFrame(key) { | ||
@@ -22,5 +55,34 @@ const frameWithVal = this._frames.find((frame) => frame[key] !== undefined); | ||
} | ||
/** | ||
* Returns a merged copy of the current state. The objects are merged | ||
* in the same order as they are resolved. | ||
*/ | ||
getCurrentState() { | ||
return Object.assign({}, this.sharedState, this.presenter.state, ...this._frames); | ||
} | ||
/** | ||
* Creates a new frame scope. Think of a scope as a Javacript block | ||
* scope, where variables defined inside the scope are only available | ||
* to that scope. | ||
* | ||
* ```js | ||
* ctx.newFrame() | ||
* ``` | ||
*/ | ||
newFrame() { | ||
this._frames.unshift({}); | ||
} | ||
/** | ||
* Set key/value pair on the frame object. The value will only be available until | ||
* the `removeFrame` is not called. | ||
* | ||
* ```js | ||
* ctx.setOnFrame('username', 'virk') | ||
* | ||
* // nested values | ||
* ctx.setOnFrame('user.username', 'virk') | ||
* ``` | ||
* | ||
* @throws Error if no frame scopes exists. | ||
*/ | ||
setOnFrame(key, value) { | ||
@@ -33,10 +95,49 @@ const recentFrame = this._frames[0]; | ||
} | ||
/** | ||
* Removes the most recent frame/scope. All values set inside the | ||
* frame via `setOnFrame` will be removed. | ||
*/ | ||
removeFrame() { | ||
this._frames.shift(); | ||
} | ||
/** | ||
* Mark output as safe | ||
*/ | ||
safe(value) { | ||
return new SafeValue(value); | ||
} | ||
/** | ||
* Escapes the value to be HTML safe. Only strings are escaped | ||
* and rest all values will be returned as it is. | ||
*/ | ||
escape(input) { | ||
return typeof (input) === 'string' ? he_1.default.escape(input) : input; | ||
return typeof (input) === 'string' | ||
? he_1.default.escape(input) | ||
: (input instanceof SafeValue ? input.value : input); | ||
} | ||
/** | ||
* Resolves value for a given key. It will look for the value in different | ||
* locations and continues till the end if `undefined` is returned at | ||
* each step. | ||
* | ||
* The following steps are followed in the same order as defined. | ||
* | ||
* 1. Check for value inside frames. | ||
* 2. Then on the presenter instance. | ||
* 3. Then the presenter `state` object. | ||
* 4. Finally fallback to the sharedState. | ||
* | ||
* @example | ||
* ```js | ||
* ctx.resolve('username') | ||
* ``` | ||
*/ | ||
resolve(key) { | ||
if (key === '$state') { | ||
return this.getCurrentState(); | ||
} | ||
let value; | ||
/** | ||
* Pull from one of the nested frames | ||
*/ | ||
value = this._getFromFrame(key); | ||
@@ -46,2 +147,6 @@ if (value !== undefined) { | ||
} | ||
/** | ||
* Check for value as a property on the presenter | ||
* itself. | ||
*/ | ||
value = this.presenter[key]; | ||
@@ -51,2 +156,5 @@ if (value !== undefined) { | ||
} | ||
/** | ||
* Otherwise look into presenter state | ||
*/ | ||
value = this.presenter.state[key]; | ||
@@ -56,6 +164,25 @@ if (value !== undefined) { | ||
} | ||
/** | ||
* Finally fallback to defined globals | ||
*/ | ||
value = this.sharedState[key]; | ||
return typeof (value) === 'function' ? value.bind(this.sharedState) : value; | ||
} | ||
/** | ||
* Set/Update the value in the context. The value is defined in the following | ||
* order. | ||
* | ||
* 1. If the value already exists on the presenter state, then it will be updated | ||
* 2. If the scope is inside a frame, then will be created/updated on the frame. | ||
* 3. At last, the value is created on the presenter state. | ||
* | ||
* ```js | ||
* ctx.set('username', 'virk') | ||
* ``` | ||
*/ | ||
set(key, value) { | ||
/** | ||
* If value already exists on the presenter | ||
* state, then mutate it first | ||
*/ | ||
if (this.presenter.state[key] !== undefined || !this._frames.length) { | ||
@@ -65,2 +192,5 @@ lodash_1.set(this.presenter.state, key, value); | ||
} | ||
/** | ||
* If frames exists, then set it on frame | ||
*/ | ||
this.setOnFrame(key, value); | ||
@@ -70,3 +200,3 @@ } | ||
exports.Context = Context; | ||
Context._macros = {}; | ||
Context._getters = {}; | ||
Context.macros = {}; | ||
Context.getters = {}; |
@@ -0,4 +1,18 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
/** | ||
* 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 { Token } from 'edge-lexer'; | ||
import { MacroableConstructorContract } from 'macroable'; | ||
import { ParseTagDefininationContract } from 'edge-parser'; | ||
/** | ||
* The shape in which the loader must resolve the template | ||
*/ | ||
export declare type LoaderTemplate = { | ||
@@ -10,12 +24,36 @@ template: string; | ||
}; | ||
/** | ||
* Loader contract that every loader must adheres to. | ||
*/ | ||
export interface LoaderContract { | ||
/** | ||
* List of mounted disks | ||
*/ | ||
mounted: { | ||
[diskName: string]: string; | ||
}; | ||
/** | ||
* Save disk name and dirPath to resolve views | ||
*/ | ||
mount(diskName: string, dirPath: string): void; | ||
/** | ||
* Remove disk from the previously saved paths | ||
*/ | ||
unmount(diskName: string): void; | ||
/** | ||
* Resolve template contents and optionally the Presenter | ||
*/ | ||
resolve(templatePath: string, withPresenter: boolean): LoaderTemplate; | ||
/** | ||
* Make absolute path to a template | ||
*/ | ||
makePath(templatePath: string): string; | ||
/** | ||
* Register in memory template and presenter | ||
*/ | ||
register(templatePath: string, contents: LoaderTemplate): void; | ||
} | ||
/** | ||
* Shape of runtime context | ||
*/ | ||
export interface ContextContract { | ||
@@ -26,2 +64,5 @@ presenter: { | ||
sharedState: any; | ||
safe<T extends any>(value: T): { | ||
value: T; | ||
}; | ||
newFrame(): void; | ||
@@ -34,5 +75,12 @@ setOnFrame(key: string, value: any): void; | ||
} | ||
export interface ContextConstructorContract extends MacroableConstructorContract { | ||
/** | ||
* Shape of context constructor | ||
*/ | ||
export interface ContextConstructorContract extends MacroableConstructorContract<ContextContract> { | ||
new (presenter: any, sharedState: any): ContextContract; | ||
} | ||
/** | ||
* The final tag must have a tagName along with other properties | ||
* required by lexer and parser | ||
*/ | ||
export interface TagContract extends ParseTagDefininationContract { | ||
@@ -42,5 +90,11 @@ tagName: string; | ||
} | ||
/** | ||
* Shape of required tags | ||
*/ | ||
export declare type TagsContract = { | ||
[tagName: string]: TagContract; | ||
}; | ||
/** | ||
* Shape of the compiler | ||
*/ | ||
export interface CompilerContract { | ||
@@ -50,2 +104,5 @@ compile(templatePath: string, inline: boolean): LoaderTemplate; | ||
} | ||
/** | ||
* Shape of the renderer that renders the edge templates | ||
*/ | ||
export interface EdgeRendererContract { | ||
@@ -55,2 +112,6 @@ share(locals: any): this; | ||
} | ||
/** | ||
* Shape of options that can be passed to the | ||
* edge constructor | ||
*/ | ||
export declare type EdgeOptions = { | ||
@@ -60,2 +121,5 @@ loader?: LoaderContract; | ||
}; | ||
/** | ||
* Shape of the main module | ||
*/ | ||
export interface EdgeContract { | ||
@@ -62,0 +126,0 @@ loader: LoaderContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { Compiler } from '../Compiler'; | ||
@@ -5,16 +8,122 @@ import { TagContract, EdgeOptions, EdgeContract, LoaderTemplate, EdgeRendererContract } from '../Contracts'; | ||
private _options; | ||
/** | ||
* Globals are shared with all rendered templates | ||
*/ | ||
private _globals; | ||
/** | ||
* List of registered tags. Adding new tags will only impact | ||
* this list | ||
*/ | ||
private _tags; | ||
/** | ||
* The loader to load templates. A loader can read and return | ||
* templates from anywhere. The default loader reads files | ||
* from the disk | ||
*/ | ||
loader: import("../Contracts").LoaderContract; | ||
/** | ||
* The underlying compiler in use | ||
*/ | ||
compiler: Compiler; | ||
constructor(_options?: EdgeOptions); | ||
/** | ||
* 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; | ||
/** | ||
* Mount defaults views directory. | ||
* | ||
* ``` | ||
* edge.mount(join(__dirname, 'admin')) | ||
* edge.render('filename') | ||
* ``` | ||
*/ | ||
mount(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; | ||
/** | ||
* Render a template with optional state | ||
* | ||
* ```ts | ||
* edge.render('welcome', { greeting: 'Hello world' }) | ||
* ``` | ||
*/ | ||
render(templatePath: string, state?: any): string; | ||
/** | ||
* Returns a new instance of edge. The instance | ||
* can be used to define locals. | ||
*/ | ||
getRenderer(): EdgeRendererContract; | ||
/** | ||
* 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; | ||
} |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
@@ -10,2 +13,10 @@ if (mod && mod.__esModule) return mod; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const Tags = __importStar(require("../Tags")); | ||
@@ -19,5 +30,20 @@ const Loader_1 = require("../Loader"); | ||
this._options = _options; | ||
/** | ||
* Globals are shared with all rendered templates | ||
*/ | ||
this._globals = {}; | ||
/** | ||
* List of registered tags. Adding new tags will only impact | ||
* this list | ||
*/ | ||
this._tags = {}; | ||
/** | ||
* The loader to load templates. A loader can read and return | ||
* templates from anywhere. The default loader reads files | ||
* from the disk | ||
*/ | ||
this.loader = this._options.loader || new Loader_1.Loader(); | ||
/** | ||
* The underlying compiler in use | ||
*/ | ||
this.compiler = new Compiler_1.Compiler(this.loader, this._tags, !!this._options.cache); | ||
@@ -34,2 +60,9 @@ Object.keys(Tags).forEach((name) => this.registerTag(Tags[name])); | ||
} | ||
/** | ||
* Un Mount a disk from the loader. | ||
* | ||
* ```js | ||
* edge.unmount('admin') | ||
* ``` | ||
*/ | ||
unmount(diskName) { | ||
@@ -39,2 +72,11 @@ this.loader.unmount(diskName); | ||
} | ||
/** | ||
* 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) { | ||
@@ -44,2 +86,17 @@ this._globals[name] = value; | ||
} | ||
/** | ||
* 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) { | ||
@@ -52,2 +109,21 @@ if (typeof (tag.run) === 'function') { | ||
} | ||
/** | ||
* 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) { | ||
@@ -57,8 +133,31 @@ this.loader.register(templatePath, contents); | ||
} | ||
/** | ||
* Render a template with optional state | ||
* | ||
* ```ts | ||
* edge.render('welcome', { greeting: 'Hello world' }) | ||
* ``` | ||
*/ | ||
render(templatePath, state) { | ||
return this.getRenderer().render(templatePath, state); | ||
} | ||
/** | ||
* Returns a new instance of edge. The instance | ||
* can be used to define locals. | ||
*/ | ||
getRenderer() { | ||
return new Renderer_1.EdgeRenderer(this.compiler, this._globals); | ||
} | ||
/** | ||
* 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) { | ||
@@ -65,0 +164,0 @@ return this.getRenderer().share(data); |
@@ -0,15 +1,123 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { LoaderContract, LoaderTemplate } from '../Contracts'; | ||
/** | ||
* The job of a loader is to load the template and it's presenter for a given path. | ||
* The base loader (shipped with edge) looks for files on the file-system and | ||
* reads them synchronously. | ||
* | ||
* You are free to define your own loaders that implements the [[LoaderContract]] interface. | ||
*/ | ||
export declare class Loader implements LoaderContract { | ||
/** | ||
* List of mounted directories | ||
*/ | ||
private _mountedDirs; | ||
/** | ||
* List of pre-registered (in-memory) templates | ||
*/ | ||
private _preRegistered; | ||
/** | ||
* Attempts to load the presenter for a given template. If presenter doesn't exists, it | ||
* will swallow the error. | ||
* | ||
* Also this method will **bypass the `require` cache**, since in production compiled templates | ||
* and their presenters are cached anyways. | ||
*/ | ||
private _getPresenterForTemplate; | ||
/** | ||
* Reads the content of a template from the disk. An exception is raised | ||
* when file is missing or if `readFileSync` returns an error. | ||
*/ | ||
private _readTemplateContents; | ||
readonly mounted: { | ||
/** | ||
* 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(): { | ||
[key: string]: string; | ||
}; | ||
/** | ||
* 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: string, dirPath: string): void; | ||
/** | ||
* Remove the previously mounted dir. | ||
* | ||
* ```js | ||
* loader.unmount('default') | ||
* ``` | ||
*/ | ||
unmount(diskName: string): void; | ||
/** | ||
* 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: string): string; | ||
/** | ||
* Resolves the template from the disk, optionally loads the presenter too. The presenter | ||
* resolution is based on the convention and resolved from the same directory | ||
* as the template. | ||
* | ||
* ## Presenter convention | ||
* - View name - welcome.edge | ||
* - Presenter name - Welcome.presenter.js | ||
* | ||
* ```js | ||
* loader.resolve('welcome', true) | ||
* | ||
* // output | ||
* { | ||
* template: `<h1> Template content </h1>`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* ``` | ||
*/ | ||
resolve(templatePath: string, withPresenter: boolean): LoaderTemplate; | ||
/** | ||
* Register in memory template and Presenter 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: string, contents: LoaderTemplate): void; | ||
} |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* 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. | ||
*/ | ||
const fs_1 = require("fs"); | ||
@@ -8,7 +19,27 @@ const utils_1 = require("@poppinss/utils"); | ||
const utils_2 = require("../utils"); | ||
/** | ||
* The job of a loader is to load the template and it's presenter for a given path. | ||
* The base loader (shipped with edge) looks for files on the file-system and | ||
* reads them synchronously. | ||
* | ||
* You are free to define your own loaders that implements the [[LoaderContract]] interface. | ||
*/ | ||
class Loader { | ||
constructor() { | ||
/** | ||
* List of mounted directories | ||
*/ | ||
this._mountedDirs = new Map(); | ||
/** | ||
* List of pre-registered (in-memory) templates | ||
*/ | ||
this._preRegistered = new Map(); | ||
} | ||
/** | ||
* Attempts to load the presenter for a given template. If presenter doesn't exists, it | ||
* will swallow the error. | ||
* | ||
* Also this method will **bypass the `require` cache**, since in production compiled templates | ||
* and their presenters are cached anyways. | ||
*/ | ||
_getPresenterForTemplate(templatePath) { | ||
@@ -27,2 +58,6 @@ const presenterPath = templatePath | ||
} | ||
/** | ||
* 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) { | ||
@@ -41,2 +76,16 @@ try { | ||
} | ||
/** | ||
* 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() { | ||
@@ -48,8 +97,39 @@ return Array.from(this._mountedDirs).reduce((obj, [key, value]) => { | ||
} | ||
/** | ||
* 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) { | ||
@@ -60,2 +140,5 @@ if (this._preRegistered.has(templatePath)) { | ||
const [diskName, template] = utils_2.extractDiskAndTemplateName(templatePath); | ||
/** | ||
* Raise exception when disk name is not defined | ||
*/ | ||
const mountedDir = this._mountedDirs.get(diskName); | ||
@@ -67,3 +150,25 @@ if (!mountedDir) { | ||
} | ||
/** | ||
* Resolves the template from the disk, optionally loads the presenter too. The presenter | ||
* resolution is based on the convention and resolved from the same directory | ||
* as the template. | ||
* | ||
* ## Presenter convention | ||
* - View name - welcome.edge | ||
* - Presenter name - Welcome.presenter.js | ||
* | ||
* ```js | ||
* loader.resolve('welcome', true) | ||
* | ||
* // output | ||
* { | ||
* template: `<h1> Template content </h1>`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* ``` | ||
*/ | ||
resolve(templatePath, withPresenter) { | ||
/** | ||
* Return from pre-registered one's if exists | ||
*/ | ||
if (this._preRegistered.has(templatePath)) { | ||
@@ -73,2 +178,5 @@ const contents = this._preRegistered.get(templatePath); | ||
} | ||
/** | ||
* Make absolute to the file on the disk | ||
*/ | ||
templatePath = path_1.isAbsolute(templatePath) ? templatePath : this.makePath(templatePath); | ||
@@ -80,6 +188,29 @@ return { | ||
} | ||
/** | ||
* Register in memory template and Presenter 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) { | ||
/** | ||
* Ensure template content is defined as a string | ||
*/ | ||
if (typeof (contents.template) !== 'string') { | ||
throw new utils_1.Exception('Make sure to define the template content as a string', 500, 'E_MISSING_TEMPLATE_CONTENTS'); | ||
} | ||
/** | ||
* Do not overwrite existing template with same template path | ||
*/ | ||
if (this._preRegistered.has(templatePath)) { | ||
@@ -86,0 +217,0 @@ throw new utils_1.Exception(`Cannot override previously registered {${templatePath}} template`, 500, 'E_DUPLICATE_TEMPLATE_PATH'); |
@@ -0,1 +1,11 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
/** | ||
* The Base presenter is passed to context for reading | ||
* the state values. | ||
* | ||
* However, a custom presenter a do a lot by defining | ||
* custom properties and methods. | ||
*/ | ||
export declare class Presenter { | ||
@@ -2,0 +12,0 @@ state: any; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
/** | ||
* The Base presenter is passed to context for reading | ||
* the state values. | ||
* | ||
* However, a custom presenter a do a lot by defining | ||
* custom properties and methods. | ||
*/ | ||
class Presenter { | ||
@@ -4,0 +22,0 @@ constructor(state) { |
import { EdgeRendererContract, CompilerContract } from '../Contracts'; | ||
/** | ||
* Renders a given template with it's shared state | ||
*/ | ||
export declare class EdgeRenderer implements EdgeRendererContract { | ||
@@ -7,4 +10,11 @@ private _compiler; | ||
constructor(_compiler: CompilerContract, _globals: any); | ||
/** | ||
* Share local variables with the template. They will overwrite the | ||
* globals | ||
*/ | ||
share(data: any): this; | ||
/** | ||
* Render the template | ||
*/ | ||
render(templatePath: string, state?: any): string; | ||
} |
"use strict"; | ||
/* | ||
* 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. | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lodash_1 = require("lodash"); | ||
const Template_1 = require("../Template"); | ||
/** | ||
* Renders a given template with it's shared state | ||
*/ | ||
class EdgeRenderer { | ||
@@ -11,2 +22,6 @@ constructor(_compiler, _globals) { | ||
} | ||
/** | ||
* Share local variables with the template. They will overwrite the | ||
* globals | ||
*/ | ||
share(data) { | ||
@@ -16,2 +31,5 @@ lodash_1.merge(this._locals, data); | ||
} | ||
/** | ||
* Render the template | ||
*/ | ||
render(templatePath, state = {}) { | ||
@@ -18,0 +36,0 @@ const template = new Template_1.Template(this._compiler, this._globals, this._locals); |
@@ -0,5 +1,31 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
/** | ||
* This class generates a valid object as a string, which is written to the template | ||
* output. The reason we need a string like object, since we don't want it's | ||
* properties to be evaluated during the object creation, instead it must | ||
* be evaluated when the compiled output is invoked. | ||
*/ | ||
export declare class StringifiedObject { | ||
private obj; | ||
/** | ||
* Add key/value pair to the object. | ||
* | ||
* ```js | ||
* stringifiedObject.add('username', `'virk'`) | ||
* ``` | ||
*/ | ||
add(key: any, value: any): void; | ||
/** | ||
* Returns the object alike string back. | ||
* | ||
* ```js | ||
* stringifiedObject.flush() | ||
* | ||
* // returns | ||
* `{ username: 'virk' }` | ||
* ``` | ||
*/ | ||
flush(): string; | ||
} |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
/** | ||
* This class generates a valid object as a string, which is written to the template | ||
* output. The reason we need a string like object, since we don't want it's | ||
* properties to be evaluated during the object creation, instead it must | ||
* be evaluated when the compiled output is invoked. | ||
*/ | ||
class StringifiedObject { | ||
@@ -7,5 +24,22 @@ constructor() { | ||
} | ||
/** | ||
* Add key/value pair to the object. | ||
* | ||
* ```js | ||
* stringifiedObject.add('username', `'virk'`) | ||
* ``` | ||
*/ | ||
add(key, value) { | ||
this.obj += this.obj.length ? `, ${key}: ${value}` : `${key}: ${value}`; | ||
} | ||
/** | ||
* Returns the object alike string back. | ||
* | ||
* ```js | ||
* stringifiedObject.flush() | ||
* | ||
* // returns | ||
* `{ username: 'virk' }` | ||
* ``` | ||
*/ | ||
flush() { | ||
@@ -12,0 +46,0 @@ const obj = `{ ${this.obj} }`; |
@@ -0,2 +1,9 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* The component tag implementation. It is one of the most complex tags and | ||
* can be used as a reference for creating other tags. | ||
*/ | ||
export declare const componentTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_error_1 = require("edge-error"); | ||
@@ -7,2 +18,5 @@ const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* A list of allowed expressions for the component name | ||
*/ | ||
const componentNameAllowedExpressions = [ | ||
@@ -17,4 +31,11 @@ edge_parser_1.expressions.Identifier, | ||
]; | ||
/** | ||
* Returns the component name and props by parsing the component jsArg expression | ||
*/ | ||
function getComponentNameAndProps(expression, parser) { | ||
let name; | ||
/** | ||
* Use the first expression inside the sequence expression as the name | ||
* of the component | ||
*/ | ||
if (expression.type === edge_parser_1.expressions.SequenceExpression) { | ||
@@ -26,3 +47,10 @@ name = expression.expressions.shift(); | ||
} | ||
/** | ||
* Ensure the component name is a literal value or an expression that | ||
* outputs a literal value | ||
*/ | ||
utils_1.allowExpressions(name, componentNameAllowedExpressions, parser.options.filename, `{${parser.stringifyExpression(name)}} is not a valid argument for component name`); | ||
/** | ||
* Parse rest of sequence expressions as an objectified string. | ||
*/ | ||
if (expression.type === edge_parser_1.expressions.SequenceExpression) { | ||
@@ -34,7 +62,21 @@ return [ | ||
} | ||
/** | ||
* When top level expression is not a sequence expression, then we assume props | ||
* as empty stringified object. | ||
*/ | ||
return [parser.stringifyExpression(name), '{}']; | ||
} | ||
/** | ||
* Parses the slot component to fetch it's name and props | ||
*/ | ||
function getSlotNameAndProps(expression, parser) { | ||
/** | ||
* We just generate the acorn AST only, since we don't want parser to transform | ||
* ast to edge statements for a `@slot` tag. | ||
*/ | ||
const parsed = parser.generateAcornExpression(expression.properties.jsArg, expression.loc).expression; | ||
utils_1.allowExpressions(parsed, [edge_parser_1.expressions.Literal, edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${expression.properties.jsArg}} is not a valid argument type for the @slot tag`); | ||
/** | ||
* Fetch the slot name | ||
*/ | ||
let name; | ||
@@ -47,6 +89,17 @@ if (parsed.type === edge_parser_1.expressions.SequenceExpression) { | ||
} | ||
/** | ||
* Validating the slot name to be a literal value, since slot names cannot be dynamic | ||
*/ | ||
utils_1.allowExpressions(name, [edge_parser_1.expressions.Literal], parser.options.filename, 'slot name must be a valid string literal'); | ||
/** | ||
* Return the slot name with empty props, when the expression is a literal | ||
* value. | ||
*/ | ||
if (parsed.type === edge_parser_1.expressions.Literal) { | ||
return [name.raw, null]; | ||
} | ||
/** | ||
* Make sure the sequence expression has only 2 arguments in it. Though it doesn't hurt | ||
* the rendering of component, we must not run code with false expectations. | ||
*/ | ||
if (parsed.expressions.length > 2) { | ||
@@ -60,4 +113,11 @@ throw new edge_error_1.EdgeError('maximum of 2 arguments are allowed for @slot tag', 'E_MAX_ARGUMENTS', { | ||
utils_1.allowExpressions(parsed.expressions[1], [edge_parser_1.expressions.Identifier], parser.options.filename, `{${parser.stringifyExpression(parsed.expressions[1])}} is not valid prop identifier for @slot tag`); | ||
/** | ||
* Returning the slot name and slot props name | ||
*/ | ||
return [name.raw, parsed.expressions[1].name]; | ||
} | ||
/** | ||
* The component tag implementation. It is one of the most complex tags and | ||
* can be used as a reference for creating other tags. | ||
*/ | ||
exports.componentTag = { | ||
@@ -69,4 +129,15 @@ block: true, | ||
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
/** | ||
* Check component js props for allowed expressions | ||
*/ | ||
utils_1.allowExpressions(parsed, componentNameAllowedExpressions.concat(edge_parser_1.expressions.SequenceExpression), parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @component tag`); | ||
/** | ||
* Pulling the name and props for the component. The underlying method will | ||
* ensure that the arguments passed to component tag are valid | ||
*/ | ||
const [name, props] = getComponentNameAndProps(parsed, parser); | ||
/** | ||
* Loop over all the children and set them as part of slots. If no slot | ||
* is defined, then the content will be part of the main slot | ||
*/ | ||
const slots = {}; | ||
@@ -76,7 +147,16 @@ token.children.forEach((child, index) => { | ||
let slotProps = null; | ||
/** | ||
* Update the slot name and props when a new slot is detected | ||
*/ | ||
if (utils_1.isBlockToken(child, 'slot')) { | ||
[slotName, slotProps] = getSlotNameAndProps(child, parser); | ||
} | ||
/** | ||
* Create a new slot with buffer to process the childs | ||
*/ | ||
if (!slots[slotName]) { | ||
slots[slotName] = { buffer: new edge_parser_1.EdgeBuffer(`slot_${index}`), props: slotProps }; | ||
/** | ||
* Only start the frame, when there are props in use for a given slot. | ||
*/ | ||
if (slotProps) { | ||
@@ -87,2 +167,7 @@ slots[slotName].buffer.writeStatement('ctx.newFrame();'); | ||
} | ||
/** | ||
* We must process slot of a component by ourselves (instead of making slot tag) | ||
* process it. This way, we can disallow slots appearing outside the component | ||
* tag | ||
*/ | ||
if (utils_1.isBlockToken(child, 'slot')) { | ||
@@ -95,4 +180,11 @@ child.children.forEach((token) => parser.processLexerToken(token, slots[slotName].buffer)); | ||
}); | ||
/** | ||
* We convert the slots to an objectified string, that is passed to `template.renderWithState`, | ||
* which will pass it to the component as it's local state. | ||
*/ | ||
const obj = new StringifiedObject_1.StringifiedObject(); | ||
Object.keys(slots).forEach((slot) => { | ||
/** | ||
* Cleanup the previously started frame scope | ||
*/ | ||
if (slots[slot].props) { | ||
@@ -105,4 +197,7 @@ slots[slot].buffer.writeStatement('ctx.removeFrame();'); | ||
}); | ||
/** | ||
* Write the line to render the component with it's own state | ||
*/ | ||
buffer.writeLine(`template.renderWithState(${name}, ${props}, ${obj.flush()})`); | ||
}, | ||
}; |
@@ -0,2 +1,12 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Add debugger break point to the compiled template | ||
* | ||
* ```edge | ||
* @debugger | ||
* ``` | ||
*/ | ||
export declare const debuggerTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Add debugger break point to the compiled template | ||
* | ||
* ```edge | ||
* @debugger | ||
* ``` | ||
*/ | ||
exports.debuggerTag = { | ||
@@ -7,2 +17,5 @@ block: false, | ||
tagName: 'debugger', | ||
/** | ||
* Compiles else block node to Javascript else statement | ||
*/ | ||
compile(_parser, buffer) { | ||
@@ -9,0 +22,0 @@ buffer.writeStatement('debugger;'); |
@@ -0,2 +1,14 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Each tag is used to run a foreach loop on arrays and even objects. | ||
* | ||
* ```edge | ||
* @each(user in users) | ||
* {{ user }} {{ $loop.index }} | ||
* @endeach | ||
* ``` | ||
*/ | ||
export declare const eachTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const lodash_1 = require("lodash"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Returns the list to loop over for the each expression | ||
*/ | ||
function getLoopList(expression, parser) { | ||
return parser.stringifyExpression(parser.acornToEdgeExpression(expression)); | ||
} | ||
/** | ||
* Returns loop item and the index for the each expression | ||
*/ | ||
function getLoopItemAndIndex(expression, filename) { | ||
utils_1.allowExpressions(expression, [edge_parser_1.expressions.SequenceExpression, edge_parser_1.expressions.Identifier], filename, `invalid left hand side expression for the @each tag`); | ||
/** | ||
* Return list index from the sequence expression | ||
*/ | ||
if (expression.type === 'SequenceExpression') { | ||
@@ -18,2 +38,11 @@ utils_1.allowExpressions(expression.expressions[0], [edge_parser_1.expressions.Identifier], filename, `invalid expression for {value} identifier for the @each tag`); | ||
} | ||
/** | ||
* Each tag is used to run a foreach loop on arrays and even objects. | ||
* | ||
* ```edge | ||
* @each(user in users) | ||
* {{ user }} {{ $loop.index }} | ||
* @endeach | ||
* ``` | ||
*/ | ||
exports.eachTag = { | ||
@@ -23,9 +52,28 @@ block: true, | ||
tagName: 'each', | ||
/** | ||
* Compile the template | ||
*/ | ||
compile(parser, buffer, token) { | ||
/** | ||
* Instead of using `parser.generateEdgeExpression`, we make use of | ||
* `generateAcornExpression`, since we do not want to process | ||
* `Indentifer` expressions as per the normal parsing logic | ||
* and just want to extract it's name. | ||
*/ | ||
const parsed = parser.generateAcornExpression(token.properties.jsArg, token.loc).expression; | ||
utils_1.allowExpressions(parsed, [edge_parser_1.expressions.BinaryExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid binary expression for the @each tag`); | ||
/** | ||
* Finding if an else child exists inside the each tag | ||
*/ | ||
const elseIndex = token.children.findIndex((child) => utils_1.isBlockToken(child, 'else')); | ||
const elseChild = elseIndex > -1 ? token.children.splice(elseIndex) : []; | ||
/** | ||
* Fetching the item,index and list for the each loop | ||
*/ | ||
const [item, index] = getLoopItemAndIndex(parsed.left, parser.options.filename); | ||
const list = getLoopList(parsed.right, parser); | ||
/** | ||
* If there is an else statement, then wrap the loop | ||
* inside the `if` statement first | ||
*/ | ||
if (elseIndex > -1) { | ||
@@ -35,14 +83,44 @@ buffer.writeStatement(`if(ctx.size(${list})) {`); | ||
} | ||
/** | ||
* Write the loop statement to the template | ||
*/ | ||
buffer.writeStatement(`ctx.loop(${list}, function (${item}, loop) {`); | ||
/** | ||
* Indent block | ||
*/ | ||
buffer.indent(); | ||
/** | ||
* Start a new context frame. Frame ensures the value inside | ||
* the loop is given priority over top level values. Think | ||
* of it as a Javascript block scope. | ||
*/ | ||
buffer.writeStatement('ctx.newFrame();'); | ||
/** | ||
* Set key and value pair on the context | ||
*/ | ||
buffer.writeStatement(`ctx.setOnFrame('${item}', ${item});`); | ||
buffer.writeStatement(`ctx.setOnFrame('$loop', loop);`); | ||
buffer.writeStatement(`ctx.setOnFrame('${index}', loop.key);`); | ||
/** | ||
* Process all kids | ||
*/ | ||
token.children.forEach((child) => { | ||
parser.processLexerToken(child, buffer); | ||
}); | ||
/** | ||
* Remove the frame | ||
*/ | ||
buffer.writeStatement('ctx.removeFrame();'); | ||
/** | ||
* Dedent block | ||
*/ | ||
buffer.dedent(); | ||
/** | ||
* Close each loop | ||
*/ | ||
buffer.writeStatement('});'); | ||
/** | ||
* If there is an else statement, then process | ||
* else childs and close the if block | ||
*/ | ||
if (elseIndex > -1) { | ||
@@ -54,2 +132,5 @@ elseChild.forEach((child) => parser.processLexerToken(child, buffer)); | ||
}, | ||
/** | ||
* Add methods to the runtime context for running the loop | ||
*/ | ||
run(context) { | ||
@@ -56,0 +137,0 @@ context.macro('loop', function loop(source, callback) { |
@@ -0,2 +1,5 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
export declare const elseTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -7,2 +10,5 @@ exports.elseTag = { | ||
tagName: 'else', | ||
/** | ||
* Compiles else block node to Javascript else statement | ||
*/ | ||
compile(_parser, buffer) { | ||
@@ -9,0 +15,0 @@ buffer.dedent(); |
@@ -0,2 +1,16 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Else if tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* // If | ||
* @elseif(user.username) | ||
* // Else if | ||
* @endif | ||
* ``` | ||
*/ | ||
export declare const elseIfTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Else if tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* // If | ||
* @elseif(user.username) | ||
* // Else if | ||
* @endif | ||
* ``` | ||
*/ | ||
exports.elseIfTag = { | ||
@@ -9,9 +31,21 @@ block: false, | ||
tagName: 'elseif', | ||
/** | ||
* Compiles the else if block node to a Javascript if statement | ||
*/ | ||
compile(parser, buffer, token) { | ||
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
utils_1.disAllowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`); | ||
/** | ||
* Dedent block | ||
*/ | ||
buffer.dedent(); | ||
/** | ||
* Start else block | ||
*/ | ||
buffer.writeStatement(`} else if(${parser.stringifyExpression(parsed)}) {`); | ||
/** | ||
* Indent block again | ||
*/ | ||
buffer.indent(); | ||
}, | ||
}; |
@@ -0,2 +1,13 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* If tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* @endif | ||
* ``` | ||
*/ | ||
export declare const ifTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* If tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* @endif | ||
* ``` | ||
*/ | ||
exports.ifTag = { | ||
@@ -9,11 +28,29 @@ block: true, | ||
tagName: 'if', | ||
/** | ||
* Compiles the if block node to a Javascript if statement | ||
*/ | ||
compile(parser, buffer, token) { | ||
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
utils_1.disAllowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @if tag`); | ||
/** | ||
* Start if block | ||
*/ | ||
buffer.writeStatement(`if(${parser.stringifyExpression(parsed)}) {`); | ||
/** | ||
* Indent upcoming code | ||
*/ | ||
buffer.indent(); | ||
/** | ||
* Process of all kids recursively | ||
*/ | ||
token.children.forEach((child) => parser.processLexerToken(child, buffer)); | ||
/** | ||
* Remove identation | ||
*/ | ||
buffer.dedent(); | ||
/** | ||
* Close if block | ||
*/ | ||
buffer.writeStatement('}'); | ||
}, | ||
}; |
@@ -0,2 +1,13 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Include tag is used to include partials in the same scope of the parent | ||
* template. | ||
* | ||
* ```edge | ||
* @include('partials.header') | ||
* ``` | ||
*/ | ||
export declare const includeTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Include tag is used to include partials in the same scope of the parent | ||
* template. | ||
* | ||
* ```edge | ||
* @include('partials.header') | ||
* ``` | ||
*/ | ||
exports.includeTag = { | ||
@@ -9,2 +28,5 @@ block: false, | ||
tagName: 'include', | ||
/** | ||
* Compiles else block node to Javascript else statement | ||
*/ | ||
compile(parser, buffer, token) { | ||
@@ -21,4 +43,9 @@ const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @include tag`); | ||
/** | ||
* Include template. Since the partials can be a runtime value, we cannot inline | ||
* the content right now and have to defer to runtime to get the value of | ||
* the partial and then process it | ||
*/ | ||
buffer.writeLine(`template.renderInline(${parser.stringifyExpression(parsed)})(template, ctx)`); | ||
}, | ||
}; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
export { ifTag as if } from './If'; | ||
@@ -2,0 +5,0 @@ export { elseTag as else } from './Else'; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
var If_1 = require("./If"); | ||
@@ -4,0 +15,0 @@ exports.if = If_1.ifTag; |
@@ -0,2 +1,9 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Layout tag is used to define parent layout for a given template. The layout | ||
* must appear in the first line of the template itself. | ||
*/ | ||
export declare const layoutTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Layout tag is used to define parent layout for a given template. The layout | ||
* must appear in the first line of the template itself. | ||
*/ | ||
exports.layoutTag = { | ||
@@ -8,3 +15,5 @@ block: false, | ||
compile() { | ||
// The layouts are handled by the template itself. I am just a way to | ||
// tell lexer to parse me as a block node | ||
}, | ||
}; |
@@ -0,2 +1,9 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Section tag is used to define the sections on a given template. Sections cannot be | ||
* nested and must appear as top level children inside a component. | ||
*/ | ||
export declare const sectionTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Section tag is used to define the sections on a given template. Sections cannot be | ||
* nested and must appear as top level children inside a component. | ||
*/ | ||
exports.sectionTag = { | ||
@@ -4,0 +11,0 @@ block: true, |
@@ -0,2 +1,23 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* The set tag is used to set runtime values within the template. The value | ||
* is set inside the current scope of the template. | ||
* | ||
* ```edge | ||
* @set('user.username', 'virk') | ||
* <p> {{ user.username }} </p> | ||
* ``` | ||
* | ||
* Set it inside the each loop. | ||
* | ||
* ```edge | ||
* @each(user in users) | ||
* @set('age', user.age + 1) | ||
* {{ age }} | ||
* @endeach | ||
* ``` | ||
*/ | ||
export declare const setTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const edge_error_1 = require("edge-error"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* The set tag is used to set runtime values within the template. The value | ||
* is set inside the current scope of the template. | ||
* | ||
* ```edge | ||
* @set('user.username', 'virk') | ||
* <p> {{ user.username }} </p> | ||
* ``` | ||
* | ||
* Set it inside the each loop. | ||
* | ||
* ```edge | ||
* @each(user in users) | ||
* @set('age', user.age + 1) | ||
* {{ age }} | ||
* @endeach | ||
* ``` | ||
*/ | ||
exports.setTag = { | ||
@@ -10,5 +39,14 @@ block: false, | ||
tagName: 'set', | ||
/** | ||
* Compiles else block node to Javascript else statement | ||
*/ | ||
compile(parser, buffer, token) { | ||
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
/** | ||
* The set tag only accepts a sequence expression. | ||
*/ | ||
utils_1.allowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid key-value pair for the @slot tag`); | ||
/** | ||
* Disallow more than 2 values for the sequence expression | ||
*/ | ||
if (parsed.expressions.length > 2) { | ||
@@ -22,5 +60,11 @@ throw new edge_error_1.EdgeError(`maximum of 2 arguments are allowed for the @set tag`, 'E_MAX_ARGUMENTS', { | ||
const [key, value] = parsed.expressions; | ||
/** | ||
* The key has to be a literal value | ||
*/ | ||
utils_1.allowExpressions(key, [edge_parser_1.expressions.Literal], parser.options.filename, 'The first argument for @set tag must be a string literal'); | ||
/** | ||
* Write statement to mutate the key | ||
*/ | ||
buffer.writeStatement(`ctx.set(${key.raw}, ${parser.stringifyExpression(value)});`); | ||
}, | ||
}; |
@@ -0,2 +1,9 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Slot tag is used to define the slots of a given component. Slots cannot be | ||
* nested and must appear as top level children inside a component. | ||
*/ | ||
export declare const slotTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_error_1 = require("edge-error"); | ||
/** | ||
* Slot tag is used to define the slots of a given component. Slots cannot be | ||
* nested and must appear as top level children inside a component. | ||
*/ | ||
exports.slotTag = { | ||
@@ -5,0 +20,0 @@ block: true, |
@@ -0,2 +1,12 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Super tag is used inside sections to inherit the parent section | ||
* content. | ||
* | ||
* The implementation of super tag is handled by the compiler itself, but we need | ||
* the tag to exists, so that the lexer can parse it as a tag. | ||
*/ | ||
export declare const superTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Super tag is used inside sections to inherit the parent section | ||
* content. | ||
* | ||
* The implementation of super tag is handled by the compiler itself, but we need | ||
* the tag to exists, so that the lexer can parse it as a tag. | ||
*/ | ||
exports.superTag = { | ||
@@ -8,3 +18,5 @@ block: false, | ||
compile() { | ||
// The super tag is handled by the compiler itself. I am just a way to | ||
// tell lexer to parse me as an inline node | ||
}, | ||
}; |
@@ -0,2 +1,15 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Inverse of the `if` condition. The term `unless` is more readable and logical | ||
* vs using `@if(!expression)`. | ||
* | ||
* ```edge | ||
* @unless(auth.user) | ||
* <a href="/login"> Login </a> | ||
* @endunless | ||
* ``` | ||
*/ | ||
export declare const unlessTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Inverse of the `if` condition. The term `unless` is more readable and logical | ||
* vs using `@if(!expression)`. | ||
* | ||
* ```edge | ||
* @unless(auth.user) | ||
* <a href="/login"> Login </a> | ||
* @endunless | ||
* ``` | ||
*/ | ||
exports.unlessTag = { | ||
@@ -9,13 +30,31 @@ block: true, | ||
tagName: 'unless', | ||
/** | ||
* Compiles the if block node to a Javascript if statement | ||
*/ | ||
compile(parser, buffer, token) { | ||
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
utils_1.disAllowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @unless tag`); | ||
/** | ||
* Start if block | ||
*/ | ||
buffer.writeStatement(`if(!${parser.stringifyExpression(parsed)}) {`); | ||
/** | ||
* Indent upcoming code | ||
*/ | ||
buffer.indent(); | ||
/** | ||
* Process of all kids recursively | ||
*/ | ||
token.children.forEach((child) => { | ||
parser.processLexerToken(child, buffer); | ||
}); | ||
/** | ||
* Remove identation | ||
*/ | ||
buffer.dedent(); | ||
/** | ||
* Close if block | ||
*/ | ||
buffer.writeStatement('}'); | ||
}, | ||
}; |
@@ -0,2 +1,24 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* Yield tag is a shorthand of `if/else` for markup based content. | ||
* | ||
* ```edge | ||
* @yield($slots.main) | ||
* <p> This is the fallback content, when $slots.main is missing </p> | ||
* @endyield | ||
* ``` | ||
* | ||
* The longer version of above is following | ||
* | ||
* ```@edge | ||
* @if ($slots.main) | ||
* {{{ $slots.main }}} | ||
* @else | ||
* <p> This is the fallback content, when $slots.main is missing </p> | ||
* @endif | ||
* ``` | ||
*/ | ||
export declare const yieldTag: TagContract; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const edge_parser_1 = require("edge-parser"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Yield tag is a shorthand of `if/else` for markup based content. | ||
* | ||
* ```edge | ||
* @yield($slots.main) | ||
* <p> This is the fallback content, when $slots.main is missing </p> | ||
* @endyield | ||
* ``` | ||
* | ||
* The longer version of above is following | ||
* | ||
* ```@edge | ||
* @if ($slots.main) | ||
* {{{ $slots.main }}} | ||
* @else | ||
* <p> This is the fallback content, when $slots.main is missing </p> | ||
* @endif | ||
* ``` | ||
*/ | ||
exports.yieldTag = { | ||
@@ -9,2 +39,5 @@ block: true, | ||
tagName: 'yield', | ||
/** | ||
* Compiles the if block node to a Javascript if statement | ||
*/ | ||
compile(parser, buffer, token) { | ||
@@ -14,2 +47,5 @@ const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc); | ||
const parsedString = parser.stringifyExpression(parsed); | ||
/** | ||
* Write main content when it's truthy | ||
*/ | ||
buffer.writeStatement(`if(${parsedString}) {`); | ||
@@ -19,2 +55,5 @@ buffer.indent(); | ||
buffer.dedent(); | ||
/** | ||
* Else write fallback | ||
*/ | ||
if (!token.properties.selfclosed) { | ||
@@ -21,0 +60,0 @@ buffer.writeStatement('} else {'); |
@@ -0,9 +1,49 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { CompilerContract } from '../Contracts'; | ||
/** | ||
* The template is used to compile and run templates. Also the instance | ||
* of template is passed during runtime to render `dynamic partials` | ||
* and `dynamic components`. | ||
*/ | ||
export declare class Template { | ||
private _compiler; | ||
/** | ||
* The shared state is used to hold the globals and locals, | ||
* since it is shared with components too. | ||
*/ | ||
private _sharedState; | ||
constructor(_compiler: CompilerContract, globals: any, locals: any); | ||
/** | ||
* Render the template inline by sharing the state of the current template. | ||
* | ||
* ```js | ||
* const partialFn = template.renderInline('includes.user') | ||
* | ||
* // render and use output | ||
* partialFn(template, ctx) | ||
* ``` | ||
*/ | ||
renderInline(templatePath: string): Function; | ||
/** | ||
* Renders the template with custom state. The `sharedState` of the template is still | ||
* passed to this template. | ||
* | ||
* Also a set of custom slots can be passed along. The slots uses the state of the parent | ||
* template. | ||
* | ||
* ```js | ||
* template.renderWithState('components.user', { username: 'virk' }, slotsIfAny) | ||
* ``` | ||
*/ | ||
renderWithState(template: string, state: any, slots: any): string; | ||
/** | ||
* Render a template with it's state. | ||
* | ||
* ```js | ||
* template.render('welcome', { key: 'value' }) | ||
* ``` | ||
*/ | ||
render(template: string, state: any): string; | ||
} |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const lodash_1 = require("lodash"); | ||
const Context_1 = require("../Context"); | ||
const Presenter_1 = require("../Presenter"); | ||
/** | ||
* The template is used to compile and run templates. Also the instance | ||
* of template is passed during runtime to render `dynamic partials` | ||
* and `dynamic components`. | ||
*/ | ||
class Template { | ||
@@ -11,5 +27,26 @@ constructor(_compiler, globals, locals) { | ||
} | ||
/** | ||
* Render the template inline by sharing the state of the current template. | ||
* | ||
* ```js | ||
* const partialFn = template.renderInline('includes.user') | ||
* | ||
* // render and use output | ||
* partialFn(template, ctx) | ||
* ``` | ||
*/ | ||
renderInline(templatePath) { | ||
return new Function('template', 'ctx', this._compiler.compile(templatePath, true).template); | ||
} | ||
/** | ||
* Renders the template with custom state. The `sharedState` of the template is still | ||
* passed to this template. | ||
* | ||
* Also a set of custom slots can be passed along. The slots uses the state of the parent | ||
* template. | ||
* | ||
* ```js | ||
* template.renderWithState('components.user', { username: 'virk' }, slotsIfAny) | ||
* ``` | ||
*/ | ||
renderWithState(template, state, slots) { | ||
@@ -21,2 +58,9 @@ const { template: compiledTemplate, Presenter } = this._compiler.compile(template, false); | ||
} | ||
/** | ||
* Render a template with it's state. | ||
* | ||
* ```js | ||
* template.render('welcome', { key: 'value' }) | ||
* ``` | ||
*/ | ||
render(template, state) { | ||
@@ -23,0 +67,0 @@ const { template: compiledTemplate, Presenter } = this._compiler.compile(template, false); |
@@ -0,7 +1,66 @@ | ||
/** | ||
* @module edge | ||
*/ | ||
import { Parser, expressions as expressionsList, ParserToken, ParserTagToken } from 'edge-parser'; | ||
/** | ||
* Validates the expression type to be part of the allowed | ||
* expressions only. | ||
* | ||
* The filename is required to report errors. | ||
* | ||
* ```js | ||
* allowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge') | ||
* ``` | ||
*/ | ||
export declare function allowExpressions(expression: any, expressions: (keyof typeof expressionsList)[], filename: string, message: string): void; | ||
/** | ||
* Validates the expression type not to be part of the disallowed | ||
* expressions. | ||
* | ||
* The filename is required to report errors. | ||
* | ||
* ```js | ||
* disAllowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge') | ||
* ``` | ||
*/ | ||
export declare function disAllowExpressions(expression: any, expressions: (keyof typeof expressionsList)[], filename: string, message: string): void; | ||
/** | ||
* 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' } | ||
* ``` | ||
*/ | ||
export declare function expressionsToStringifyObject(expressions: any[], parser: Parser): string; | ||
/** | ||
* 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'] | ||
* ``` | ||
*/ | ||
export declare function extractDiskAndTemplateName(templatePath: string): [string, string]; | ||
/** | ||
* Returns a boolean, telling whether the lexer node is a block node | ||
* or not. | ||
*/ | ||
export declare function isBlockToken(token: ParserToken, name: string): token is ParserTagToken; | ||
/** | ||
* Returns line and number for a given AST token | ||
*/ | ||
export declare function getLineAndColumnForToken(token: ParserToken): [number, number]; |
"use strict"; | ||
/** | ||
* @module edge | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* 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. | ||
*/ | ||
const path_1 = require("path"); | ||
@@ -7,2 +18,12 @@ const edge_error_1 = require("edge-error"); | ||
const StringifiedObject_1 = require("../StringifiedObject"); | ||
/** | ||
* Validates the expression type to be part of the allowed | ||
* expressions only. | ||
* | ||
* The filename is required to report errors. | ||
* | ||
* ```js | ||
* allowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge') | ||
* ``` | ||
*/ | ||
function allowExpressions(expression, expressions, filename, message) { | ||
@@ -18,2 +39,12 @@ if (!expressions.includes(expression.type)) { | ||
exports.allowExpressions = allowExpressions; | ||
/** | ||
* Validates the expression type not to be part of the disallowed | ||
* expressions. | ||
* | ||
* The filename is required to report errors. | ||
* | ||
* ```js | ||
* disAllowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge') | ||
* ``` | ||
*/ | ||
function disAllowExpressions(expression, expressions, filename, message) { | ||
@@ -29,2 +60,17 @@ if (expressions.includes(expression.type)) { | ||
exports.disAllowExpressions = disAllowExpressions; | ||
/** | ||
* 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' } | ||
* ``` | ||
*/ | ||
function expressionsToStringifyObject(expressions, parser) { | ||
@@ -47,2 +93,16 @@ const objectifyString = new StringifiedObject_1.StringifiedObject(); | ||
exports.expressionsToStringifyObject = expressionsToStringifyObject; | ||
/** | ||
* 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'] | ||
* ``` | ||
*/ | ||
function extractDiskAndTemplateName(templatePath) { | ||
@@ -58,2 +118,6 @@ let [disk, ...rest] = templatePath.split('::'); | ||
exports.extractDiskAndTemplateName = extractDiskAndTemplateName; | ||
/** | ||
* Returns a boolean, telling whether the lexer node is a block node | ||
* or not. | ||
*/ | ||
function isBlockToken(token, name) { | ||
@@ -66,2 +130,5 @@ if (token.type === edge_lexer_1.TagTypes.TAG || token.type === edge_lexer_1.TagTypes.ETAG) { | ||
exports.isBlockToken = isBlockToken; | ||
/** | ||
* Returns line and number for a given AST token | ||
*/ | ||
function getLineAndColumnForToken(token) { | ||
@@ -68,0 +135,0 @@ if (token.type === 'newline' || token.type === 'raw') { |
{ | ||
"name": "edge.js", | ||
"version": "2.0.2", | ||
"version": "2.1.0", | ||
"description": "Template engine", | ||
@@ -35,7 +35,7 @@ "main": "build/index.js", | ||
"devDependencies": { | ||
"@adonisjs/mrm-preset": "^2.1.0", | ||
"@poppinss/dev-utils": "^1.0.1", | ||
"@types/node": "^12.7.5", | ||
"@adonisjs/mrm-preset": "^2.2.4", | ||
"@poppinss/dev-utils": "^1.0.4", | ||
"@types/node": "^13.7.6", | ||
"commitizen": "^4.0.3", | ||
"cz-conventional-changelog": "^3.0.2", | ||
"cz-conventional-changelog": "^3.1.0", | ||
"dedent": "^0.7.0", | ||
@@ -45,13 +45,13 @@ "del-cli": "^3.0.0", | ||
"fs-extra": "^8.1.0", | ||
"husky": "^3.0.5", | ||
"husky": "^4.2.3", | ||
"japa": "^3.0.1", | ||
"mrm": "^1.2.2", | ||
"np": "^5.1.0", | ||
"ts-node": "^8.4.1", | ||
"tslint": "^5.20.0", | ||
"mrm": "^2.1.0", | ||
"np": "^5.2.1", | ||
"ts-node": "^8.6.2", | ||
"tslint": "^6.0.0", | ||
"tslint-eslint-rules": "^5.4.0", | ||
"typedoc": "^0.15.0", | ||
"typedoc": "^0.16.10", | ||
"typedoc-clarity-theme": "^1.1.0", | ||
"typedoc-plugin-external-module-name": "^2.1.0", | ||
"typescript": "^3.6.3", | ||
"typedoc-plugin-external-module-name": "^3.0.0", | ||
"typescript": "^3.8.2", | ||
"yorkie": "^2.0.0", | ||
@@ -74,11 +74,9 @@ "youch": "^2.0.10" | ||
"dependencies": { | ||
"@poppinss/utils": "^2.0.0", | ||
"debug": "^4.1.1", | ||
"@poppinss/utils": "^2.1.2", | ||
"edge-error": "^1.0.4", | ||
"edge-parser": "^2.1.1", | ||
"edge-parser": "^2.1.2", | ||
"he": "^1.2.0", | ||
"import-fresh": "^3.1.0", | ||
"import-fresh": "^3.2.1", | ||
"lodash": "^4.17.15", | ||
"macroable": "^2.0.2", | ||
"node-exceptions": "^4.0.1" | ||
"macroable": "^4.0.2" | ||
}, | ||
@@ -85,0 +83,0 @@ "gitHooks": { |
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
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
102137
7
60
3023
+ Addedmacroable@4.0.4(transitive)
- Removeddebug@^4.1.1
- Removednode-exceptions@^4.0.1
- Removeddebug@4.3.7(transitive)
- Removedmacroable@2.0.2(transitive)
- Removednode-exceptions@4.0.1(transitive)
Updated@poppinss/utils@^2.1.2
Updatededge-parser@^2.1.2
Updatedimport-fresh@^3.2.1
Updatedmacroable@^4.0.2