UNPKG

edge.js

Version:
648 lines (638 loc) 17.4 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/template.ts import he from "he"; import { EdgeError as EdgeError2 } from "edge-error"; import lodash3 from "@poppinss/utils/lodash"; import Macroable from "@poppinss/macroable"; // src/edge/stacks.ts import { EOL } from "os"; var Stacks = class { #contentSources = /* @__PURE__ */ new Map(); /** * Pre-seeded content before the placeholder has been * defined. */ #seededPlaceholders = /* @__PURE__ */ new Map(); /** * Placeholders to be flushed */ #placeholders = /* @__PURE__ */ new Map(); /** * Returns the placeholder name for a given stack */ #createPlaceholder(name) { return `<!-- @edge.stacks.${name} -->`; } /** * Create a new stack placeholder. Multiple calls to this method * with the same name results in an exception. */ create(name) { if (this.#placeholders.has(name)) { throw new Error(`Cannot declare stack "${name}" for multiple times`); } const seededPlaceholder = this.#seededPlaceholders.get(name) || []; this.#seededPlaceholders.delete(name); this.#placeholders.set(name, [...seededPlaceholder]); return this.#createPlaceholder(name); } /** * Push content inside a given stack. Content can be pre-seeded * without creating a stack */ pushTo(name, contents) { let placeholder = this.#placeholders.get(name); if (!placeholder) { if (!this.#seededPlaceholders.has(name)) { this.#seededPlaceholders.set(name, []); } const seededPlaceholder = this.#seededPlaceholders.get(name); seededPlaceholder.push(contents); return this; } placeholder.push(contents); return this; } /** * Push contents to a stack with a unique source id. A * source can only push once to a given stack. */ pushOnceTo(name, sourceId, contents) { const contentSources = this.#contentSources.get(name); if (contentSources && contentSources.has(sourceId)) { return; } this.pushTo(name, contents); if (contentSources) { contentSources.add(sourceId); } else { this.#contentSources.set(name, /* @__PURE__ */ new Set([sourceId])); } } /** * Fill placeholders with their actual content */ fillPlaceholders(contents) { for (let [name, sources] of this.#placeholders) { contents = contents.replace(this.#createPlaceholder(name), sources.join(EOL)); } this.#placeholders.clear(); this.#seededPlaceholders.clear(); return contents; } }; // src/migrate/props.ts import lodash2 from "@poppinss/utils/lodash"; import stringifyAttributes2 from "stringify-attributes"; // src/component/props.ts import lodash from "@poppinss/utils/lodash"; // src/utils.ts import classNames from "classnames"; import { EdgeError } from "edge-error"; import { find, html } from "property-information"; function definePropertyInformation(property, value) { html.normal[property] = property; html.property[property] = { attribute: property, boolean: true, property, space: "html", booleanish: false, commaOrSpaceSeparated: false, commaSeparated: false, spaceSeparated: false, number: false, overloadedBoolean: false, defined: false, mustUseProperty: false, ...value }; } definePropertyInformation("x-cloak"); definePropertyInformation("x-ignore"); definePropertyInformation("x-transition:enterstart", { attribute: "x-transition:enter-start", property: "x-transition:enterStart", boolean: false, spaceSeparated: true, commaOrSpaceSeparated: true }); definePropertyInformation("x-transition:enterend", { attribute: "x-transition:enter-end", property: "x-transition:enterEnd", boolean: false, spaceSeparated: true, commaOrSpaceSeparated: true }); definePropertyInformation("x-transition:leavestart", { attribute: "x-transition:leave-start", property: "x-transition:leaveStart", boolean: false, spaceSeparated: true, commaOrSpaceSeparated: true }); definePropertyInformation("x-transition:leaveend", { attribute: "x-transition:leave-end", property: "x-transition:leaveEnd", boolean: false, spaceSeparated: true, commaOrSpaceSeparated: true }); var alpineNamespaces = { x: "x-", xOn: "x-on:", xBind: "x-bind:", xTransition: "x-transition:" }; function unallowedExpression(message, filename, loc) { throw new EdgeError(message, "E_UNALLOWED_EXPRESSION", { line: loc.line, col: loc.col, filename }); } function isSubsetOf(expression, expressions, errorCallback) { if (!expressions.includes(expression.type)) { errorCallback(); } } function isNotSubsetOf(expression, expressions, errorCallback) { if (expressions.includes(expression.type)) { errorCallback(); } } function parseJsArg(parser, token) { return parser.utils.transformAst( parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser ); } function each(collection, iteratee) { if (Array.isArray(collection)) { for (let [key, value] of collection.entries()) { iteratee(value, key); } return; } if (typeof collection === "string") { let index = 0; for (let value of collection) { iteratee(value, index++); } return; } if (collection && typeof collection === "object") { for (let [key, value] of Object.entries(collection)) { iteratee(value, key); } } } async function asyncEach(collection, iteratee) { if (Array.isArray(collection)) { for (let [key, value] of collection.entries()) { await iteratee(value, key); } return; } if (typeof collection === "string") { let index = 0; for (let value of collection) { await iteratee(value, index++); } return; } if (collection && typeof collection === "object") { for (let [key, value] of Object.entries(collection)) { await iteratee(value, key); } } } var StringifiedObject = class { #obj = ""; addSpread(key) { this.#obj += this.#obj.length ? `, ${key}` : `${key}`; } /** * Add key/value pair to the object. * * ```js * stringifiedObject.add('username', `'virk'`) * ``` */ add(key, value, isComputed = false) { key = isComputed ? `[${key}]` : key; this.#obj += this.#obj.length ? `, ${key}: ${value}` : `${key}: ${value}`; } /** * Returns the object alike string back. * * ```js * stringifiedObject.flush() * * // returns * `{ username: 'virk' }` * ``` */ flush() { const obj = `{ ${this.#obj} }`; this.#obj = ""; return obj; } }; function stringifyAttributes(props, namespace) { const attributes = Object.keys(props); if (attributes.length === 0) { return ""; } return attributes.reduce((result, key) => { let value = props[key]; key = namespace ? `${namespace}${key}` : key; if (!value) { return result; } if (alpineNamespaces[key] && typeof value === "object") { result = result.concat(stringifyAttributes(value, alpineNamespaces[key])); return result; } const propInfo = find(html, key); if (!propInfo) { return result; } const attribute = propInfo.attribute; if (value === true) { result.push(attribute); return result; } if (key === "class") { value = `"${classNames(value)}"`; } else if (Array.isArray(value)) { value = `"${value.join(propInfo.commaSeparated ? "," : " ")}"`; } else { value = `"${String(value)}"`; } result.push(`${attribute}=${value}`); return result; }, []).join(" "); } var seed = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; var nanoid = (length = 15) => { let output = ""; const random = crypto.getRandomValues(new Uint8Array(length)); for (let n = 0; n < length; n++) { output += seed[63 & random[n]]; } return output; }; // src/component/props.ts var ComponentProps = class _ComponentProps { #values; constructor(values) { this.#values = values; Object.assign(this, values); } /** * Create a typed instance of Component props with properties */ static create(values) { return new _ComponentProps(values); } /** * Reference to props raw values */ all() { return this.#values; } /** * Check if a key exists */ has(key) { return lodash.has(this.#values, key); } /** * Get key value */ get(key, defaultValue) { return lodash.get(this.#values, key, defaultValue); } /** * Returns a new props bag with only the mentioned keys */ only(keys) { return new _ComponentProps(lodash.pick(this.#values, keys)); } /** * Returns a new props bag with except the mentioned keys */ except(keys) { return new _ComponentProps(lodash.omit(this.#values, keys)); } /** * Merge defaults with the props * * - All other attributes will be overwritten when defined in props * - Classes will be merged together. */ merge(values) { if (values.class && this.#values["class"]) { const classesSet = /* @__PURE__ */ new Set(); (Array.isArray(values.class) ? values.class : [values]).forEach((item) => { classesSet.add(item); }); (Array.isArray(this.#values["class"]) ? this.#values["class"] : [this.#values["class"]]).forEach((item) => { classesSet.add(item); }); return new _ComponentProps({ ...values, ...this.#values, class: Array.from(classesSet) }); } return new _ComponentProps({ ...values, ...this.#values }); } /** * Merge defaults with the props, if the given condition is truthy */ mergeIf(conditional, values) { if (conditional) { return this.merge(values); } return this; } /** * Merge defaults with the props, if the given condition is falsy */ mergeUnless(conditional, values) { if (!conditional) { return this.merge(values); } return this; } /** * Converts props to HTML attributes */ toAttrs() { return htmlSafe(stringifyAttributes(this.#values)); } }; // src/migrate/props.ts var Props = class { /** * Use ".next" property to migrate to newer * api */ next; constructor(props) { this[Symbol.for("options")] = { props }; Object.assign(this, props); this.next = new ComponentProps(props); } /** * Merges the className attribute with the class attribute */ #mergeClassAttributes(props) { if (props.className) { if (!props.class) { props.class = []; } if (!Array.isArray(props.class)) { props.class = [props.class]; } props.class = props.class.concat(props.className); props.className = false; } return props; } /** * Find if a key exists inside the props */ has(key) { const value = this.get(key); return value !== void 0 && value !== null; } /** * Get value for a given key */ get(key, defaultValue) { return lodash2.get(this.all(), key, defaultValue); } /** * Returns all the props */ all() { return this[Symbol.for("options")].props; } /** * Validate prop value */ validate(key, validateFn) { const value = this.get(key); validateFn(key, value); } /** * Return values for only the given keys */ only(keys) { return lodash2.pick(this.all(), keys); } /** * Return values except the given keys */ except(keys) { return lodash2.omit(this.all(), keys); } /** * Serialize all props to a string of HTML attributes */ serialize(mergeProps, prioritizeInline = true) { const attributes = prioritizeInline ? lodash2.merge({}, this.all(), mergeProps) : lodash2.merge({}, mergeProps, this.all()); return htmlSafe(stringifyAttributes2(this.#mergeClassAttributes(attributes))); } /** * Serialize only the given keys to a string of HTML attributes */ serializeOnly(keys, mergeProps, prioritizeInline = true) { const attributes = prioritizeInline ? lodash2.merge({}, this.only(keys), mergeProps) : lodash2.merge({}, mergeProps, this.only(keys)); return htmlSafe(stringifyAttributes2(this.#mergeClassAttributes(attributes))); } /** * Serialize except the given keys to a string of HTML attributes */ serializeExcept(keys, mergeProps, prioritizeInline = true) { const attributes = prioritizeInline ? lodash2.merge({}, this.except(keys), mergeProps) : lodash2.merge({}, mergeProps, this.except(keys)); return htmlSafe(stringifyAttributes2(this.#mergeClassAttributes(attributes))); } }; // src/template.ts var SafeValue = class { constructor(value) { this.value = value; } }; function escape(input) { return input instanceof SafeValue ? input.value : he.escape(String(input)); } function htmlSafe(value) { return new SafeValue(value); } var Template = class extends Macroable { #compiler; #processor; /** * The shared state is used to hold the globals and locals, * since it is shared with components too. */ #sharedState; /** * Template stacks holds a collection of placeholders * and their content to be filled before returning * the output. */ stacks = new Stacks(); constructor(compiler, globals, locals, processor) { super(); this.#compiler = compiler; this.#processor = processor; this.#sharedState = compiler.compat ? lodash3.merge({}, globals, locals) : { ...globals, ...locals }; } /** * Trims top and bottom new lines from the content */ #trimTopBottomNewLines(value) { return value.replace(/^\n|^\r\n/, "").replace(/\n$|\r\n$/, ""); } /** * Render a compiled template with state */ #renderCompiled(compiledTemplate, state) { const templateState = { ...this.#sharedState, ...state }; const $context = {}; if (this.#compiler.async) { return compiledTemplate(this, templateState, $context).then((output2) => { output2 = this.#trimTopBottomNewLines(output2); output2 = this.stacks.fillPlaceholders(output2); return this.#processor.executeOutput({ output: output2, template: this, state: templateState }); }); } let output = this.#trimTopBottomNewLines(compiledTemplate(this, templateState, $context)); output = this.stacks.fillPlaceholders(output); return this.#processor.executeOutput({ output, template: this, state: templateState }); } /** * Render a partial * * ```js * const partialFn = template.compilePartial('includes/user') * * // render and use output * partialFn(template, state, ctx) * ``` */ compilePartial(templatePath, ...localVariables) { return this.#compiler.compile(templatePath, localVariables); } /** * Render a component * * ```js * const componentFn = template.compileComponent('components/button') * * // render and use output * componentFn(template, template.getComponentState(props, slots, caller), ctx) * ``` */ compileComponent(templatePath) { return this.#compiler.compile(templatePath); } /** * Returns the isolated state for a given component */ getComponentState(props, slots, caller) { return { ...this.#sharedState, ...props, $slots: slots, $caller: caller, $props: this.#compiler.compat ? new Props(props) : new ComponentProps(props) }; } /** * Render a template with it's state. * * ```js * template.render('welcome', { key: 'value' }) * ``` */ render(template, state) { let compiledTemplate = this.#compiler.compile(template); return this.#renderCompiled(compiledTemplate, state); } /** * Render template from a raw string * * ```js * template.renderRaw('Hello {{ username }}', { username: 'virk' }) * ``` */ renderRaw(contents, state, templatePath) { let compiledTemplate = this.#compiler.compileRaw(contents, templatePath); return this.#renderCompiled(compiledTemplate, state); } /** * Escapes the value to be HTML safe. Only strings are escaped * and rest all values will be returned as it is. */ escape(input) { return escape(input); } /** * Creates an instance of the EdgeError */ createError(errorMessage, filename, lineNumber, column) { return new EdgeError2(errorMessage, "E_RUNTIME_EXCEPTION", { filename, line: lineNumber, col: column }); } /** * Throws EdgeError. Use "createError" to create a new * error instance */ newError(errorMessage, filename, lineNumber, column) { throw this.createError(errorMessage, filename, lineNumber, column); } /** * Rethrows the runtime exception by re-constructing the error message * to point back to the original filename */ reThrow(error, filename, lineNumber) { if (error instanceof EdgeError2) { throw error; } const message = error.message.replace(/state\./, ""); throw new EdgeError2(message, "E_RUNTIME_EXCEPTION", { filename, line: lineNumber, col: 0 }); } }; export { __export, unallowedExpression, isSubsetOf, isNotSubsetOf, parseJsArg, each, asyncEach, StringifiedObject, stringifyAttributes, nanoid, escape, htmlSafe, Template };