diff --git a/bin/test.ts b/bin/test.ts index f09b1c3..795621c 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -18,7 +18,7 @@ import { BASE_URL } from '../test_helpers/index.js' */ processCLIArgs(process.argv.slice(2)) configure({ - files: ['test/**/*.spec.ts'], + files: ['tests/**/*.spec.ts'], plugins: [assert(), fileSystem({ basePath: BASE_URL })], }) diff --git a/index.ts b/index.ts index f1ee5e8..57239a8 100644 --- a/index.ts +++ b/index.ts @@ -8,7 +8,7 @@ */ import { Edge } from './src/edge/index.js' -import { safeValue } from './src/template/index.js' +import { safeValue } from './src/template.js' import { GLOBALS } from './src/edge/globals/index.js' /** diff --git a/src/cache_manager/index.ts b/src/cache_manager.ts similarity index 82% rename from src/cache_manager/index.ts rename to src/cache_manager.ts index 942a728..3cac1f8 100644 --- a/src/cache_manager/index.ts +++ b/src/cache_manager.ts @@ -7,13 +7,13 @@ * file that was distributed with this source code. */ -import type { LoaderTemplate, CacheManagerContract } from '../types.js' +import type { CacheManagerContract, CompiledTemplate } from './types.js' /** * In memory cache manager to cache pre-compiled templates. */ export class CacheManager implements CacheManagerContract { - #cacheStore: Map = new Map() + #cacheStore: Map = new Map() constructor(public enabled: boolean) {} @@ -29,7 +29,7 @@ export class CacheManager implements CacheManagerContract { * Returns the template from the cache. If caching is disabled, * then it will return undefined. */ - get(absPath: string): undefined | LoaderTemplate { + get(absPath: string): undefined | CompiledTemplate { if (!this.enabled) { return } @@ -41,7 +41,7 @@ export class CacheManager implements CacheManagerContract { * 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) { + set(absPath: string, payload: CompiledTemplate) { if (!this.enabled) { return } diff --git a/src/compiler/index.ts b/src/compiler.ts similarity index 81% rename from src/compiler/index.ts rename to src/compiler.ts index 4bb1926..abe5ba1 100644 --- a/src/compiler/index.ts +++ b/src/compiler.ts @@ -8,25 +8,38 @@ */ import { EdgeError } from 'edge-error' +import * as lexerUtils from 'edge-lexer/utils' import { Parser, EdgeBuffer, Stack } from 'edge-parser' import type { Token, TagToken } from 'edge-lexer/types' -import * as lexerUtils from 'edge-lexer/utils' -import { Processor } from '../processor/index.js' -import { CacheManager } from '../cache_manager/index.js' + +import { Processor } from './processor.js' +import { CacheManager } from './cache_manager.js' import type { ClaimTagFn, TagsContract, LoaderContract, - LoaderTemplate, CompilerOptions, - CompilerContract, -} from '../types.js' + CompiledTemplate, +} from './types.js' + +const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor /** * 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. */ -export class Compiler implements CompilerContract { +export class Compiler { + /** + * The variables someone can access inside templates. All other + * variables will get prefixed with `state` property name + */ + #inlineVariables: string[] = ['$filename', 'state', '$context'] + + /** + * A fixed set of params to pass to the template every time. + */ + #templateParams = ['template', 'state', '$context'] + #claimTagFn?: ClaimTagFn #loader: LoaderContract #tags: TagsContract @@ -38,7 +51,7 @@ export class Compiler implements CompilerContract { cacheManager: CacheManager /** - * Know if compiler is compiling for the async mode or not + * Know if compiler is compiling in the async mode or not */ async: boolean @@ -183,7 +196,8 @@ export class Compiler implements CompilerContract { async: this.async, statePropertyName: 'state', escapeCallPath: ['template', 'escape'], - localVariables: ['$filename', 'state', '$context'], + toAttributesCallPath: ['template', 'toAttributes'], + localVariables: this.#inlineVariables, onTag: (tag) => this.#processor.executeTag({ tag, path: templatePath }), }) @@ -209,6 +223,19 @@ export class Compiler implements CompilerContract { }) } + /** + * Wraps template output to a function along with local variables + */ + #wrapToFunction(template: string, localVariables?: string[]): CompiledTemplate { + const args = localVariables ? this.#templateParams.concat(localVariables) : this.#templateParams + + if (this.async) { + return new AsyncFunction(...args, template) + } + + return new Function(...args, template) as CompiledTemplate + } + /** * Define a function to claim tags */ @@ -252,9 +279,9 @@ export class Compiler implements CompilerContract { * compiler.compile('welcome') * ``` */ - compile(templatePath: string, localVariables?: string[], skipCache = false): LoaderTemplate { + compile(templatePath: string, localVariables?: string[]): CompiledTemplate { const absPath = this.#loader.makePath(templatePath) - let cachedResponse = skipCache ? null : this.cacheManager.get(absPath) + let cachedResponse = localVariables ? null : this.cacheManager.get(absPath) /** * Process the template and cache it @@ -269,20 +296,23 @@ export class Compiler implements CompilerContract { const templateTokens = this.tokenize(absPath, parser) templateTokens.forEach((token) => parser.processToken(token, buffer)) - const template = buffer.flush() - if (!skipCache) { - this.cacheManager.set(absPath, { template }) + /** + * Processing template via hook + */ + const template = this.#processor.executeCompiled({ + path: absPath, + compiled: buffer.flush(), + }) + + const compiledTemplate = this.#wrapToFunction(template, localVariables) + if (!localVariables) { + this.cacheManager.set(absPath, compiledTemplate) } - cachedResponse = { template } + cachedResponse = compiledTemplate } - const template = this.#processor.executeCompiled({ - path: absPath, - compiled: cachedResponse.template, - }) - - return { template } + return cachedResponse! } /** @@ -291,10 +321,10 @@ export class Compiler implements CompilerContract { * handles layouts. * * ```js - * compiler.compile('welcome') + * compiler.compileRaw('welcome') * ``` */ - compileRaw(contents: string, templatePath: string = 'eval.edge'): LoaderTemplate { + compileRaw(contents: string, templatePath: string = 'eval.edge'): CompiledTemplate { const parser = this.#getParserFor(templatePath) const buffer = this.#getBufferFor(templatePath) const templateTokens = this.tokenizeRaw(contents, templatePath, parser) @@ -306,6 +336,6 @@ export class Compiler implements CompilerContract { compiled: buffer.flush(), }) - return { template } + return this.#wrapToFunction(template) } } diff --git a/src/component/props.ts b/src/component/props.ts index 75b751e..a930297 100644 --- a/src/component/props.ts +++ b/src/component/props.ts @@ -10,7 +10,7 @@ import lodash from '@poppinss/utils/lodash' import stringifyAttributes from 'stringify-attributes' -import { safeValue } from '../template/index.js' +import { htmlSafe } from '../template.js' import { PropsContract } from '../types.js' /** @@ -102,7 +102,7 @@ export class Props implements PropsContract { ? lodash.merge({}, this.all(), mergeProps) : lodash.merge({}, mergeProps, this.all()) - return safeValue(stringifyAttributes(this.#mergeClassAttributes(attributes))) + return htmlSafe(stringifyAttributes(this.#mergeClassAttributes(attributes))) } /** @@ -116,7 +116,7 @@ export class Props implements PropsContract { ? lodash.merge({}, this.only(keys), mergeProps) : lodash.merge({}, mergeProps, this.only(keys)) - return safeValue(stringifyAttributes(this.#mergeClassAttributes(attributes))) + return htmlSafe(stringifyAttributes(this.#mergeClassAttributes(attributes))) } /** @@ -130,6 +130,6 @@ export class Props implements PropsContract { ? lodash.merge({}, this.except(keys), mergeProps) : lodash.merge({}, mergeProps, this.except(keys)) - return safeValue(stringifyAttributes(this.#mergeClassAttributes(attributes))) + return htmlSafe(stringifyAttributes(this.#mergeClassAttributes(attributes))) } } diff --git a/src/edge/globals/index.ts b/src/edge/globals/index.ts index 96d033c..2d1c188 100644 --- a/src/edge/globals/index.ts +++ b/src/edge/globals/index.ts @@ -15,7 +15,7 @@ import inspect from '@poppinss/inspect' const { string: prettyPrintHtml } = inspect import string from '@poppinss/utils/string' -import { safeValue, escape } from '../../template/index.js' +import { htmlSafe, escape } from '../../template.js' export const GLOBALS = { /** @@ -33,7 +33,7 @@ export const GLOBALS = { * Inspect state */ inspect: (value: any) => { - return safeValue(prettyPrintHtml.html(value)) + return htmlSafe(prettyPrintHtml.html(value)) }, /** @@ -90,7 +90,7 @@ export const GLOBALS = { * whereas this method converts it to an actual instance of date */ stringify: stringify, - safe: safeValue, + safe: htmlSafe, camelCase: string.camelCase, snakeCase: string.snakeCase, dashCase: string.dashCase, diff --git a/src/edge/index.ts b/src/edge/index.ts index ce477d2..a5d7cf4 100644 --- a/src/edge/index.ts +++ b/src/edge/index.ts @@ -7,12 +7,12 @@ * file that was distributed with this source code. */ -import * as Tags from '../tags/index.js' -import { Loader } from '../loader/index.js' -import { Compiler } from '../compiler/index.js' -import { Template } from '../template/index.js' -import { Processor } from '../processor/index.js' -import { EdgeRenderer } from '../renderer/index.js' +import * as Tags from '../tags/main.js' +import { Loader } from '../loader.js' +import { Compiler } from '../compiler.js' +import { Template } from '../template.js' +import { Processor } from '../processor.js' +import { EdgeRenderer } from '../renderer.js' import { TagContract, diff --git a/src/loader/index.ts b/src/loader.ts similarity index 99% rename from src/loader/index.ts rename to src/loader.ts index 4457c0f..45a0db5 100644 --- a/src/loader/index.ts +++ b/src/loader.ts @@ -9,7 +9,7 @@ import { readFileSync } from 'node:fs' import { join, isAbsolute } from 'node:path' -import type { LoaderContract, LoaderTemplate } from '../types.js' +import type { LoaderContract, LoaderTemplate } from './types.js' /** * The job of a loader is to load the template from a given path. diff --git a/src/processor/index.ts b/src/processor.ts similarity index 90% rename from src/processor/index.ts rename to src/processor.ts index a630dcc..98e1c8b 100644 --- a/src/processor/index.ts +++ b/src/processor.ts @@ -7,14 +7,14 @@ * file that was distributed with this source code. */ +import type { Template } from './template.js' import type { TagToken } from 'edge-lexer/types' -import { ProcessorContract, TemplateContract } from '../types.js' /** * Exposes the API to register a set of handlers to process the * templates output at different stages */ -export class Processor implements ProcessorContract { +export class Processor { #handlers: Map any>> = new Map() /** @@ -72,11 +72,7 @@ export class Processor implements ProcessorContract { /** * Execute output handlers */ - executeOutput(data: { - output: string - template: TemplateContract - state: Record - }): string { + executeOutput(data: { output: string; template: Template; state: Record }): string { const handlers = this.#handlers.get('output') if (!handlers) { return data.output @@ -105,7 +101,7 @@ export class Processor implements ProcessorContract { event: 'output', handler: (data: { output: string - template: TemplateContract + template: Template state: Record }) => string | void ): this diff --git a/src/renderer/index.ts b/src/renderer.ts similarity index 80% rename from src/renderer/index.ts rename to src/renderer.ts index 3343fac..f6195a7 100644 --- a/src/renderer/index.ts +++ b/src/renderer.ts @@ -9,26 +9,21 @@ import lodash from '@poppinss/utils/lodash' -import { Template } from '../template/index.js' -import { Processor } from '../processor/index.js' -import { EdgeRendererContract, CompilerContract } from '../types.js' +import { Template } from './template.js' +import { Processor } from './processor.js' +import type { Compiler } from './compiler.js' /** * Renders a given template with it's shared state */ -export class EdgeRenderer implements EdgeRendererContract { +export class EdgeRenderer { #locals: any = {} - #compiler: CompilerContract - #asyncCompiler: CompilerContract + #compiler: Compiler + #asyncCompiler: Compiler #globals: any #processor: Processor - constructor( - compiler: CompilerContract, - asyncCompiler: CompilerContract, - globals: any, - processor: Processor - ) { + constructor(compiler: Compiler, asyncCompiler: Compiler, globals: any, processor: Processor) { this.#compiler = compiler this.#asyncCompiler = asyncCompiler this.#globals = globals diff --git a/src/stringified_object/index.ts b/src/stringified_object.ts similarity index 100% rename from src/stringified_object/index.ts rename to src/stringified_object.ts diff --git a/src/tags/component.ts b/src/tags/component.ts index f0a309d..c864061 100644 --- a/src/tags/component.ts +++ b/src/tags/component.ts @@ -13,8 +13,8 @@ import * as lexerUtils from 'edge-lexer/utils' import { EdgeBuffer, expressions, Parser } from 'edge-parser' import { TagContract } from '../types.js' -import { StringifiedObject } from '../stringified_object/index.js' -import { isSubsetOf, unallowedExpression, parseJsArg } from '../utils/index.js' +import { StringifiedObject } from '../stringified_object.js' +import { isSubsetOf, unallowedExpression, parseJsArg } from '../utils.js' /** * A list of allowed expressions for the component name diff --git a/src/tags/each.ts b/src/tags/each.ts index ae22789..dc52d2b 100644 --- a/src/tags/each.ts +++ b/src/tags/each.ts @@ -12,7 +12,7 @@ import * as lexerUtils from 'edge-lexer/utils' import { Parser, expressions } from 'edge-parser' import { TagContract } from '../types.js' -import { isSubsetOf, asyncEach, each, unallowedExpression } from '../utils/index.js' +import { isSubsetOf, asyncEach, each, unallowedExpression } from '../utils.js' /** * Returns the list to loop over for the each binary expression diff --git a/src/tags/else_if.ts b/src/tags/else_if.ts index 0db51db..3e3d0a2 100644 --- a/src/tags/else_if.ts +++ b/src/tags/else_if.ts @@ -9,7 +9,7 @@ import { expressions } from 'edge-parser' import { TagContract } from '../types.js' -import { isNotSubsetOf, unallowedExpression, parseJsArg } from '../utils/index.js' +import { isNotSubsetOf, unallowedExpression, parseJsArg } from '../utils.js' /** * Else if tag is used to define conditional blocks. We keep `@elseif` tag diff --git a/src/tags/if.ts b/src/tags/if.ts index 8a278a0..9204572 100644 --- a/src/tags/if.ts +++ b/src/tags/if.ts @@ -10,7 +10,7 @@ import { expressions } from 'edge-parser' import { TagContract } from '../types.js' -import { unallowedExpression, isNotSubsetOf, parseJsArg } from '../utils/index.js' +import { unallowedExpression, isNotSubsetOf, parseJsArg } from '../utils.js' /** * If tag is used to define conditional blocks. diff --git a/src/tags/include.ts b/src/tags/include.ts index ea74f9d..5766269 100644 --- a/src/tags/include.ts +++ b/src/tags/include.ts @@ -1,7 +1,7 @@ /* * edge * - * (c) Harminder Virk + * (c) AdonisJS * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -9,20 +9,20 @@ import { expressions, Parser } from 'edge-parser' -import { TagContract } from '../types.js' -import { unallowedExpression, isSubsetOf, parseJsArg } from '../utils/index.js' +import type { TagContract } from '../types.js' +import { unallowedExpression, isSubsetOf, parseJsArg } from '../utils.js' /** * List of expressions allowed for the include tag */ export const ALLOWED_EXPRESSION = [ - expressions.Identifier, expressions.Literal, - expressions.LogicalExpression, - expressions.MemberExpression, - expressions.ConditionalExpression, + expressions.Identifier, expressions.CallExpression, expressions.TemplateLiteral, + expressions.MemberExpression, + expressions.LogicalExpression, + expressions.ConditionalExpression, ] /** diff --git a/src/tags/include_if.ts b/src/tags/include_if.ts index b42b85f..033f4e7 100644 --- a/src/tags/include_if.ts +++ b/src/tags/include_if.ts @@ -12,7 +12,7 @@ import { expressions } from 'edge-parser' import { TagContract } from '../types.js' import { ALLOWED_EXPRESSION, getRenderExpression } from './include.js' -import { unallowedExpression, isSubsetOf, parseJsArg, isNotSubsetOf } from '../utils/index.js' +import { unallowedExpression, isSubsetOf, parseJsArg, isNotSubsetOf } from '../utils.js' /** * Include tag is used to include partials in the same scope of the parent diff --git a/src/tags/inject.ts b/src/tags/inject.ts index 7fa9db4..4a32265 100644 --- a/src/tags/inject.ts +++ b/src/tags/inject.ts @@ -10,7 +10,7 @@ import { expressions } from 'edge-parser' import { TagContract } from '../types.js' -import { isSubsetOf, unallowedExpression, parseJsArg } from '../utils/index.js' +import { isSubsetOf, unallowedExpression, parseJsArg } from '../utils.js' /** * The inject tag is used within the components to share values with the diff --git a/src/tags/index.ts b/src/tags/main.ts similarity index 100% rename from src/tags/index.ts rename to src/tags/main.ts diff --git a/src/tags/new_error.ts b/src/tags/new_error.ts index 9ac34f3..e922e91 100644 --- a/src/tags/new_error.ts +++ b/src/tags/new_error.ts @@ -10,7 +10,7 @@ import { expressions } from 'edge-parser' import { TagContract } from '../types.js' -import { parseJsArg } from '../utils/index.js' +import { parseJsArg } from '../utils.js' /** * newError tag to raise exceptions inside your templates. They will point diff --git a/src/tags/set.ts b/src/tags/set.ts index 3b14055..b806330 100644 --- a/src/tags/set.ts +++ b/src/tags/set.ts @@ -12,7 +12,7 @@ import { expressions } from 'edge-parser' import lodash from '@poppinss/utils/lodash' import { TagContract } from '../types.js' -import { isSubsetOf, unallowedExpression, parseJsArg } from '../utils/index.js' +import { isSubsetOf, unallowedExpression, parseJsArg } from '../utils.js' /** * The set tag is used to set runtime values within the template. The value diff --git a/src/tags/unless.ts b/src/tags/unless.ts index 44f434d..781b26b 100644 --- a/src/tags/unless.ts +++ b/src/tags/unless.ts @@ -10,7 +10,7 @@ import { expressions } from 'edge-parser' import { TagContract } from '../types.js' -import { isNotSubsetOf, unallowedExpression, parseJsArg } from '../utils/index.js' +import { isNotSubsetOf, unallowedExpression, parseJsArg } from '../utils.js' /** * Inverse of the `if` condition. The term `unless` is more readable and logical diff --git a/src/template/index.ts b/src/template.ts similarity index 58% rename from src/template/index.ts rename to src/template.ts index 7e73fbf..3a807eb 100644 --- a/src/template/index.ts +++ b/src/template.ts @@ -7,14 +7,17 @@ * file that was distributed with this source code. */ -import Macroable from '@poppinss/macroable' +import he from 'he' import { EdgeError } from 'edge-error' import lodash from '@poppinss/utils/lodash' -import he from 'he' +import Macroable from '@poppinss/macroable' +import stringifyAttributes from 'stringify-attributes' + +import { Compiler } from './compiler.js' +import { Processor } from './processor.js' +import { Props } from './component/props.js' +import { CompiledTemplate } from './types.js' -import { Processor } from '../processor/index.js' -import { Props } from '../component/props.js' -import type { CompilerContract, TemplateContract } from '../types.js' /** * An instance of this class passed to the escape * method ensures that underlying value is never @@ -34,7 +37,7 @@ export function escape(input: any): string { /** * Mark value as safe and not to be escaped */ -export function safeValue(value: string) { +export function htmlSafe(value: string) { return new SafeValue(value) } @@ -43,76 +46,49 @@ export function safeValue(value: string) { * of template is passed during runtime to render `dynamic partials` * and `dynamic components`. */ -export class Template extends Macroable implements TemplateContract { - /** - * Required by Macroable - */ - protected static macros = {} - protected static getters = {} +export class Template extends Macroable { + #compiler: Compiler + #processor: Processor /** * The shared state is used to hold the globals and locals, * since it is shared with components too. */ - private sharedState: any + #sharedState: Record - constructor( - private compiler: CompilerContract, - globals: any, - locals: any, - private processor: Processor - ) { + constructor(compiler: Compiler, globals: any, locals: any, processor: Processor) { super() - this.sharedState = lodash.merge({}, globals, locals) - } - - /** - * Wraps template to a function - */ - private wrapToFunction(template: string, ...localVariables: string[]) { - 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} }`)() + this.#compiler = compiler + this.#processor = processor + this.#sharedState = lodash.merge({}, globals, locals) } /** * Trims top and bottom new lines from the content */ - private trimTopBottomNewLines(value: string) { + #trimTopBottomNewLines(value: string) { return value.replace(/^\n|^\r\n/, '').replace(/\n$|\r\n$/, '') } /** * Render a compiled template with state */ - private renderCompiled(compiledTemplate: string, state: any) { - const templateState = Object.assign({}, this.sharedState, state) + #renderCompiled(compiledTemplate: CompiledTemplate, state: any) { + const templateState = Object.assign({}, this.#sharedState, state) const $context = {} /** * Process template as a promise. */ - if (this.compiler.async) { - return this.wrapToFunction(compiledTemplate)(this, templateState, $context).then( - (output: string) => { - output = this.trimTopBottomNewLines(output) - return this.processor.executeOutput({ output, template: this, state: templateState }) - } - ) + if (this.#compiler.async) { + return compiledTemplate(this, templateState, $context).then((output: string) => { + output = this.#trimTopBottomNewLines(output) + return this.#processor.executeOutput({ output, template: this, state: templateState }) + }) } - const output = this.trimTopBottomNewLines( - this.wrapToFunction(compiledTemplate)(this, templateState, $context) - ) - - return this.processor.executeOutput({ output, template: this, state: templateState }) + const output = this.#trimTopBottomNewLines(compiledTemplate(this, templateState, $context)) + return this.#processor.executeOutput({ output, template: this, state: templateState }) } /** @@ -125,9 +101,8 @@ export class Template extends Macroable implements TemplateContract { * partialFn(template, state, ctx) * ``` */ - compilePartial(templatePath: string, ...localVariables: string[]): Function { - const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables, true) - return this.wrapToFunction(compiledTemplate, ...localVariables) + compilePartial(templatePath: string, ...localVariables: string[]): CompiledTemplate { + return this.#compiler.compile(templatePath, localVariables) } /** @@ -140,9 +115,8 @@ export class Template extends Macroable implements TemplateContract { * componentFn(template, template.getComponentState(props, slots, caller), ctx) * ``` */ - compileComponent(templatePath: string, ...localVariables: string[]): string { - const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables) - return this.wrapToFunction(compiledTemplate, ...localVariables) + compileComponent(templatePath: string): CompiledTemplate { + return this.#compiler.compile(templatePath) } /** @@ -153,7 +127,7 @@ export class Template extends Macroable implements TemplateContract { slots: { [key: string]: any }, caller: { filename: string; line: number; col: number } ) { - return Object.assign({}, this.sharedState, props, { + return Object.assign({}, this.#sharedState, props, { $slots: slots, $caller: caller, $props: new Props(props), @@ -168,8 +142,8 @@ export class Template extends Macroable implements TemplateContract { * ``` */ render | string>(template: string, state: any): T { - let { template: compiledTemplate } = this.compiler.compile(template) - return this.renderCompiled(compiledTemplate, state) + let compiledTemplate = this.#compiler.compile(template) + return this.#renderCompiled(compiledTemplate, state) } /** @@ -184,8 +158,8 @@ export class Template extends Macroable implements TemplateContract { state: any, templatePath?: string ): T { - let { template: compiledTemplate } = this.compiler.compileRaw(contents, templatePath) - return this.renderCompiled(compiledTemplate, state) + let compiledTemplate = this.#compiler.compileRaw(contents, templatePath) + return this.#renderCompiled(compiledTemplate, state) } /** @@ -196,6 +170,13 @@ export class Template extends Macroable implements TemplateContract { return escape(input) } + /** + * Converts an object to HTML attributes + */ + toAttributes(attributes: Record) { + return stringifyAttributes(attributes) + } + /** * Raise an error */ diff --git a/src/types.ts b/src/types.ts index 425a722..2faad73 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,10 +7,12 @@ * file that was distributed with this source code. */ -import type { Token, TagToken } from 'edge-lexer/types' +import type { TagToken } from 'edge-lexer/types' import type { Parser, EdgeBuffer } from 'edge-parser' import type { ParserTagDefinitionContract, ClaimTagFn } from 'edge-parser/types' +import { Template } from './template.js' + /** * The shape in which the loader must resolve the template */ @@ -63,44 +65,39 @@ export interface LoaderContract { remove(templatePath: string): void } -/** - * Shape of template constructor - */ -export interface TemplateConstructorContract { - macro: (this: any, name: string, value: (...args: any[]) => any) => void - getter: (this: any, name: string, accumulator: () => any, singleton?: boolean) => void - - new ( - compiler: CompilerContract, - globals: any, - locals: any, - processor: ProcessorContract - ): TemplateContract -} - /** * The tag must have a tagName along with other properties * required by lexer and parser */ export interface TagContract extends ParserTagDefinitionContract { tagName: string - boot?(template: TemplateConstructorContract): void + boot?(template: typeof Template): void } /** - * Shape of required tags + * Shape of collection of tags */ export type TagsContract = { [tagName: string]: TagContract } +/** + * Shape of compiled template as a function + */ +export type CompiledTemplate = ( + template: Template, + state: Record, + $context: Record | undefined, + ...localVariables: any[] +) => any + /** * Shape of the cache manager */ export interface CacheManagerContract { enabled: boolean - get(templatePath: string): undefined | LoaderTemplate - set(templatePath: string, compiledOutput: LoaderTemplate): void + get(templatePath: string): undefined | CompiledTemplate + set(templatePath: string, compiledOutput: CompiledTemplate): void has(templatePath: string): boolean delete(templatePath: string): void } @@ -113,27 +110,6 @@ export type CompilerOptions = { async?: boolean } -/** - * Shape of the compiler - */ -export interface CompilerContract { - cacheManager: CacheManagerContract - async: boolean - claimTag(fn: ClaimTagFn): this - compile(templatePath: string, localVariables?: string[], skipCache?: boolean): LoaderTemplate - tokenize(templatePath: string, parser?: Parser): Token[] - - /** - * Compile the raw string as a template - */ - compileRaw(contents: string, templatePath?: string): LoaderTemplate - - /** - * Tokenize the raw string as a template - */ - tokenizeRaw(contents: string, templatePath?: string, parser?: Parser): Token[] -} - /** * Shape of the props class passed to the components */ @@ -169,118 +145,6 @@ export interface PropsContract { serializeExcept(keys: string[], mergeProps?: any): { value: string } } -/** - * Shape of the template contract - */ -export interface TemplateContract { - /** - * Compiles partial - */ - compilePartial(templatePath: string, ...localVariables: string[]): Function - - /** - * Compiles a component - */ - compileComponent(templatePath: string, ...localVariables: string[]): string - - /** - * Returns the state for a component - */ - getComponentState( - props: { [key: string]: any }, - slots: { [key: string]: any }, - caller: { filename: string; line: number; col: number } - ): { - $props: PropsContract & { [key: string]: any } - $slots: { [key: string]: any } - $caller: { filename: string; line: number; col: number } - } - - /** - * Renders a template to a string - */ - render | string>(template: string, state: any): T - renderRaw | string>( - contents: string, - state: any, - templatePath?: string - ): T - - /** - * Escape input - */ - escape(input: any): string - - /** - * Rethrow exceptions by pointing back to edge source file and line number - */ - reThrow(error: any, filename: string, line: number): never -} - -/** - * Shape of the renderer that renders the edge templates - */ -export interface EdgeRendererContract { - /** - * Share state with the template and its partials and component - */ - share(locals: any): this - - /** - * Render a template asynchronously - */ - render(templatePath: string, state?: any): Promise - renderRaw(contents: string, state?: any, templatePath?: string): Promise - - /** - * Render a template synchronously - */ - renderSync(templatePath: string, state?: any): string - renderRawSync(contents: string, state?: any, templatePath?: string): string -} - -/** - * The processor is used to execute process functions for different - * lifecycles - */ -export interface ProcessorContract { - /** - * Hook into the raw text to modify its contents. Make sure to return the - * new string back or return "void" in case no modifications have been - * performed - */ - process(event: 'raw', handler: (data: { raw: string; path: string }) => string | void): this - - /** - * Hook into the tag node to modify its properties - */ - process(event: 'tag', handler: (data: { tag: TagToken; path: string }) => void): this - - /** - * Hook into the compiled template to modify its contents. Make sure to return the - * new string back or return "void" in case no modifications have been - * performed - */ - process( - event: 'compiled', - handler: (data: { compiled: string; path: string }) => string | void - ): this - - /** - * Hook into the compiled output to modify its contents. Make sure to return the - * new string back or return "void" in case no modifications have been - * performed - */ - process( - event: 'output', - handler: (data: { - output: string - template: TemplateContract - state: Record - }) => string | void - ): this -} - /** * Shape of options that can be passed to the * edge constructor @@ -290,119 +154,10 @@ export type EdgeOptions = { cache?: boolean } -/** - * Shape of the main module - */ -export interface EdgeContract { - /** - * Loader for loading templates. You can also define a custom loader when creating - * a new instance of edge - */ - loader: LoaderContract - - /** - * Compiler to be used for compiling synchronously - */ - compiler: CompilerContract - - /** - * Compiler to be used for compiling asynchronously - */ - asyncCompiler: CompilerContract - - /** - * Processor reference to hook into the compile and the rendering - * phase of templates - */ - processor: ProcessorContract - - /** - * Set of registered globals. One can define custom globals using `edge.global` - * method - */ - GLOBALS: { [key: string]: any } - - /** - * A custom set of registered tags. One can define a custom tag using `edge.registerTag` - * method - */ - tags: { [name: string]: TagContract } - - /** - * Register a plugin. Plugins are lazily invoked just before the views are rendered. This - * ensures that plugins will receive a fully configured edge instance. - * - * Also plugins are invoked only once. Unless, the `options.recurring` value is set - */ - use( - pluginFn: (edge: this, firstRun: boolean, options: T) => void, - options?: T - ): this - - /** - * Register a custom tag - */ - registerTag(tag: TagContract): this - - /** - * Register an inline template - */ - registerTemplate(templatePath: string, contents: LoaderTemplate): this - - /** - * Remove the template registered using the "registerTemplate" method - */ - removeTemplate(templatePath: string): this - - /** - * Register a global value - */ - global(key: string, value: any): this - - /** - * Mount/disk - */ - mount(diskName: string): this - mount(diskName: string, dirPath: string): this - - /** - * Unmount disk - */ - unmount(diskName: 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 - - /** - * Get a renderer instance to render templates - */ - getRenderer(): EdgeRendererContract - - /** - * Creates a renderer instances and shares the locals with it - */ - share(locals: any): EdgeRendererContract - - /** - * Render a template asynchronously - */ - render(templatePath: string, state?: any): Promise - renderRaw(contents: string, state?: any, templatePath?: string): Promise - - /** - * Render a template synchronously - */ - renderSync(templatePath: string, state?: any): string - renderRawSync(contents: string, state?: any, templatePath?: string): string -} - /** * Required for someone creating custom tags */ -export type EdgeBufferContract = EdgeBuffer export type ParserContract = Parser export type TagTokenContract = TagToken +export type EdgeBufferContract = EdgeBuffer export type { ClaimTagFn } diff --git a/src/utils/index.ts b/src/utils.ts similarity index 100% rename from src/utils/index.ts rename to src/utils.ts index 994be97..649868a 100644 --- a/src/utils/index.ts +++ b/src/utils.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import type { TagToken } from 'edge-lexer/types' import { EdgeError } from 'edge-error' +import type { TagToken } from 'edge-lexer/types' import { expressions as expressionsList, Parser } from 'edge-parser' type ExpressionList = readonly (keyof typeof expressionsList)[] diff --git a/test/assert_extend.ts b/tests/assert_extend.ts similarity index 100% rename from test/assert_extend.ts rename to tests/assert_extend.ts diff --git a/test/async_fixtures.spec.ts b/tests/async_fixtures.spec.ts similarity index 77% rename from test/async_fixtures.spec.ts rename to tests/async_fixtures.spec.ts index 8e068fc..c3e90fd 100644 --- a/test/async_fixtures.spec.ts +++ b/tests/async_fixtures.spec.ts @@ -9,18 +9,19 @@ import './assert_extend.js' +import dedent from 'dedent-js' import { test } from '@japa/runner' +import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import { readdirSync, readFileSync, statSync } from 'node:fs' -import * as tags from '../src/tags/index.js' -import { Loader } from '../src/loader/index.js' -import { Template } from '../src/template/index.js' -import { Compiler } from '../src/compiler/index.js' -import { Processor } from '../src/processor/index.js' +import * as tags from '../src/tags/main.js' +import { Loader } from '../src/loader.js' +import { Template } from '../src/template.js' +import { Compiler } from '../src/compiler.js' +import { Processor } from '../src/processor.js' import { normalizeNewLines, normalizeFilename } from '../test_helpers/index.js' -import { fileURLToPath } from 'node:url' const basePath = join(dirname(fileURLToPath(import.meta.url)), '../async_fixtures') @@ -50,17 +51,20 @@ test.group('Async Fixtures', (group) => { /** * Compiled output */ - const { template: compiled } = compiler.compile(`${dir}/index.edge`) + const compiledTemplate = compiler.compile(`${dir}/index.edge`) const expectedCompiled = normalizeNewLines( readFileSync(join(dirBasePath, 'compiled.js'), 'utf-8') ) assert.stringEqual( - compiled, - expectedCompiled - .split('\n') - .map((line) => normalizeFilename(dirBasePath, line)) - .join('\n') + compiledTemplate.toString(), + dedent`async function anonymous(template,state,$context + ) { + ${expectedCompiled + .split('\n') + .map((line) => normalizeFilename(dirBasePath, line)) + .join('\n')} + }` ) /** @@ -101,17 +105,20 @@ test.group('Async Fixtures | Cached', (group) => { /** * Compiled output */ - const { template: compiled } = compiler.compile(`${dir}/index.edge`) + const compiledTemplate = compiler.compile(`${dir}/index.edge`) const expectedCompiled = normalizeNewLines( readFileSync(join(dirBasePath, 'compiled.js'), 'utf-8') ) assert.stringEqual( - compiled, - expectedCompiled - .split('\n') - .map((line) => normalizeFilename(dirBasePath, line)) - .join('\n') + compiledTemplate.toString(), + dedent`async function anonymous(template,state,$context + ) { + ${expectedCompiled + .split('\n') + .map((line) => normalizeFilename(dirBasePath, line)) + .join('\n')} + }` ) /** diff --git a/test/compiler.spec.ts b/tests/compiler.spec.ts similarity index 89% rename from test/compiler.spec.ts rename to tests/compiler.spec.ts index 2e14bd0..40534e1 100644 --- a/test/compiler.spec.ts +++ b/tests/compiler.spec.ts @@ -8,18 +8,18 @@ */ import './assert_extend.js' -import { test } from '@japa/runner' -import { join } from 'node:path' import dedent from 'dedent-js' +import { join } from 'node:path' +import { test } from '@japa/runner' // @ts-ignore untyped module import stringify from 'js-stringify' -import { TagTypes, MustacheTypes } from 'edge-lexer/types' +import { TagTypes, MustacheTypes } from 'edge-lexer' -import { Loader } from '../src/loader/index.js' +import { Loader } from '../src/loader.js' import { setTag } from '../src/tags/set.js' -import { Template } from '../src/template/index.js' -import { Compiler } from '../src/compiler/index.js' -import { Processor } from '../src/processor/index.js' +import { Template } from '../src/template.js' +import { Compiler } from '../src/compiler.js' +import { Processor } from '../src/processor.js' import { layoutTag } from '../src/tags/layout.js' import { sectionTag } from '../src/tags/section.js' import { componentTag } from '../src/tags/component.js' @@ -33,11 +33,13 @@ test.group('Compiler | Cache', () => { loader.mount('default', fs.basePath) const compiler = new Compiler(loader, {}, new Processor()) - const { template } = compiler.compile('foo') + const compiledTemplate = compiler.compile('foo') assert.stringEqual( - template, - normalizeNewLines(dedent`let out = ""; + compiledTemplate.toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify(join(fs.basePath, 'foo.edge'))}; try { @@ -46,7 +48,8 @@ test.group('Compiler | Cache', () => { } catch (error) { template.reThrow(error, $filename, $lineNumber); } - return out;`) + return out; + }`) ) }) @@ -57,9 +60,9 @@ test.group('Compiler | Cache', () => { loader.mount('default', fs.basePath) const compiler = new Compiler(loader, {}, new Processor(), { cache: true }) - assert.equal( - compiler.compile('foo').template, - compiler.cacheManager.get(join(fs.basePath, 'foo.edge'))!.template + assert.strictEqual( + compiler.compile('foo'), + compiler.cacheManager.get(join(fs.basePath, 'foo.edge')) ) }) @@ -137,7 +140,7 @@ test.group('Compiler | Tokenize', () => { ]) }) - test('during tokenize, merge @set tags of a given layout', async ({ assert, fs }) => { + test('during tokenize merge set tags of a given layout', async ({ assert, fs }) => { await fs.create( 'master.edge', dedent` @@ -254,7 +257,7 @@ test.group('Compiler | Tokenize', () => { } }) - test('during tokenize, merge @section tags of a nested layouts', async ({ assert, fs }) => { + test('during tokenize merge @section tags of a nested layouts', async ({ assert, fs }) => { await fs.create( 'super-master.edge', dedent` @@ -448,8 +451,10 @@ test.group('Compiler | Compile', () => { const compiler = new Compiler(loader, tags, new Processor()) assert.stringEqual( - compiler.compile('index.edge').template, - normalizeNewLines(dedent`let out = ""; + compiler.compile('index.edge').toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify(join(fs.basePath, 'index.edge'))}; try { @@ -464,7 +469,7 @@ test.group('Compiler | Compile', () => { template.reThrow(error, $filename, $lineNumber); } return out; - `) + }`) ) }) @@ -586,8 +591,8 @@ test.group('Compiler | Compile', () => { ) try { - const fn = compiler.compile('index.edge').template - new Function('template', 'state', fn)(new Template(compiler, {}, {}, new Processor()), {}) + const fn = compiler.compile('index.edge') + fn(new Template(compiler, {}, {}, new Processor()), {}, {}) } catch (error) { assert.equal(error.message, 'getUserName is not a function') assert.equal(error.filename, join(fs.basePath, 'master.edge')) @@ -633,8 +638,8 @@ test.group('Compiler | Compile', () => { ) try { - const fn = compiler.compile('index.edge').template - new Function('template', 'state', fn)(new Template(compiler, {}, {}, new Processor()), {}) + const fn = compiler.compile('index.edge') + fn(new Template(compiler, {}, {}, new Processor()), {}, {}) } catch (error) { assert.equal(error.message, 'getContent is not a function') assert.equal(error.filename, join(fs.basePath, 'index.edge')) @@ -664,13 +669,19 @@ test.group('Compiler | Compile Raw', () => { const compiler = new Compiler(loader, tags, new Processor()) assert.stringEqual( - compiler.compileRaw(dedent` + compiler + .compileRaw( + dedent` @layout('master') @section('content') {{ content }} @endsection - `).template, - normalizeNewLines(dedent`let out = ""; + ` + ) + .toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify('eval.edge')}; try { @@ -685,7 +696,7 @@ test.group('Compiler | Compile Raw', () => { template.reThrow(error, $filename, $lineNumber); } return out; - `) + }`) ) }) @@ -787,13 +798,15 @@ test.group('Compiler | Compile Raw', () => { ) try { - const fn = compiler.compileRaw(dedent` + const fn = compiler.compileRaw( + dedent` @layout('master') @section('content') {{ content }} @endsection - `).template - new Function('template', 'state', fn)(new Template(compiler, {}, {}, new Processor()), {}) + ` + ) + fn(new Template(compiler, {}, {}, new Processor()), {}, {}) } catch (error) { assert.equal(error.message, 'getUserName is not a function') assert.equal(error.filename, join(fs.basePath, 'master.edge')) @@ -829,13 +842,15 @@ test.group('Compiler | Compile Raw', () => { ) try { - const fn = compiler.compileRaw(dedent` + const fn = compiler.compileRaw( + dedent` @layout('master') @section('content') {{ getContent() }} @endsection - `).template - new Function('template', 'state', fn)(new Template(compiler, {}, {}, new Processor()), {}) + ` + ) + fn(new Template(compiler, {}, {}, new Processor()), {}, {}) } catch (error) { assert.equal(error.message, 'getContent is not a function') assert.equal(error.filename, 'eval.edge') @@ -901,8 +916,10 @@ test.group('Compiler | Processor', () => { ) assert.stringEqual( - compiler.compile('index.edge').template, - normalizeNewLines(dedent`let out = ""; + compiler.compile('index.edge').toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify(join(fs.basePath, 'index.edge'))}; try { @@ -911,7 +928,7 @@ test.group('Compiler | Processor', () => { template.reThrow(error, $filename, $lineNumber); } return out; - `) + }`) ) }) @@ -1049,11 +1066,17 @@ test.group('Compiler | Processor', () => { processor ) - assert.equal(compiler.compile('index.edge').template, 'bar') + assert.equal( + compiler.compile('index.edge').toString(), + dedent`function anonymous(template,state,$context + ) { + bar + }` + ) }) - test('run compiled processor function even when template is cached', async ({ assert, fs }) => { - assert.plan(6) + test('do not run compiled function when template is called', async ({ assert, fs }) => { + assert.plan(2) await fs.create('index.edge', dedent`Hello`) const loader = new Loader() @@ -1092,50 +1115,6 @@ test.group('Compiler | Processor', () => { compiler.compile('index.edge') }) - test('do not mutate cache when compiled processor function returns a different value', async ({ - assert, - fs, - }) => { - assert.plan(9) - await fs.create('index.edge', dedent`Hello`) - - const loader = new Loader() - loader.mount('default', fs.basePath) - - const processor = new Processor() - processor.process('compiled', ({ compiled, path }) => { - assert.stringEqual( - compiled, - normalizeNewLines(dedent`let out = ""; - let $lineNumber = 1; - let $filename = ${stringify(join(fs.basePath, 'index.edge'))}; - try { - out += "Hello"; - } catch (error) { - template.reThrow(error, $filename, $lineNumber); - } - return out; - `) - ) - assert.equal(path, join(fs.basePath, 'index.edge')) - return 'foo' - }) - - const compiler = new Compiler( - loader, - { - section: sectionTag, - layout: layoutTag, - }, - processor, - { cache: true } - ) - - assert.equal(compiler.compile('index.edge').template, 'foo') - assert.equal(compiler.compile('index.edge').template, 'foo') - assert.equal(compiler.compile('index.edge').template, 'foo') - }) - test('run raw processor function for layouts', async ({ assert, fs }) => { assert.plan(5) @@ -1200,8 +1179,10 @@ test.group('Compiler | Processor', () => { ) assert.stringEqual( - compiler.compile('index.edge').template, - normalizeNewLines(dedent`let out = ""; + compiler.compile('index.edge').toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify(join(fs.basePath, 'index.edge'))}; try { @@ -1216,7 +1197,7 @@ test.group('Compiler | Processor', () => { template.reThrow(error, $filename, $lineNumber); } return out; - `) + }`) ) }) @@ -1262,8 +1243,7 @@ test.group('Compiler | Processor', () => { } catch (error) { template.reThrow(error, $filename, $lineNumber); } - return out; - `) + return out;`) ) assert.equal(path, join(fs.basePath, 'index.edge')) }) @@ -1278,8 +1258,10 @@ test.group('Compiler | Processor', () => { ) assert.stringEqual( - compiler.compile('index.edge').template, - normalizeNewLines(dedent`let out = ""; + compiler.compile('index.edge').toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify(join(fs.basePath, 'index.edge'))}; try { @@ -1294,7 +1276,7 @@ test.group('Compiler | Processor', () => { template.reThrow(error, $filename, $lineNumber); } return out; - `) + }`) ) }) @@ -1341,8 +1323,10 @@ test.group('Compiler | Processor', () => { }) assert.stringEqual( - compiler.compile('index.edge').template, - normalizeNewLines(dedent`let out = ""; + compiler.compile('index.edge').toString(), + normalizeNewLines(dedent`function anonymous(template,state,$context + ) { + let out = ""; let $lineNumber = 1; let $filename = ${stringify(join(fs.basePath, 'index.edge'))}; try { @@ -1351,7 +1335,7 @@ test.group('Compiler | Processor', () => { template.reThrow(error, $filename, $lineNumber); } return out; - `) + }`) ) }) }) diff --git a/test/component.spec.ts b/tests/component.spec.ts similarity index 98% rename from test/component.spec.ts rename to tests/component.spec.ts index d62b4c3..7a5934a 100644 --- a/test/component.spec.ts +++ b/tests/component.spec.ts @@ -7,24 +7,25 @@ * file that was distributed with this source code. */ -import { test } from '@japa/runner' -import { dirname, join } from 'node:path' +import './assert_extend.js' + import dedent from 'dedent-js' +import { test } from '@japa/runner' import { EdgeError } from 'edge-error' +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path' import { Filesystem } from '@poppinss/dev-utils' -import { Loader } from '../src/loader/index.js' -import { Compiler } from '../src/compiler/index.js' -import { Template } from '../src/template/index.js' -import { Processor } from '../src/processor/index.js' +import { Loader } from '../src/loader.js' +import { Compiler } from '../src/compiler.js' +import { Template } from '../src/template.js' +import { Processor } from '../src/processor.js' import { ifTag } from '../src/tags/if.js' import { slotTag } from '../src/tags/slot.js' import { injectTag } from '../src/tags/inject.js' import { includeTag } from '../src/tags/include.js' import { componentTag } from '../src/tags/component.js' -import './assert_extend.js' -import { fileURLToPath } from 'node:url' const fs = new Filesystem(join(dirname(fileURLToPath(import.meta.url)), 'views')) const tags = { diff --git a/test/each.spec.ts b/tests/each.spec.ts similarity index 98% rename from test/each.spec.ts rename to tests/each.spec.ts index 06e596e..e20c782 100644 --- a/test/each.spec.ts +++ b/tests/each.spec.ts @@ -8,7 +8,7 @@ */ import { test } from '@japa/runner' -import { asyncEach, each } from '../src/utils/index.js' +import { asyncEach, each } from '../src/utils.js' test.group('async each', () => { test('iterate over array', async ({ assert }) => { diff --git a/test/edge.spec.ts b/tests/edge.spec.ts similarity index 89% rename from test/edge.spec.ts rename to tests/edge.spec.ts index 0ca162b..c56625a 100644 --- a/test/edge.spec.ts +++ b/tests/edge.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable unicorn/no-await-expression-member */ /* * edge * @@ -9,10 +8,10 @@ */ import './assert_extend.js' +import dedent from 'dedent-js' import { EOL } from 'node:os' -import { test } from '@japa/runner' import { join } from 'node:path' -import dedent from 'dedent-js' +import { test } from '@japa/runner' import { Edge } from '../src/edge/index.js' import { GLOBALS } from '../src/edge/globals/index.js' @@ -82,7 +81,8 @@ test.group('Edge', () => { await fs.create('foo.edge', 'Hello {{ username }}') edge.mount(fs.basePath) - assert.equal((await edge.render('foo', { username: 'virk' })).trim(), 'Hello virk') + const output = await edge.render('foo', { username: 'virk' }) + assert.equal(output.trim(), 'Hello virk') }) test('pass locals to the view context', async ({ assert, fs }) => { @@ -94,8 +94,11 @@ test.group('Edge', () => { const tmpl = edge.getRenderer() tmpl.share({ username: 'nikk' }) - assert.equal((await tmpl.render('foo', {})).trim(), 'Hello nikk') - assert.equal((await edge.render('foo', {})).trim(), 'Hello guest') + const output = await tmpl.render('foo', {}) + const output1 = await edge.render('foo', {}) + + assert.equal(output.trim(), 'Hello nikk') + assert.equal(output1.trim(), 'Hello guest') }) test('register a template as a string', async ({ assert }) => { @@ -105,7 +108,8 @@ test.group('Edge', () => { template: 'Hello {{ username }}', }) - assert.equal((await edge.render('foo', { username: 'virk' })).trim(), 'Hello virk') + const output = await edge.render('foo', { username: 'virk' }) + assert.equal(output.trim(), 'Hello virk') }) test('register a template on a named disk', async ({ assert, fs }) => { @@ -116,23 +120,28 @@ test.group('Edge', () => { template: 'Hello {{ username }}', }) - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') + const output = await edge.render('hello::foo', { username: 'virk' }) + assert.equal(output.trim(), 'Hello virk') }) - test('clear compiled cache when template is removed', async ({ assert }) => { + test('clear compiled cache when in-memory template is removed', async ({ assert }) => { const edge = new Edge({ cache: true }) edge.registerTemplate('foo', { template: 'Hello {{ username }}', }) - assert.equal((await edge.render('foo', { username: 'virk' })).trim(), 'Hello virk') + + const output = await edge.render('foo', { username: 'virk' }) + assert.equal(output.trim(), 'Hello virk') assert.equal(edge.renderSync('foo', { username: 'virk' }).trim(), 'Hello virk') edge.removeTemplate('foo') edge.registerTemplate('foo', { template: 'Hi {{ username }}', }) - assert.equal((await edge.render('foo', { username: 'virk' })).trim(), 'Hi virk') + + const output1 = await edge.render('foo', { username: 'virk' }) + assert.equal(output1.trim(), 'Hi virk') assert.equal(edge.renderSync('foo', { username: 'virk' }).trim(), 'Hi virk') }) @@ -217,12 +226,13 @@ test.group('Edge', () => { try { await edge.render('foo', false) } catch ({ stack }) { + console.log(stack) assert.equal( stack.split('\n')[1].trim(), `at anonymous (${join(fs.basePath, 'bar.edge')}:1:4)` ) } - }).skip(true, 'Todo: Fix me') + }) test('pass absolute path of partial to parser errors', async ({ assert, fs }) => { assert.plan(1) @@ -240,7 +250,7 @@ test.group('Edge', () => { `at anonymous (${join(fs.basePath, 'bar.edge')}:1:3)` ) } - }).skip(true, 'Todo: Fix me') + }) test('pass absolute path of component to lexer errors', async ({ assert, fs }) => { assert.plan(1) @@ -258,7 +268,7 @@ test.group('Edge', () => { `at anonymous (${join(fs.basePath, 'bar.edge')}:1:4)` ) } - }).skip(true, 'Todo: Fix me') + }) test('pass absolute path of component to parser errors', async ({ assert, fs }) => { assert.plan(1) @@ -276,7 +286,7 @@ test.group('Edge', () => { `at anonymous (${join(fs.basePath, 'bar.edge')}:1:3)` ) } - }).skip(true, 'Todo: Fix me') + }) test('register and call plugins before rendering a view', async ({ assert, fs }) => { assert.plan(3) @@ -294,7 +304,8 @@ test.group('Edge', () => { template: 'Hello {{ username }}', }) - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') + const output = await edge.render('hello::foo', { username: 'virk' }) + assert.equal(output.trim(), 'Hello virk') }) test('do not run plugins until a view is rendered', async ({ assert, fs }) => { @@ -315,7 +326,7 @@ test.group('Edge', () => { }) test('run plugins only once', async ({ assert, fs }) => { - assert.plan(5) + assert.plan(2) const edge = new Edge() edge.use(($edge) => { @@ -330,13 +341,13 @@ test.group('Edge', () => { template: 'Hello {{ username }}', }) - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') + await edge.render('hello::foo', { username: 'virk' }) + await edge.render('hello::foo', { username: 'virk' }) + await edge.render('hello::foo', { username: 'virk' }) }) test('run recurring plugins again and again', async ({ assert, fs }) => { - assert.plan(9) + assert.plan(6) const edge = new Edge() edge.use( @@ -354,9 +365,9 @@ test.group('Edge', () => { template: 'Hello {{ username }}', }) - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') - assert.equal((await edge.render('hello::foo', { username: 'virk' })).trim(), 'Hello virk') + await edge.render('hello::foo', { username: 'virk' }) + await edge.render('hello::foo', { username: 'virk' }) + await edge.render('hello::foo', { username: 'virk' }) }) test('hook into renderer instance', async ({ assert, fs }) => { @@ -371,9 +382,8 @@ test.group('Edge', () => { template: 'Hello {{ foo }}', }) - assert.equal((await edge.render('hello::foo')).trim(), 'Hello bar') - assert.equal((await edge.render('hello::foo')).trim(), 'Hello bar') - assert.equal((await edge.render('hello::foo')).trim(), 'Hello bar') + const output = await edge.render('hello::foo') + assert.equal(output.trim(), 'Hello bar') }) }) @@ -444,7 +454,7 @@ test.group('Edge | regression', () => { template: 'Hello {{ safe(username) }}', }) assert.equal(await edge.render('eval', { username: '

virk

' }), 'Hello

virk

') - }).skip(true, 'Todo: Fix me') + }) test('truncate string by characters', async ({ assert }) => { const edge = new Edge() diff --git a/test/filename.spec.ts b/tests/filename.spec.ts similarity index 100% rename from test/filename.spec.ts rename to tests/filename.spec.ts index a46f8f0..a8041dd 100644 --- a/test/filename.spec.ts +++ b/tests/filename.spec.ts @@ -8,9 +8,9 @@ */ import './assert_extend.js' -import { test } from '@japa/runner' -import { join } from 'node:path' import dedent from 'dedent-js' +import { join } from 'node:path' +import { test } from '@japa/runner' import { Edge } from '../src/edge/index.js' import { normalizeNewLines } from '../test_helpers/index.js' diff --git a/test/fixtures.spec.ts b/tests/fixtures.spec.ts similarity index 80% rename from test/fixtures.spec.ts rename to tests/fixtures.spec.ts index b4944f7..4542ae2 100644 --- a/test/fixtures.spec.ts +++ b/tests/fixtures.spec.ts @@ -9,18 +9,19 @@ import './assert_extend.js' +import dedent from 'dedent-js' import { test } from '@japa/runner' +import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import { readdirSync, readFileSync, statSync } from 'node:fs' -import * as tags from '../src/tags/index.js' -import { Loader } from '../src/loader/index.js' -import { Template } from '../src/template/index.js' -import { Compiler } from '../src/compiler/index.js' -import { Processor } from '../src/processor/index.js' +import * as tags from '../src/tags/main.js' +import { Loader } from '../src/loader.js' +import { Template } from '../src/template.js' +import { Compiler } from '../src/compiler.js' +import { Processor } from '../src/processor.js' import { normalizeNewLines, normalizeFilename } from '../test_helpers/index.js' -import { fileURLToPath } from 'node:url' const basePath = join(dirname(fileURLToPath(import.meta.url)), '../fixtures') @@ -51,16 +52,19 @@ test.group('Fixtures', (group) => { /** * Compiled output */ - const { template: compiled } = compiler.compile(`${dir}/index.edge`) + const compiledTemplate = compiler.compile(`${dir}/index.edge`) const expectedCompiled = normalizeNewLines( readFileSync(join(dirBasePath, 'compiled.js'), 'utf-8') ) assert.stringEqual( - compiled, - expectedCompiled + compiledTemplate.toString(), + dedent`function anonymous(template,state,$context + ) { + ${expectedCompiled .split('\n') .map((line) => normalizeFilename(dirBasePath, line)) - .join('\n') + .join('\n')} + }` ) /** @@ -101,16 +105,19 @@ test.group('Fixtures | Cache', (group) => { /** * Compiled output */ - const { template: compiled } = compiler.compile(`${dir}/index.edge`) + const compiledTemplate = compiler.compile(`${dir}/index.edge`) const expectedCompiled = normalizeNewLines( readFileSync(join(dirBasePath, 'compiled.js'), 'utf-8') ) assert.stringEqual( - compiled, - expectedCompiled - .split('\n') - .map((line) => normalizeFilename(dirBasePath, line)) - .join('\n') + compiledTemplate.toString(), + dedent`function anonymous(template,state,$context + ) { + ${expectedCompiled + .split('\n') + .map((line) => normalizeFilename(dirBasePath, line)) + .join('\n')} + }` ) /** diff --git a/test/loader.spec.ts b/tests/loader.spec.ts similarity index 99% rename from test/loader.spec.ts rename to tests/loader.spec.ts index 8326f52..32535b4 100644 --- a/test/loader.spec.ts +++ b/tests/loader.spec.ts @@ -7,12 +7,12 @@ * file that was distributed with this source code. */ -import { test } from '@japa/runner' import { join } from 'node:path' - -import { Loader } from '../src/loader/index.js' +import { test } from '@japa/runner' import { getDirname } from '@poppinss/utils' +import { Loader } from '../src/loader.js' + const dirnameEsm = getDirname(import.meta.url) test.group('Loader', () => { diff --git a/test/new_error.spec.ts b/tests/new_error.spec.ts similarity index 91% rename from test/new_error.spec.ts rename to tests/new_error.spec.ts index 30e24e6..be95aa6 100644 --- a/test/new_error.spec.ts +++ b/tests/new_error.spec.ts @@ -7,19 +7,19 @@ * file that was distributed with this source code. */ +import dedent from 'dedent-js' import { test } from '@japa/runner' +import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' -import dedent from 'dedent-js' import { Filesystem } from '@poppinss/dev-utils' -import { Loader } from '../src/loader/index.js' -import { Compiler } from '../src/compiler/index.js' -import { newError } from '../src/tags/index.js' -import { Processor } from '../src/processor/index.js' -import { Template } from '../src/template/index.js' +import { Loader } from '../src/loader.js' +import { Compiler } from '../src/compiler.js' +import { newError } from '../src/tags/main.js' +import { Processor } from '../src/processor.js' +import { Template } from '../src/template.js' import './assert_extend.js' -import { fileURLToPath } from 'node:url' const tags = { newError } const fs = new Filesystem(join(dirname(fileURLToPath(import.meta.url)), 'views')) diff --git a/test/newline_fixtures.spec.ts b/tests/newline_fixtures.spec.ts similarity index 87% rename from test/newline_fixtures.spec.ts rename to tests/newline_fixtures.spec.ts index 83926da..371b1d6 100644 --- a/test/newline_fixtures.spec.ts +++ b/tests/newline_fixtures.spec.ts @@ -9,17 +9,16 @@ import './assert_extend.js' import { test } from '@japa/runner' +import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import { readdirSync, readFileSync, statSync } from 'node:fs' -import * as tags from '../src/tags/index.js' -import { Loader } from '../src/loader/index.js' -import { Template } from '../src/template/index.js' -import { Compiler } from '../src/compiler/index.js' -import { Processor } from '../src/processor/index.js' - +import { Loader } from '../src/loader.js' +import * as tags from '../src/tags/main.js' +import { Template } from '../src/template.js' +import { Compiler } from '../src/compiler.js' +import { Processor } from '../src/processor.js' import { normalizeNewLines, normalizeFilename } from '../test_helpers/index.js' -import { fileURLToPath } from 'node:url' const basePath = join(dirname(fileURLToPath(import.meta.url)), '../newline_fixtures') diff --git a/test/props.spec.ts b/tests/props.spec.ts similarity index 100% rename from test/props.spec.ts rename to tests/props.spec.ts diff --git a/test/stringified_object.spec.ts b/tests/stringified_object.spec.ts similarity index 93% rename from test/stringified_object.spec.ts rename to tests/stringified_object.spec.ts index 135cac5..a824755 100644 --- a/test/stringified_object.spec.ts +++ b/tests/stringified_object.spec.ts @@ -9,7 +9,7 @@ import { test } from '@japa/runner' import { Parser, Stack } from 'edge-parser' -import { StringifiedObject } from '../src/stringified_object/index.js' +import { StringifiedObject } from '../src/stringified_object.js' /** * Sample loc @@ -63,6 +63,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST("({ username: 'virk' })", LOC, 'eval.edge'), @@ -79,6 +80,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST('({ username })', LOC, 'eval.edge'), @@ -95,6 +97,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST('({ [username]: username })', LOC, 'eval.edge'), @@ -111,6 +114,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST("({ username: 'virk', age: 22 })", LOC, 'eval.edge'), @@ -128,6 +132,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST('({ username, age: 22 })', LOC, 'eval.edge'), @@ -144,6 +149,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST("(title = 'Hello')", LOC, 'eval.edge'), @@ -160,6 +166,7 @@ test.group('StringifiedObject | fromAcornAst', () => { async: false, statePropertyName: 'state', escapeCallPath: 'escape', + toAttributesCallPath: 'toAttributes', }) const expression = parser.utils.transformAst( parser.utils.generateAST("(title = 'Hello', body = 'Some content')", LOC, 'eval.edge'), diff --git a/test/tags.spec.ts b/tests/tags.spec.ts similarity index 98% rename from test/tags.spec.ts rename to tests/tags.spec.ts index a55953e..031e6e4 100644 --- a/test/tags.spec.ts +++ b/tests/tags.spec.ts @@ -7,16 +7,16 @@ * file that was distributed with this source code. */ +import dedent from 'dedent-js' import { test } from '@japa/runner' +import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' -import dedent from 'dedent-js' import { Filesystem } from '@poppinss/dev-utils' -import * as tags from '../src/tags/index.js' -import { Loader } from '../src/loader/index.js' -import { Compiler } from '../src/compiler/index.js' -import { Processor } from '../src/processor/index.js' -import { fileURLToPath } from 'node:url' +import * as tags from '../src/tags/main.js' +import { Loader } from '../src/loader.js' +import { Compiler } from '../src/compiler.js' +import { Processor } from '../src/processor.js' const fs = new Filesystem(join(dirname(fileURLToPath(import.meta.url)), 'views')) diff --git a/test/template.spec.ts b/tests/template.spec.ts similarity index 86% rename from test/template.spec.ts rename to tests/template.spec.ts index 3048348..4fa507d 100644 --- a/test/template.spec.ts +++ b/tests/template.spec.ts @@ -10,17 +10,16 @@ import './assert_extend.js' import { test } from '@japa/runner' import path, { join } from 'node:path' +import { fileURLToPath } from 'node:url' import { Filesystem } from '@poppinss/dev-utils' -import { Loader } from '../src/loader/index.js' -import { Compiler } from '../src/compiler/index.js' +import { Loader } from '../src/loader.js' +import { Compiler } from '../src/compiler.js' import { slotTag } from '../src/tags/slot.js' -import { Processor } from '../src/processor/index.js' +import { Processor } from '../src/processor.js' import { includeTag } from '../src/tags/include.js' import { componentTag } from '../src/tags/component.js' -import { Template, safeValue } from '../src/template/index.js' - -import { fileURLToPath } from 'node:url' +import { Template, htmlSafe } from '../src/template.js' const tags = { slot: slotTag, component: componentTag, include: includeTag } const fs = new Filesystem(join(path.dirname(fileURLToPath(import.meta.url)), 'views')) @@ -33,13 +32,17 @@ test.group('Template', (group) => { await fs.cleanup() }) - test('run template using the given state', async ({ assert }) => { + test('render template using the given state', async ({ assert }) => { await fs.add('foo.edge', 'Hello {{ username }}') + const processor = new Processor() const compiler = new Compiler(loader, tags, processor, { cache: false }) - const output = new Template(compiler, {}, {}, processor).render('foo', { + const template = new Template(compiler, {}, {}, processor) + + const output = template.render('foo', { username: 'virk', - }) as string + }) + assert.equal(output.trim(), 'Hello virk') }) @@ -47,8 +50,7 @@ test.group('Template', (group) => { await fs.add('foo.edge', 'Hello {{ getUsername() }}') const processor = new Processor() const compiler = new Compiler(loader, tags, processor, { cache: false }) - - const output = new Template( + const template = new Template( compiler, { username: 'virk' }, { @@ -57,18 +59,21 @@ test.group('Template', (group) => { }, }, processor - ).render('foo', {}) as string + ) + + const output = template.render('foo', {}) assert.equal(output.trim(), 'Hello VIRK') }) - test('run partial inside existing state', async ({ assert }) => { + test('compile and render a partial', async ({ assert }) => { await fs.add('foo.edge', 'Hello {{ username }}') const processor = new Processor() const compiler = new Compiler(loader, tags, processor, { cache: false }) const template = new Template(compiler, {}, {}, processor) + const partial = template.compilePartial('foo') - const output = template.compilePartial('foo')(template, { username: 'virk' }) + const output = partial(template, { username: 'virk' }, {}) assert.equal(output.trim(), 'Hello virk') }) @@ -78,9 +83,10 @@ test.group('Template', (group) => { const processor = new Processor() const compiler = new Compiler(loader, tags, processor, { cache: false }) const template = new Template(compiler, {}, {}, processor) + const partial = template.compilePartial('foo', 'user') const user = { username: 'virk' } - const output = template.compilePartial('foo', 'user')(template, {}, {}, user) + const output = partial(template, {}, {}, user) assert.equal(output.trim(), 'Hello virk') }) @@ -181,7 +187,7 @@ test.group('Template', (group) => { const processor = new Processor() const compiler = new Compiler(loader, tags, processor, { cache: false }) const template = new Template(compiler, {}, {}, processor) - assert.equal(template.escape(safeValue('

Hello world

')), '

Hello world

') + assert.equal(template.escape(htmlSafe('

Hello world

')), '

Hello world

') }) test('stringify array before escape', ({ assert }) => { @@ -233,18 +239,13 @@ test.group('Template', (group) => { await fs.add('foo.edge', 'Hello {{ username }}') const processor = new Processor() - processor.process('compiled', ({ compiled }) => { - compiled = `username = 'virk'; \n ${compiled}` - return compiled - }) - const compiler = new Compiler(loader, tags, processor, { cache: true }) const template = new Template(compiler, {}, {}, processor) - assert.equal(template.compilePartial('foo')(template, {}).trim(), 'Hello undefined') - assert.equal( - template.compilePartial('foo', 'username')(template, 'username').trim(), - 'Hello virk' - ) + const partail = template.compilePartial('foo') + assert.equal(partail(template, {}, {}).trim(), 'Hello undefined') + + const partailWithInlineVariables = template.compilePartial('foo', 'username') + assert.equal(partailWithInlineVariables(template, {}, {}, 'virk').trim(), 'Hello virk') }) })