From 8c3bc0698835811474cd6288851b6db3bebd36e0 Mon Sep 17 00:00:00 2001 From: ncpa0cpl <32438334+ncpa0cpl@users.noreply.github.com> Date: Wed, 3 Jan 2024 09:37:15 +0100 Subject: [PATCH] feat: removed the `renderToStringTemplateTag` render function --- README.md | 60 +---- src/html-renderer/element-resolver.ts | 2 +- src/index.ts | 7 - src/string-template-renderer/default-cache.ts | 36 --- src/string-template-renderer/interpolate.ts | 93 -------- .../jsx-elem-to-strings.ts | 207 ------------------ .../render-to-string-template-tag.ts | 77 ------- .../resolve-element.ts | 25 --- .../string-template-tag-type.ts | 4 - .../to-template-string-array.ts | 16 -- .../map-attribute-name.ts | 2 +- 11 files changed, 4 insertions(+), 525 deletions(-) delete mode 100644 src/string-template-renderer/default-cache.ts delete mode 100644 src/string-template-renderer/interpolate.ts delete mode 100644 src/string-template-renderer/jsx-elem-to-strings.ts delete mode 100644 src/string-template-renderer/render-to-string-template-tag.ts delete mode 100644 src/string-template-renderer/resolve-element.ts delete mode 100644 src/string-template-renderer/string-template-tag-type.ts delete mode 100644 src/string-template-renderer/to-template-string-array.ts rename src/{string-template-renderer => utilities}/map-attribute-name.ts (85%) diff --git a/README.md b/README.md index f7ddfbd..18d5450 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,8 @@ A JSX based html templating engine for browsers or Node environments. 1. [Adding custom web component tags](#adding-custom-web-component-tags) 2. [Adding a global html attribute](#adding-a-global-html-attribute) 8. [Express JS View Engine](#express-js-view-engine) -9. [Rendering to a string tag template](#rendering-to-a-string-tag-template) - 1. [Example](#example-2) -10. [Monkey-Patching type definitions](#monkey-patching-type-definitions) -11. [Contributing](#contributing) +9. [Monkey-Patching type definitions](#monkey-patching-type-definitions) +10. [Contributing](#contributing) ## Getting started @@ -348,60 +346,6 @@ app.get("/", (_, resp) => { For this approach to work, the JSX Components must be exported as defaults (ex. `export default () =>
` or `exports.default = () =>
`) and the views must be transpiled to `.js` files. -## Rendering to a string tag template - -A [string tag template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) is special type of function that can be used for custom parsing of template literals. - -JSXTE allows you to leverage any existing string tag templates but with a JSX syntax instead of a template literal. - -### Example - -```tsx -// using template literal: -import { html } from "some-library"; -const props = { - /* ... */ -}; -const result = html`
-

${props.header}

-
`; - -// using JSXTE: -import { renderToStringTemplateTag } from "jsxte"; -import { html } from "some-library"; -const props = { - /* ... */ -}; -const result = renderToStringTemplateTag( - html, -
-

{props.header}

-
, -); -``` - -If the string tag template function uses non standard html attribute names (ex. `className` instead of `class` or `@click` instead of `onclick`) you can map the attribute names render by this method by specifying mapping for those: - -```tsx -const result = renderToStringTemplateTag( - html, -
- -
, - { - attributeMap: { - onclick: "@click", - class: "className", - }, - }, -); -``` - ## Monkey-Patching type definitions It is possible to monkey-patch type definition of all HTML tags and add new attributes to them. diff --git a/src/html-renderer/element-resolver.ts b/src/html-renderer/element-resolver.ts index 6277382..3b38ce2 100644 --- a/src/html-renderer/element-resolver.ts +++ b/src/html-renderer/element-resolver.ts @@ -1,4 +1,4 @@ -import { mapAttributeName } from "../string-template-renderer/map-attribute-name"; +import { mapAttributeName } from "../utilities/map-attribute-name"; import type { HTMLElementStruct, RendererHTMLAttributes } from "./types"; export class HTMLElementResolver { diff --git a/src/index.ts b/src/index.ts index 4f008a9..536c2e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,6 @@ import "./utilities/array-flat-polyfill"; export * from "./express/index"; export * from "./jsx/jsx.types"; -export { - Interpolate, - InterpolateTag, -} from "./string-template-renderer/interpolate"; -export { DefaultTemplateArrayCache } from "./string-template-renderer/default-cache"; export { ErrorBoundary } from "./error-boundary/error-boundary"; export { defineContext } from "./component-api/component-api"; export { @@ -18,7 +13,6 @@ export { renderToJson, renderToJsonAsync, } from "./json-renderer/render-to-json"; -export { renderToStringTemplateTag } from "./string-template-renderer/render-to-string-template-tag"; export { memo } from "./utilities/memo"; export { createElement } from "./jsx/jsx-runtime"; export { JsxteRenderError } from "./jsxte-render-error"; @@ -28,7 +22,6 @@ export type { ComponentApi, } from "./component-api/component-api"; export type { AttributeBool, HTMLProps } from "./jsx/base-html-tag-props"; -export type { StringTemplateParserOptions } from "./string-template-renderer/render-to-string-template-tag"; export type { Crossorigin } from "./jsx/prop-types/shared/crossorigin"; export type { RefererPolicy } from "./jsx/prop-types/shared/referer-policy"; export type { Target } from "./jsx/prop-types/shared/target"; diff --git a/src/string-template-renderer/default-cache.ts b/src/string-template-renderer/default-cache.ts deleted file mode 100644 index 421c3cf..0000000 --- a/src/string-template-renderer/default-cache.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { TemplateLiteralCache } from "./render-to-string-template-tag"; - -const arrIsEqual = ( - a: TemplateStringsArray, - b: TemplateStringsArray, -) => { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; - return true; -}; - -export class DefaultTemplateArrayCache - implements TemplateLiteralCache -{ - private entries: Array = []; - - set(templateArray: TemplateStringsArray) { - this.entries.push(templateArray); - } - - get( - templateArray: TemplateStringsArray, - ): TemplateStringsArray | undefined { - for (let i = 0; i < this.entries.length; i++) { - if (arrIsEqual(this.entries[i]!, templateArray)) { - return this.entries[i]; - } - } - - return undefined; - } - - clear() { - this.entries = []; - } -} diff --git a/src/string-template-renderer/interpolate.ts b/src/string-template-renderer/interpolate.ts deleted file mode 100644 index 95c2b06..0000000 --- a/src/string-template-renderer/interpolate.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Only fo use with the `renderToStringTemplateTag` render function. - * - * Whatever is passed to this component as children will be interpolated into - * the template string. - * - * Example - * - * ```tsx - * function someFunction() {} - * - * renderToStringTemplateTag( - * html, - *
- * - * {someFunction} - * - *
- * ); - * - * // The above is equivalent to: - * - * html` - *
- * ${someFunction} - *
- * `; - * ``` - */ -export class Interpolate { - /** - * @internal - */ - static _isInterpolate(o: any): o is typeof Interpolate { - const canBeClass = typeof o === "function"; - const isNotNull = o !== null; - - if (!canBeClass || !isNotNull) return false; - - const baseName = (o as any as typeof Interpolate)._baseName; - return baseName === this._baseName; - } - - private static _baseName = "Interpolate"; - - constructor(_: { children?: any }) {} -} - -/** - * Only fo use with the `renderToStringTemplateTag` render function. - * - * Whatever is passed to this component as children will be rendered using the - * tag function and interpolated into the template string. - * - * Example - * - * ```tsx - * renderToStringTemplateTag( - * html, - *
- * - * Hello World - * - *
- * ); - * - * // The above is equivalent to: - * - * html` - *
- * ${html`Hello World`} - *
- * `; - * ``` - */ -export class InterpolateTag { - /** - * @internal - */ - static _isInterpolateRender(o: any): o is typeof InterpolateTag { - const canBeClass = typeof o === "function"; - const isNotNull = o !== null; - - if (!canBeClass || !isNotNull) return false; - - const baseName = (o as any as typeof InterpolateTag)._baseName; - return baseName === this._baseName; - } - - private static _baseName = "InterpolateTag"; - - constructor(_: { children?: any }) {} -} diff --git a/src/string-template-renderer/jsx-elem-to-strings.ts b/src/string-template-renderer/jsx-elem-to-strings.ts deleted file mode 100644 index 8f07168..0000000 --- a/src/string-template-renderer/jsx-elem-to-strings.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { ComponentApi } from "../component-api/component-api"; -import { ErrorBoundary } from "../error-boundary/error-boundary"; -import { createElement } from "../jsx-runtime"; -import { SELF_CLOSING_TAG_LIST } from "../utilities/self-closing-tag-list"; -import { Interpolate, InterpolateTag } from "./interpolate"; -import { mapAttributeName } from "./map-attribute-name"; -import type { StringTemplateParserOptions } from "./render-to-string-template-tag"; -import { resolveElement } from "./resolve-element"; -import type { StringTemplateTag } from "./string-template-tag-type"; -import { toTemplateStringArray } from "./to-template-string-array"; - -export type StringTemplateParserInternalOptions = - StringTemplateParserOptions & { - tag: StringTemplateTag; - }; - -function assertSyncElem( - e: JSXTE.TagElement | JSXTE.TextNodeElement | JSX.AsyncElement, -): asserts e is JSXTE.SyncElement {} - -type TagFunctionArgs = [string[], any[]]; - -const concatToLastStringOrPush = (a: TagFunctionArgs, s?: string) => { - if (s) { - if (a[0][a[0].length - 1] !== undefined) { - a[0][a[0].length - 1] += s; - } else { - a[0].push(s); - } - } -}; - -export const jsxElemToTagFuncArgsSync = ( - element: JSX.Element, - options: StringTemplateParserInternalOptions, - _componentApi: ComponentApi = ComponentApi.create(), -): TagFunctionArgs => { - // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check - switch (typeof element) { - case "string": - return [["", ""], [element]]; - case "bigint": - case "number": - return [["", ""], [String(element)]]; - case "boolean": - case "function": - case "symbol": - case "undefined": - return [["", ""], [""]]; - } - - if (element === null) return [["", ""], [""]]; - - const { attributeMap = {} } = options; - - const componentApi = _componentApi - ? ComponentApi.clone(_componentApi) - : ComponentApi.create({ attributeMap }); - - assertSyncElem(element); - - if (element.type === "textNode") { - return [["", ""], [element.text]]; - } - - if (typeof element.tag !== "string") { - if (ErrorBoundary._isErrorBoundary(element.tag)) { - const boundary = new element.tag(element.props); - - try { - const subElem = boundary.render( - element.props, - componentApi, - ) as any as JSXTE.SyncElement; - - if (subElem instanceof Promise) { - throw new Error( - `Encountered an async Component: [${element.tag.name}.render] Asynchronous Component's cannot be parsed by rendertoHTML. If you wante to use asynchronous components use renderToHtmlAsync instead.`, - ); - } - - return jsxElemToTagFuncArgsSync(subElem, options, componentApi); - } catch (e) { - const fallbackElem = boundary.onError( - e, - element.props, - componentApi, - ) as any as JSXTE.SyncElement; - - if (fallbackElem instanceof Promise) { - throw new Error( - `Encountered an async Component: [${element.tag.name}.onError] Asynchronous Component's cannot be parsed by rendertoHTML. If you wante to use asynchronous components use renderToHtmlAsync instead.`, - ); - } - - return jsxElemToTagFuncArgsSync(fallbackElem, options, componentApi); - } - } - - if (Interpolate._isInterpolate(element.tag)) { - const results: TagFunctionArgs = [[], []]; - - results[0].push("", ""); - results[1].push(element.props.children); - - return results; - } - - if (InterpolateTag._isInterpolateRender(element.tag)) { - const results: TagFunctionArgs = [[], []]; - - const [tmpTsa, params] = jsxElemToTagFuncArgsSync( - createElement("", element.props), - options, - ); - const templateStringArray = toTemplateStringArray(tmpTsa); - - results[0].push("", ""); - results[1].push(options.tag(templateStringArray, ...params)); - - return results; - } - - const subElem = element.tag( - element.props, - componentApi, - ) as any as JSXTE.SyncElement; - - if (subElem instanceof Promise) { - throw new Error( - `Encountered an async Component: [${element.tag.name}] Asynchronous Component's cannot be parsed by rendertoHTML. If you wante to use asynchronous components use renderToHtmlAsync instead.`, - ); - } - - return jsxElemToTagFuncArgsSync(subElem, options, componentApi); - } else { - const { attributes, children } = resolveElement(element); - - if (element.tag === "") { - const results: TagFunctionArgs = [[], []]; - - for (let i = 0; i < children.length; i++) { - const child = children[i]!; - const [[first, ...strings], tagParams] = jsxElemToTagFuncArgsSync( - child, - options, - componentApi, - ); - - concatToLastStringOrPush(results, first); - - results[0] = results[0].concat(strings); - results[1] = results[1].concat(tagParams); - } - - return results; - } else { - const isSelfClosingTag = - children.length === 0 && SELF_CLOSING_TAG_LIST.includes(element.tag); - - const results: TagFunctionArgs = [[], []]; - - results[0].push(`<${element.tag}`); - - const attrList = Object.entries(attributes); - for (let index = 0; index < attrList.length; index++) { - const [attrName, value] = attrList[index]!; - - if (value === false || value === null || value === undefined) { - continue; - } - - concatToLastStringOrPush( - results, - ` ${mapAttributeName(attrName, attributeMap)}="`, - ); - - results[1].push(value === true ? attrName : value); - results[0].push('"'); - } - - if (isSelfClosingTag) { - concatToLastStringOrPush(results, "/>"); - } else { - concatToLastStringOrPush(results, ">"); - - for (let i = 0; i < children.length; i++) { - const child = children[i]!; - const [[first, ...strings], tagParams] = jsxElemToTagFuncArgsSync( - child, - options, - componentApi, - ); - - concatToLastStringOrPush(results, first); - - results[0] = results[0].concat(strings); - results[1] = results[1].concat(tagParams); - } - - concatToLastStringOrPush(results, ``); - } - - return results; - } - } -}; diff --git a/src/string-template-renderer/render-to-string-template-tag.ts b/src/string-template-renderer/render-to-string-template-tag.ts deleted file mode 100644 index 4a58b0f..0000000 --- a/src/string-template-renderer/render-to-string-template-tag.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable max-len */ -import { jsxElemToTagFuncArgsSync } from "./jsx-elem-to-strings"; -import type { StringTemplateTag } from "./string-template-tag-type"; -import { toTemplateStringArray } from "./to-template-string-array"; - -export interface TemplateLiteralCache { - get: ( - templateStringsArray: TemplateStringsArray, - ) => TemplateStringsArray | undefined; - set: (templateStringsArray: TemplateStringsArray) => void; -} - -export type StringTemplateParserOptions = { - /** - * Mappings for html attribute names. Attributes defined in here - * will get renamed during the rendering to whatever is set. - * - * @example - * const options = { - * attributeMap: { onclick: "@click" }, - * }; - * - * renderToStringTemplateTag( - * html, - * , - * options - * ); - * // Will give the same result as - * html``; - */ - attributeMap?: Record; - /** - * Template literal tags have a specific behavior to them, the - * `TemplateStringsArray` is memoized for evaluations of the same - * template literal. This is possible because the - * `TemplateStringsArray` contains only parts of the template - * literal that are not dynamic, and will never change. - * - * JSXTE templates can produce different `TemplateStringsArray` so - * similar behavior is not possible. - * - * By providing a cache object you can simulate a similar behavior - * to real template literals. Cache object is expected to be an - * object with a `get` and `set` method. - * - * The `get` method shall compare the given `TemplateStringsArray` - * to the ones in the cache and if a value that is deeply equal to - * it exists, return it. - * - * The `set` method shall store the given `TemplateStringsArray` in - * the cache. - */ - cache?: TemplateLiteralCache; -}; - -export const renderToStringTemplateTag = ( - tag: StringTemplateTag, - Component: JSX.Element, - options: StringTemplateParserOptions = {}, -) => { - const [tmpTsa, params] = jsxElemToTagFuncArgsSync(Component, { - ...options, - tag, - }); - - const templateStringArray = toTemplateStringArray(tmpTsa); - - const cached = options?.cache?.get(templateStringArray as any); - - if (cached) { - return tag(cached as any, ...params); - } - - options?.cache?.set(templateStringArray as any); - - return tag(templateStringArray as any, ...params); -}; diff --git a/src/string-template-renderer/resolve-element.ts b/src/string-template-renderer/resolve-element.ts deleted file mode 100644 index a127b42..0000000 --- a/src/string-template-renderer/resolve-element.ts +++ /dev/null @@ -1,25 +0,0 @@ -type GetArrayFromUnion = Exclude>>; -type GetNonArraysFromUnion = Exclude>; - -type ArrayType = A extends Array ? T : never; - -type AsArray = [GetArrayFromUnion] extends [never] - ? GetNonArraysFromUnion[] - : Array> | GetNonArraysFromUnion>; - -export function asArray(v: T): AsArray { - if (Array.isArray(v)) { - return v; - } else { - return [v] as AsArray; - } -} - -export const resolveElement = (element: JSXTE.TagElement) => { - const { children, ...attributes } = element.props; - - return { - attributes, - children: asArray(children ?? []).flat() as JSX.Element[], - }; -}; diff --git a/src/string-template-renderer/string-template-tag-type.ts b/src/string-template-renderer/string-template-tag-type.ts deleted file mode 100644 index b40f34b..0000000 --- a/src/string-template-renderer/string-template-tag-type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type StringTemplateTag = ( - template: TemplateStringsArray, - ...arg: any[] -) => R; diff --git a/src/string-template-renderer/to-template-string-array.ts b/src/string-template-renderer/to-template-string-array.ts deleted file mode 100644 index 16bb124..0000000 --- a/src/string-template-renderer/to-template-string-array.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const toTemplateStringArray = ( - orgArr: string[], -): TemplateStringsArray => { - const templateStringArray = - orgArr.slice() as any as TemplateStringsArray; - - Object.defineProperty(templateStringArray, "raw", { - value: orgArr, - configurable: false, - enumerable: false, - writable: false, - }); - Object.freeze(templateStringArray); - - return templateStringArray; -}; diff --git a/src/string-template-renderer/map-attribute-name.ts b/src/utilities/map-attribute-name.ts similarity index 85% rename from src/string-template-renderer/map-attribute-name.ts rename to src/utilities/map-attribute-name.ts index 82e1c99..4d352ed 100644 --- a/src/string-template-renderer/map-attribute-name.ts +++ b/src/utilities/map-attribute-name.ts @@ -1,6 +1,6 @@ export const mapAttributeName = ( attributeName: string, - map: Record + map: Record, ): string => { if (attributeName in map && map[attributeName]) { return map[attributeName]!;