@kitajs/html
Advanced tools
Comparing version 2.2.2 to 3.0.0
@@ -7,8 +7,8 @@ // This file extends JSX namespace to allow any tag/attribute combination. | ||
interface HtmlTag { | ||
[name: string]: unknown | ||
[name: string]: unknown; | ||
} | ||
interface IntrinsicElements { | ||
[name: string]: HtmlTag | ||
[name: string]: HtmlTag; | ||
} | ||
} |
255
htmx.d.ts
@@ -10,7 +10,5 @@ // This file is a result from https://github.com/Desdaemon/typed-htmx and https://htmx.org | ||
declare namespace Htmx { | ||
/** | ||
* Either `true`, `false`, `"true"` or `"false"`. | ||
*/ | ||
type BoolStr = boolean | 'true' | 'false' | ||
type AnyStr = string & {} | ||
/** Either `true`, `false`, `"true"` or `"false"`. */ | ||
type BoolStr = boolean | 'true' | 'false'; | ||
type AnyStr = string & {}; | ||
type HxSwap = | ||
@@ -26,12 +24,11 @@ | 'innerHTML' | ||
| 'morph' | ||
| 'morphdom' | ||
| 'morphdom'; | ||
/** | ||
* Either `this` which refers to the element itself, or a modifier followed by a CSS selector, e.g. `closest form`. | ||
* Either `this` which refers to the element itself, or a modifier followed by a CSS | ||
* selector, e.g. `closest form`. | ||
*/ | ||
type HxTarget = 'this' | 'closest ' | 'find ' | 'next ' | 'previous ' | ||
type HxTarget = 'this' | 'closest ' | 'find ' | 'next ' | 'previous '; | ||
/** | ||
* A CSS selector, followed by one of these sync strategies, e.g. `form:abort`. | ||
*/ | ||
/** A CSS selector, followed by one of these sync strategies, e.g. `form:abort`. */ | ||
type HxSync = | ||
@@ -44,12 +41,8 @@ | ':drop' | ||
| ':queue last' | ||
| ':queue all' | ||
| ':queue all'; | ||
/** | ||
* Evaluate the values given, you can prefix the values with javascript: or js:. | ||
*/ | ||
type HxHeaders = AnyStr | 'javascript:' | 'js:' | ||
/** Evaluate the values given, you can prefix the values with javascript: or js:. */ | ||
type HxHeaders = AnyStr | 'javascript:' | 'js:'; | ||
/** | ||
* An event followed by one of these modifiers, e.g. `click once`. | ||
*/ | ||
/** An event followed by one of these modifiers, e.g. `click once`. */ | ||
type HxTriggerModifier = | ||
@@ -66,3 +59,3 @@ | ' once' | ||
| ' queue:all' | ||
| ' queue:none' | ||
| ' queue:none'; | ||
@@ -94,8 +87,10 @@ /** | ||
/** | ||
* Includes the commonly-used `X-Requested-With` header that identifies ajax requests in many backend frameworks. | ||
* Includes the commonly-used `X-Requested-With` header that identifies ajax requests | ||
* in many backend frameworks. | ||
* | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/ajax-header.js | ||
* | ||
* @see https://htmx.org/extensions/ajax-header/ | ||
*/ | ||
ajaxHeaders: 'ajax-headers' | ||
ajaxHeaders: 'ajax-headers'; | ||
@@ -106,5 +101,6 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/sse.js | ||
* | ||
* @see https://htmx.org/extensions/server-sent-events/ | ||
*/ | ||
serverSentEvents: 'sse' | ||
serverSentEvents: 'sse'; | ||
@@ -115,5 +111,6 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/ws.js | ||
* | ||
* @see https://htmx.org/extensions/web-sockets/ | ||
*/ | ||
ws: 'ws' | ||
ws: 'ws'; | ||
@@ -124,5 +121,6 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/class-tools.js | ||
* | ||
* @see https://htmx.org/extensions/class-tools/ | ||
*/ | ||
classTools: 'class-tools' | ||
classTools: 'class-tools'; | ||
@@ -133,5 +131,6 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/debug.js | ||
* | ||
* @see https://htmx.org/extensions/debug/ | ||
*/ | ||
debug: 'debug' | ||
debug: 'debug'; | ||
@@ -142,5 +141,6 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/disable-element.js | ||
* | ||
* @see https://htmx.org/extensions/disable-element/ | ||
*/ | ||
disableElement: 'disable-element' | ||
disableElement: 'disable-element'; | ||
@@ -151,5 +151,6 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/event-header.js | ||
* | ||
* @see https://htmx.org/extensions/event-header/ | ||
*/ | ||
eventHeader: 'event-header' | ||
eventHeader: 'event-header'; | ||
@@ -160,21 +161,26 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/head-support.js | ||
* | ||
* @see https://htmx.org/extensions/head-support/ | ||
*/ | ||
headSupport: 'head-support' | ||
headSupport: 'head-support'; | ||
/** | ||
* Support for [Idiomorph](https://github.com/bigskysoftware/idiomorph), an alternative swapping mechanism for htmx. | ||
* Support for [Idiomorph](https://github.com/bigskysoftware/idiomorph), an | ||
* alternative swapping mechanism for htmx. | ||
* | ||
* CDN: https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js | ||
* | ||
* @see https://github.com/bigskysoftware/idiomorph#htmx | ||
*/ | ||
idiomorph: 'morph' | ||
idiomorph: 'morph'; | ||
/** | ||
* Use JSON encoding in the body of requests, rather than the default `x-www-form-urlencoded`. | ||
* Use JSON encoding in the body of requests, rather than the default | ||
* `x-www-form-urlencoded`. | ||
* | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/json-enc.js | ||
* | ||
* @see https://htmx.org/extensions/json-enc/ | ||
*/ | ||
jsonEncode: 'json-enc' | ||
jsonEncode: 'json-enc'; | ||
@@ -185,52 +191,58 @@ /** | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/loading-states.js | ||
* | ||
* @see https://htmx.org/extensions/loading-states/ | ||
*/ | ||
loadingStates: 'loading-states' | ||
loadingStates: 'loading-states'; | ||
/** | ||
* Support for [morphdom](https://github.com/patrick-steele-idem/morphdom), | ||
* an alternative swapping mechanism for htmx. | ||
* Support for [morphdom](https://github.com/patrick-steele-idem/morphdom), an | ||
* alternative swapping mechanism for htmx. | ||
* | ||
* CDN: https://unpkg.com/htmx.org/dist/ext/morphdom-swap.js | ||
* | ||
* @see https://htmx.org/extensions/morphdom-swap/ | ||
*/ | ||
morphdom: 'morphdom' | ||
morphdom: 'morphdom'; | ||
} | ||
/** | ||
* Definitions for htmx attributes up to 1.9.3. | ||
*/ | ||
/** Definitions for htmx attributes up to 1.9.3. */ | ||
interface Attributes { | ||
/** | ||
* Issues a `GET` to the specified URL. | ||
* | ||
* @see https://htmx.org/attributes/hx-get/ | ||
*/ | ||
['hx-get']?: string | ||
['hx-get']?: string; | ||
/** | ||
* Issues a `POST` to the specified URL. | ||
* | ||
* @see https://htmx.org/attributes/hx-post/ | ||
*/ | ||
['hx-post']?: string | ||
['hx-post']?: string; | ||
/** | ||
* Issues a `PUT` to the specified URL. | ||
* | ||
* @see https://htmx.org/attributes/hx-put/ | ||
*/ | ||
['hx-put']?: string | ||
['hx-put']?: string; | ||
/** | ||
* Issues a `DELETE` to the specified URL. | ||
* | ||
* @see https://htmx.org/attributes/hx-delete/ | ||
*/ | ||
['hx-delete']?: string | ||
['hx-delete']?: string; | ||
/** | ||
* Issues a `PATCH` to the specified URL. | ||
* | ||
* @see https://htmx.org/attributes/hx-patch/ | ||
*/ | ||
['hx-patch']?: string | ||
['hx-patch']?: string; | ||
/** | ||
* Add or remove [progressive enhancement] for links and forms. | ||
* | ||
* @see https://htmx.org/attributes/hx-boost/ | ||
@@ -240,234 +252,273 @@ * | ||
*/ | ||
['hx-boost']?: BoolStr | ||
['hx-boost']?: BoolStr; | ||
/** | ||
* Handle any event with a script inline. | ||
* @see https://htmx.org/attributes/hx-on/ | ||
* @remarks Event listeners on htmx-specific events need to be specified with a spread attribute, and | ||
* are otherwise not supported in vanilla JSX. | ||
* | ||
* @remarks | ||
* Event listeners on htmx-specific events need to be specified with a spread | ||
* attribute, and are otherwise not supported in vanilla JSX. | ||
* | ||
* ```jsx | ||
* <div {...{'hx-on::before-request': '...'}} /> | ||
* <div {...{ 'hx-on::before-request': '...' }} />; | ||
* ``` | ||
* @since 1.9.3 | ||
* @see https://htmx.org/attributes/hx-on/ | ||
*/ | ||
[`hx-on:`]?: string | ||
[`hx-on:`]?: string; | ||
/** | ||
* Handle any event with a script inline. Each listener is specified on a separate line. | ||
* Handle any event with a script inline. Each listener is specified on a separate | ||
* line. | ||
* | ||
* @remarks | ||
* Superseded by `hx-on:$event`, unless IE11 support is required. | ||
* @since 1.9.0 | ||
* @see https://htmx.org/attributes/hx-on/ | ||
* @remarks Superseded by `hx-on:$event`, unless IE11 support is required. | ||
* @since 1.9.0 | ||
*/ | ||
['hx-on']?: string | ||
['hx-on']?: string; | ||
/** | ||
* Pushes the URL into the browser location bar, creating a new history entry. | ||
* | ||
* @see https://htmx.org/attributes/hx-push-url/ | ||
*/ | ||
['hx-push-url']?: BoolStr | AnyStr | ||
['hx-push-url']?: BoolStr | AnyStr; | ||
/** | ||
* Select content to swap in from a response. | ||
* | ||
* @see https://htmx.org/attributes/hx-select/ | ||
*/ | ||
['hx-select']?: string | ||
['hx-select']?: string; | ||
/** | ||
* Select content to swap in from a response, out of band (somewhere other than the target). | ||
* Select content to swap in from a response, out of band (somewhere other than the | ||
* target). | ||
* | ||
* @see https://htmx.org/attributes/hx-select-oob/ | ||
*/ | ||
['hx-select-oob']?: string | ||
['hx-select-oob']?: string; | ||
/** | ||
* Controls how content is swapped in (`outerHTML`, `beforeend`, `afterend`, …). | ||
* @see https://htmx.org/attributes/hx-swap/ | ||
* @see {@linkcode InsertPosition} which is used in [{@linkcode Element.insertAdjacentHTML}](https://developer.mozilla.org/docs/Web/API/Element/insertAdjacentHTML) | ||
* | ||
* @remarks | ||
* - `morph` swaps are part of the {@linkcode Extensions.idiomorph idiomorph} extension. | ||
* - `morphdom` swaps are part of the {@linkcode Extensions.morphdom morphdom} extension. | ||
* | ||
* @see https://htmx.org/attributes/hx-swap/ | ||
* @see {@linkcode InsertPosition} which is used in [{@linkcode Element.insertAdjacentHTML}](https://developer.mozilla.org/docs/Web/API/Element/insertAdjacentHTML) | ||
*/ | ||
['hx-swap']?: HxSwap | AnyStr | ||
['hx-swap']?: HxSwap | AnyStr; | ||
/** | ||
* Marks content in a response to be out of band (should swap in somewhere other than the target). | ||
* Marks content in a response to be out of band (should swap in somewhere other than | ||
* the target). | ||
* | ||
* @see https://htmx.org/attributes/hx-swap-oob/ | ||
*/ | ||
['hx-swap-oob']?: 'true' | HxSwap | AnyStr | ||
['hx-swap-oob']?: 'true' | HxSwap | AnyStr; | ||
/** | ||
* Specifies the target element to be swapped. | ||
* | ||
* @see https://htmx.org/attributes/hx-target/ | ||
*/ | ||
['hx-target']?: HxTarget | AnyStr | ||
['hx-target']?: HxTarget | AnyStr; | ||
/** | ||
* Specifies the event that triggers the request. | ||
* | ||
* @see https://htmx.org/attributes/hx-trigger/ | ||
*/ | ||
['hx-trigger']?: 'every ' | HxTriggerModifier | AnyStr | ||
['hx-trigger']?: 'every ' | HxTriggerModifier | AnyStr; | ||
/** | ||
* Adds values to the parameters to submit with the request (JSON-formatted). | ||
* | ||
* @see https://htmx.org/attributes/hx-vals/ | ||
*/ | ||
['hx-vals']?: HxHeaders | ||
['hx-vals']?: HxHeaders; | ||
/** | ||
* Shows a `confirm()` dialog before issuing a request. | ||
* | ||
* @see https://htmx.org/attributes/hx-confirm/ | ||
*/ | ||
['hx-confirm']?: string | ||
['hx-confirm']?: string; | ||
/** | ||
* Disables htmx processing for the given node and any children nodes. | ||
* | ||
* @see https://htmx.org/attributes/hx-disable/ | ||
*/ | ||
['hx-disable']?: boolean | ||
['hx-disable']?: boolean; | ||
/** | ||
* Control and disable automatic attribute inheritance for child nodes. | ||
* | ||
* @see https://htmx.org/attributes/hx-disinherit/ | ||
*/ | ||
['hx-disinherit']?: '*' | AnyStr | ||
['hx-disinherit']?: '*' | AnyStr; | ||
/** | ||
* Changes the request encoding type. | ||
* | ||
* @see https://htmx.org/attributes/hx-encoding/ | ||
*/ | ||
['hx-encoding']?: 'multipart/form-data' | ||
['hx-encoding']?: 'multipart/form-data'; | ||
/** | ||
* Extensions to use for this element. | ||
* | ||
* @see https://htmx.org/attributes/hx-ext/ | ||
* @see {@linkcode Extensions} for how to declare extensions in JSX. | ||
*/ | ||
['hx-ext']?: Htmx.Extensions[keyof Htmx.Extensions] | 'ignore:' | AnyStr | ||
['hx-ext']?: Htmx.Extensions[keyof Htmx.Extensions] | 'ignore:' | AnyStr; | ||
/** | ||
* Adds to the headers that will be submitted with the request. | ||
* | ||
* @see https://htmx.org/attributes/hx-headers/ | ||
*/ | ||
['hx-headers']?: HxHeaders | AnyStr | ||
['hx-headers']?: HxHeaders | AnyStr; | ||
/** | ||
* Prevent sensitive data being saved to the history cache. | ||
* | ||
* @see https://htmx.org/attributes/hx-history/ | ||
*/ | ||
['hx-history']?: 'false' | ||
['hx-history']?: 'false'; | ||
/** | ||
* The element to snapshot and restore during history navigation. | ||
* | ||
* @see https://htmx.org/attributes/hx-history-elt/ | ||
*/ | ||
['hx-history-elt']?: boolean | ||
['hx-history-elt']?: boolean; | ||
/** | ||
* Include additional data in requests. | ||
* | ||
* @see https://htmx.org/attributes/hx-include/ | ||
*/ | ||
['hx-include']?: string | ||
['hx-include']?: string; | ||
/** | ||
* The element to put the `htmx-request` class on during the request. | ||
* | ||
* @see https://htmx.org/attributes/hx-indicator/ | ||
*/ | ||
['hx-indicator']?: string | ||
['hx-indicator']?: string; | ||
/** | ||
* Filters the parameters that will be submitted with a request. | ||
* | ||
* @see https://htmx.org/attributes/hx-params/ | ||
*/ | ||
['hx-params']?: '*' | 'none' | 'not ' | AnyStr | ||
['hx-params']?: '*' | 'none' | 'not ' | AnyStr; | ||
/** | ||
* Specifies elements to keep unchanged between requests. | ||
* | ||
* @remarks | ||
* `true` is only observed by the `head-support` extension, where it prevents an | ||
* element from being removed from the `<head>`. | ||
* @see https://htmx.org/attributes/hx-preserve/ | ||
* @remarks `true` is only observed by the `head-support` extension, | ||
* where it prevents an element from being removed from the `<head>`. | ||
*/ | ||
['hx-preserve']?: boolean | 'true' | ||
['hx-preserve']?: boolean | 'true'; | ||
/** | ||
* Shows a `prompt()` before submitting a request. | ||
* | ||
* @see https://htmx.org/attributes/hx-prompt/ | ||
*/ | ||
['hx-prompt']?: string | ||
['hx-prompt']?: string; | ||
/** | ||
* Replace the URL in the browser location bar. | ||
* | ||
* @see https://htmx.org/attributes/hx-replace-url/ | ||
*/ | ||
['hx-replace-url']?: BoolStr | AnyStr | ||
['hx-replace-url']?: BoolStr | AnyStr; | ||
/** | ||
* Configures various aspects of the request. | ||
* | ||
* @see https://htmx.org/attributes/hx-request/ | ||
*/ | ||
['hx-request']?: | ||
| `"timeout": ` | ||
| `"credentials": ` | ||
| `"noHeaders": ` | ||
| HxHeaders | ||
['hx-request']?: `"timeout": ` | `"credentials": ` | `"noHeaders": ` | HxHeaders; | ||
/** | ||
* Control how requests made by different elements are synchronized. | ||
* | ||
* @see https://htmx.org/attributes/hx-sync/ | ||
*/ | ||
['hx-sync']?: HxSync | ||
['hx-sync']?: HxSync; | ||
/** | ||
* Force elements to validate themselves before a request. | ||
* | ||
* @see https://htmx.org/attributes/hx-validate/ | ||
*/ | ||
['hx-validate']?: boolean | ||
['hx-validate']?: boolean; | ||
/** | ||
* Adds values dynamically to the parameters to submit with the request. | ||
* @deprecated superseded by `hx-vals` | ||
* | ||
* @deprecated Superseded by `hx-vals` | ||
*/ | ||
['hx-vars']?: AnyStr | ||
['hx-vars']?: AnyStr; | ||
/** | ||
* The URL of the SSE server. | ||
* | ||
* @see https://htmx.org/extensions/server-sent-events/ | ||
*/ | ||
['sse-connect']?: string | ||
['sse-connect']?: string; | ||
/** | ||
* The name of the message to swap into the DOM. | ||
* | ||
* @see https://htmx.org/extensions/server-sent-events/ | ||
*/ | ||
['sse-swap']?: string | ||
['sse-swap']?: string; | ||
/** | ||
* A URL to establish a WebSocket connection against. | ||
* | ||
* @see https://htmx.org/extensions/web-sockets/ | ||
*/ | ||
['ws-connect']?: string | ||
['ws-connect']?: string; | ||
/** | ||
* Sends a message to the nearest websocket based on the trigger value for the element. | ||
* Sends a message to the nearest websocket based on the trigger value for the | ||
* element. | ||
* | ||
* @see https://htmx.org/extensions/web-sockets/ | ||
*/ | ||
['ws-send']?: boolean | ||
['ws-send']?: boolean; | ||
/** | ||
* Apply class transitions on this element. | ||
* | ||
* @see https://htmx.org/extensions/class-tools/ | ||
*/ | ||
['classes']?: `add ` | `remove ` | `toggle ` | AnyStr | ||
['classes']?: `add ` | `remove ` | `toggle ` | AnyStr; | ||
/** | ||
* The element or elements to disable during requests. | ||
* Accepts CSS selectors. | ||
* The element or elements to disable during requests. Accepts CSS selectors. | ||
* | ||
* @see https://htmx.org/extensions/disable-element/ | ||
*/ | ||
['hx-disable-element']?: 'self' | AnyStr | ||
['hx-disable-element']?: 'self' | AnyStr; | ||
/** | ||
* The strategy for merging new head content. | ||
* | ||
* @see https://htmx.org/extensions/head-support/ | ||
*/ | ||
['hx-head']?: 'merge' | 'append' | 're-eval' | ||
['hx-head']?: 'merge' | 'append' | 're-eval'; | ||
} | ||
} |
170
index.d.ts
/// <reference path="./jsx.d.ts" /> | ||
type Html = typeof Html; | ||
/** | ||
@@ -8,3 +10,2 @@ * Fast and type safe HTML templates using JSX syntax. | ||
* @license Apache License Version 2.0 | ||
* | ||
* @link https://github.com/kitajs/html | ||
@@ -16,26 +17,23 @@ * @link https://www.npmjs.com/package/@kitajs/html | ||
/** | ||
* A const used to represent a html fragment. | ||
* | ||
*/ | ||
export const Fragment: unique symbol | ||
/** | ||
* Returns true if the character at the given index is an uppercase character. | ||
* | ||
* @param {string} input the string to check. | ||
* @param {number} index the index of the character to check. | ||
* @returns {boolean} if the character at the given index is an uppercase character. | ||
* @param {string} input The string to check. | ||
* @param {number} index The index of the character to check. | ||
* @returns {boolean} If the character at the given index is an uppercase character. | ||
* @this {void} | ||
*/ | ||
export function isUpper(this: void, input: string, index: number): boolean | ||
export function isUpper(this: void, input: string, index: number): boolean; | ||
/** | ||
* Escapes a string for safe use as HTML text content. If the value is not a string, | ||
* it is coerced to one with its own `toString()` method. | ||
* Escapes a string for safe use as HTML text content. If the value is not a string, it | ||
* is coerced to one with its own `toString()` method. | ||
* | ||
* @param {unknown} value the value to escape. | ||
* @returns {string} the escaped string. | ||
* If the {@linkcode Bun} runtime is available, this function will be swapped out to | ||
* {@linkcode Bun.escapeHTML}. | ||
* | ||
* @param {unknown} value The value to escape. | ||
* @returns {string} The escaped string. | ||
* @this {void} | ||
*/ | ||
export function escapeHtml(this: void, value: any): string | ||
export function escapeHtml(this: void, value: any): string; | ||
@@ -45,7 +43,7 @@ /** | ||
* | ||
* @param {string} tag the name of the element to check. | ||
* @returns {boolean} if the element is a html void element. | ||
* @param {string} tag The name of the element to check. | ||
* @returns {boolean} If the element is a html void element. | ||
* @this {void} | ||
*/ | ||
export function isVoidElement(this: void, tag: string): boolean | ||
export function isVoidElement(this: void, tag: string): boolean; | ||
@@ -55,7 +53,8 @@ /** | ||
* | ||
* @param {object | string} style a record of literal values to use as style attributes or a string. | ||
* @returns {string} the generated html style string. | ||
* @param {object | string} style A record of literal values to use as style attributes | ||
* or a string. | ||
* @returns {string} The generated html style string. | ||
* @this {void} | ||
*/ | ||
export function styleToString(this: void, style: object | string): string | ||
export function styleToString(this: void, style: object | string): string; | ||
@@ -67,9 +66,9 @@ /** | ||
* | ||
* @example `a b="c" d="1"` | ||
* @example ;`a b="c" d="1"` | ||
* | ||
* @param {object} attributes a record of literal values to use as attributes. | ||
* @returns {string} the generated html attributes string. | ||
* @param {object} attributes A record of literal values to use as attributes. | ||
* @returns {string} The generated html attributes string. | ||
* @this {void} | ||
*/ | ||
export function attributesToString(this: void, attributes: object): string | ||
export function attributesToString(this: void, attributes: object): string; | ||
@@ -79,6 +78,6 @@ /** | ||
* | ||
* @param {string} camel the camel cased string to convert. | ||
* @param {string} camel The camel cased string to convert. | ||
* @this {void} | ||
*/ | ||
export function toKebabCase(this: void, camel: string): string | ||
export function toKebabCase(this: void, camel: string): string; | ||
@@ -88,14 +87,21 @@ /** | ||
* | ||
* @param {string | Function} name the name of the element to create or a function that creates the element. | ||
* @param {{children?: object}} [attributes] a record of literal values to use as attributes. A property named `children` will be used as the children of the element. | ||
* @param {...string} contents the inner contents of the element. | ||
* @returns {string} the generated html string. | ||
* @param {string | Function} name The name of the element to create or a function that | ||
* creates the element. | ||
* @param {{ children?: object }} [attributes] A record of literal values to use as | ||
* attributes. A property named `children` will be used as the children of the | ||
* element. | ||
* @param {...string} contents The inner contents of the element. | ||
* @returns {string} The generated html string. | ||
* @this {void} | ||
*/ | ||
export function createElement( | ||
export function createElement<C extends Children[], N extends string | Function>( | ||
this: void, | ||
name: string | Function | typeof Fragment, | ||
name: N, | ||
attributes: PropsWithChildren<any> | null, | ||
...contents: Children[] | ||
): JSX.Element | ||
...contents: C | ||
): Promise<string> extends C[number] | ||
? Promise<string> | ||
: N extends () => Promise<string> | ||
? Promise<string> | ||
: string; | ||
@@ -107,46 +113,39 @@ /** | ||
* | ||
* @param {import('.').Children[]} contents an maybe nested array of strings to concatenate. | ||
* @param {boolean} [escape=false] if we should escape the contents before concatenating them. | ||
* @returns {string} the concatenated and escaped string of contents. | ||
* @param {import('.').Children[]} contents An maybe nested array of strings to | ||
* concatenate. | ||
* @param {boolean} [escape=false] If we should escape the contents before concatenating | ||
* them. Default is `false` | ||
* @returns {string} The concatenated and escaped string of contents. | ||
* @this {void} | ||
*/ | ||
export function contentsToString( | ||
export function contentsToString<C extends Children[]>( | ||
this: void, | ||
contents: Children[], | ||
contents: C, | ||
escape?: boolean | ||
): JSX.Element | ||
): Promise<string> extends C[number] ? Promise<string> : string; | ||
/** | ||
* Compiles a **clean component** into a super fast component. This does not | ||
* support unclean components / props processing. | ||
* Compiles a **clean component** into a super fast component. This does not support | ||
* unclean components / props processing. | ||
* | ||
* A **clean component** is a component that does not process props before | ||
* applying them to the element. This means that the props are applied to the | ||
* element as is, and you need to process them before passing them to the | ||
* component. | ||
* A **clean component** is a component that does not process props before applying them | ||
* to the element. This means that the props are applied to the element as is, and you | ||
* need to process them before passing them to the component. | ||
* | ||
* @example | ||
* ```tsx | ||
* // Clean component, render as is | ||
* function Clean(props: PropsWithChildren<{ repeated: string }>) { | ||
* return <div>{props.repeated}</div> | ||
* } | ||
* @example ;```tsx // Clean component, render as is function Clean(props: | ||
* PropsWithChildren<{ repeated: string }>) { return <div>{props.repeated}</div> } | ||
* | ||
* // Calculation is done before passing to the component | ||
* html = <Clean name={'a'.repeat(5)} /> | ||
* // Calculation is done before passing to the component html = <Clean | ||
* name={'a'.repeat(5)} /> | ||
* | ||
* // Unclean component, process before render | ||
* function Unclean(props: { repeat: string; n: number }) { | ||
* return <div>{props.repeat.repeat(props.n)}</div> | ||
* } | ||
* // Unclean component, process before render function Unclean(props: { repeat: string; | ||
* n: number }) { return <div>{props.repeat.repeat(props.n)}</div> } | ||
* | ||
* // Calculation is done inside the component, thus cannot be used with .compile() | ||
* html = <Unclean repeat="a" n={5} /> | ||
* ``` | ||
* // Calculation is done inside the component, thus cannot be used with .compile() html | ||
* = <Unclean repeat="a" n={5} /> | ||
* | ||
* @param {Function} htmlComponent the *clean* component to compile. | ||
* @param {boolean} [strict=true] if we should throw an error when a property is not found. | ||
* @param {string | undefined} [separator] the string used to interpolate and separate parameters | ||
* @returns {Function} the compiled template function | ||
* @this {void} | ||
* @param {Function} htmlComponent The _clean_ component to compile. @param {boolean} | ||
* [strict=true] If we should throw an error when a property is not found. Default is | ||
* `true` @param {string | undefined} [separator] The string used to interpolate and | ||
* separate parameters @returns {Function} The compiled template function @this {void} | ||
*/ | ||
@@ -160,8 +159,17 @@ export function compile< | ||
separator?: string | ||
): Component<P> | ||
): Component<P>; | ||
/** Here for interop with `preact` and many build systems. */ | ||
export const h: typeof createElement; | ||
/** | ||
* Here for interop with `preact` and many build systems. | ||
* A JSX Fragment is used to return multiple elements from a component. | ||
* | ||
* @example ;```tsx // renders <div>1</div> and <div>2</div> without needing a wrapper | ||
* element const html = <><div>1</div><div>2</div></> | ||
* | ||
* // Html.Fragment is the same as <>...</> const html = | ||
* <Html.Fragment><div>1</div><div>2</div></Html.Fragment> | ||
*/ | ||
export const h: typeof createElement | ||
export function Fragment(props: PropsWithChildren): JSX.Element; | ||
@@ -174,5 +182,6 @@ export type Children = | ||
| undefined | ||
| Children[] | ||
| Promise<Children> | ||
| Children[]; | ||
export type PropsWithChildren<T = {}> = { children?: Children } & T | ||
export type PropsWithChildren<T = {}> = { children?: Children } & T; | ||
@@ -182,5 +191,16 @@ export type Component<T = {}> = ( | ||
props: PropsWithChildren<T> | ||
) => JSX.Element | ||
) => JSX.Element; | ||
/** | ||
* Fast and type safe HTML templates using JSX syntax. | ||
* | ||
* @module html | ||
* @license Apache License Version 2.0 | ||
* @link https://github.com/kitajs/html | ||
* @link https://www.npmjs.com/package/@kitajs/html | ||
* @link https://kitajs.github.io/html/ | ||
*/ | ||
export const Html: Html; | ||
} | ||
export = Html | ||
export = Html; |
470
index.js
/// <reference path="./jsx.d.ts" /> | ||
const ESCAPED_REGEX = /[<"'&]/ | ||
const CAMEL_REGEX = /[a-z][A-Z]/ | ||
const ESCAPED_REGEX = /[<"'&]/; | ||
const CAMEL_REGEX = /[a-z][A-Z]/; | ||
/** | ||
* @type {typeof import('.').Fragment} | ||
*/ | ||
const Fragment = Symbol.for('kHtmlFragment') | ||
/** | ||
* @type {import('.').isUpper} | ||
*/ | ||
function isUpper (input, index) { | ||
const code = input.charCodeAt(index) | ||
return code >= 65 /* A */ && code <= 90 /* Z */ | ||
/** @type {import('.').isUpper} */ | ||
function isUpper(input, index) { | ||
const code = input.charCodeAt(index); | ||
return code >= 65 /* A */ && code <= 90; /* Z */ | ||
} | ||
/** | ||
* @type {import('.').toKebabCase} | ||
*/ | ||
function toKebabCase (camel) { | ||
/** @type {import('.').toKebabCase} */ | ||
function toKebabCase(camel) { | ||
// This is a optimization to avoid the whole conversion process when the | ||
// string does not contain any uppercase characters. | ||
if (!CAMEL_REGEX.test(camel)) { | ||
return camel | ||
return camel; | ||
} | ||
const length = camel.length | ||
const length = camel.length; | ||
let start = 0 | ||
let end = 0 | ||
let kebab = '' | ||
let prev = true | ||
let curr = isUpper(camel, 0) | ||
let next | ||
let start = 0; | ||
let end = 0; | ||
let kebab = ''; | ||
let prev = true; | ||
let curr = isUpper(camel, 0); | ||
let next; | ||
for (; end < length; end++) { | ||
next = isUpper(camel, end + 1) | ||
next = isUpper(camel, end + 1); | ||
@@ -44,22 +35,20 @@ // detects the start of a new camel case word and avoid lowercasing abbreviations. | ||
// @ts-expect-error - this indexing is safe. | ||
kebab += camel.slice(start, end) + '-' + camel[end].toLowerCase() | ||
start = end + 1 | ||
kebab += camel.slice(start, end) + '-' + camel[end].toLowerCase(); | ||
start = end + 1; | ||
} | ||
prev = curr | ||
curr = next | ||
prev = curr; | ||
curr = next; | ||
} | ||
// Appends the remaining string. | ||
kebab += camel.slice(start, end) | ||
kebab += camel.slice(start, end); | ||
return kebab | ||
return kebab; | ||
} | ||
/** | ||
* @type {import('.').escapeHtml} | ||
*/ | ||
function escapeHtml (value) { | ||
/** @type {import('.').escapeHtml} */ | ||
let escapeHtml = function (value) { | ||
if (typeof value !== 'string') { | ||
value = value.toString() | ||
value = value.toString(); | ||
} | ||
@@ -70,11 +59,12 @@ | ||
if (!ESCAPED_REGEX.test(value)) { | ||
return value | ||
return value; | ||
} | ||
const length = value.length | ||
let escaped = '' | ||
const length = value.length; | ||
let escaped = ''; | ||
let start = 0 | ||
let end = 0 | ||
let start = 0; | ||
let end = 0; | ||
// Escapes double quotes to be used inside attributes | ||
// Faster than using regex | ||
@@ -86,19 +76,19 @@ // https://jsperf.app/kakihu | ||
case '&': | ||
escaped += value.slice(start, end) + '&' | ||
start = end + 1 | ||
continue | ||
escaped += value.slice(start, end) + '&'; | ||
start = end + 1; | ||
continue; | ||
// We don't need to escape > because it is only used to close tags. | ||
// https://stackoverflow.com/a/9189067 | ||
case '<': | ||
escaped += value.slice(start, end) + '<' | ||
start = end + 1 | ||
continue | ||
escaped += value.slice(start, end) + '<'; | ||
start = end + 1; | ||
continue; | ||
case '"': | ||
escaped += value.slice(start, end) + '"' | ||
start = end + 1 | ||
continue | ||
escaped += value.slice(start, end) + '"'; | ||
start = end + 1; | ||
continue; | ||
case "'": | ||
escaped += value.slice(start, end) + ''' | ||
start = end + 1 | ||
continue | ||
escaped += value.slice(start, end) + '''; | ||
start = end + 1; | ||
continue; | ||
} | ||
@@ -108,11 +98,13 @@ } | ||
// Appends the remaining string. | ||
escaped += value.slice(start, end) | ||
escaped += value.slice(start, end); | ||
return escaped | ||
} | ||
return escaped; | ||
}; | ||
/** | ||
* @type {import('.').isVoidElement} | ||
*/ | ||
function isVoidElement (tag) { | ||
/* c8 ignore next 2 */ | ||
// @ts-ignore - bun runtime have its own escapeHTML function. | ||
if (typeof Bun !== 'undefined') escapeHtml = Bun.escapeHTML; | ||
/** @type {import('.').isVoidElement} */ | ||
function isVoidElement(tag) { | ||
// Ordered by most common to least common. | ||
@@ -136,13 +128,11 @@ return ( | ||
tag === 'wbr' | ||
) | ||
); | ||
} | ||
/** | ||
* @type {import('.').styleToString} | ||
*/ | ||
function styleToString (style) { | ||
/** @type {import('.').styleToString} */ | ||
function styleToString(style) { | ||
// Faster escaping process that only looks for the " character. | ||
// As we use the " character to wrap the style string, we need to escape it. | ||
if (typeof style === 'string') { | ||
let end = style.indexOf('"') | ||
let end = style.indexOf('"'); | ||
@@ -152,10 +142,11 @@ // This is a optimization to avoid having to look twice for the " character. | ||
if (end === -1) { | ||
return style | ||
return style; | ||
} | ||
const length = style.length | ||
const length = style.length; | ||
let escaped = '' | ||
let start = 0 | ||
let escaped = ''; | ||
let start = 0; | ||
// Escapes double quotes to be used inside attributes | ||
// Faster than using regex | ||
@@ -165,4 +156,4 @@ // https://jsperf.app/kakihu | ||
if (style[end] === '"') { | ||
escaped += style.slice(start, end) + '"' | ||
start = end + 1 | ||
escaped += style.slice(start, end) + '"'; | ||
start = end + 1; | ||
} | ||
@@ -172,34 +163,33 @@ } | ||
// Appends the remaining string. | ||
escaped += style.slice(start, end) | ||
escaped += style.slice(start, end); | ||
return escaped | ||
return escaped; | ||
} | ||
const keys = Object.keys(style) | ||
const length = keys.length | ||
const keys = Object.keys(style); | ||
const length = keys.length; | ||
let key | ||
let value | ||
let index = 0 | ||
let result = '' | ||
let key, value, end, start; | ||
let index = 0; | ||
let result = ''; | ||
for (; index < length; index++) { | ||
key = keys[index] | ||
key = keys[index]; | ||
// @ts-expect-error - this indexing is safe. | ||
value = style[key] | ||
value = style[key]; | ||
if (value === null || value === undefined) { | ||
continue | ||
continue; | ||
} | ||
// @ts-expect-error - this indexing is safe. | ||
result += toKebabCase(key) + ':' | ||
result += toKebabCase(key) + ':'; | ||
// Only needs escaping when the value is a string. | ||
if (typeof value !== 'string') { | ||
result += value.toString() + ';' | ||
continue | ||
result += value.toString() + ';'; | ||
continue; | ||
} | ||
let end = value.indexOf('"') | ||
end = value.indexOf('"'); | ||
@@ -209,9 +199,10 @@ // This is a optimization to avoid having to look twice for the " character. | ||
if (end === -1) { | ||
result += value + ';' | ||
continue | ||
result += value + ';'; | ||
continue; | ||
} | ||
const length = value.length | ||
let start = 0 | ||
const length = value.length; | ||
start = 0; | ||
// Escapes double quotes to be used inside attributes | ||
// Faster than using regex | ||
@@ -221,4 +212,4 @@ // https://jsperf.app/kakihu | ||
if (value[end] === '"') { | ||
result += value.slice(start, end) + '"' | ||
start = end + 1 | ||
result += value.slice(start, end) + '"'; | ||
start = end + 1; | ||
} | ||
@@ -228,33 +219,27 @@ } | ||
// Appends the remaining string. | ||
result += value.slice(start, end) + ';' | ||
result += value.slice(start, end) + ';'; | ||
} | ||
return result | ||
return result; | ||
} | ||
/** | ||
* @type {import('.').attributesToString} | ||
*/ | ||
function attributesToString (attributes) { | ||
if (!attributes) { | ||
return '' | ||
} | ||
/** @type {import('.').attributesToString} */ | ||
function attributesToString(attributes) { | ||
const keys = Object.keys(attributes); | ||
const length = keys.length; | ||
const keys = Object.keys(attributes) | ||
const length = keys.length | ||
let key, value, type, end, start; | ||
let result = ''; | ||
let index = 0; | ||
let key, value, type | ||
let result = '' | ||
let index = 0 | ||
for (; index < length; index++) { | ||
key = keys[index] | ||
key = keys[index]; | ||
// Skips all @kitajs/html specific attributes. | ||
if (key === 'children' || key === 'safe') { | ||
continue | ||
continue; | ||
} | ||
// @ts-expect-error - this indexing is safe. | ||
value = attributes[key] | ||
value = attributes[key]; | ||
@@ -265,14 +250,14 @@ // React className compatibility. | ||
if (attributes.class !== undefined) { | ||
continue | ||
continue; | ||
} | ||
key = 'class' | ||
key = 'class'; | ||
} | ||
if (key === 'style') { | ||
result += ' style="' + styleToString(value) + '"' | ||
continue | ||
result += ' style="' + styleToString(value) + '"'; | ||
continue; | ||
} | ||
type = typeof value | ||
type = typeof value; | ||
@@ -282,13 +267,13 @@ if (type === 'boolean') { | ||
if (value) { | ||
result += ' ' + key | ||
result += ' ' + key; | ||
} | ||
continue | ||
continue; | ||
} | ||
if (value === null || value === undefined) { | ||
continue | ||
continue; | ||
} | ||
result += ' ' + key | ||
result += ' ' + key; | ||
@@ -298,9 +283,9 @@ if (type !== 'string') { | ||
if (type !== 'object') { | ||
result += '="' + value.toString() + '"' | ||
continue | ||
result += '="' + value.toString() + '"'; | ||
continue; | ||
// Dates are always safe | ||
} else if (value instanceof Date) { | ||
result += '="' + value.toISOString() + '"' | ||
continue | ||
result += '="' + value.toISOString() + '"'; | ||
continue; | ||
} | ||
@@ -310,6 +295,6 @@ | ||
// Which results in a non escaped string. | ||
value = value.toString() | ||
value = value.toString(); | ||
} | ||
let end = value.indexOf('"') | ||
end = value.indexOf('"'); | ||
@@ -319,11 +304,12 @@ // This is a optimization to avoid having to look twice for the " character. | ||
if (end === -1) { | ||
result += '="' + value + '"' | ||
continue | ||
result += '="' + value + '"'; | ||
continue; | ||
} | ||
result += '="' | ||
result += '="'; | ||
const length = value.length | ||
let start = 0 | ||
const length = value.length; | ||
start = 0; | ||
// Escapes double quotes to be used inside attributes | ||
// Faster than using regex | ||
@@ -333,4 +319,4 @@ // https://jsperf.app/kakihu | ||
if (value[end] === '"') { | ||
result += value.slice(start, end) + '"' | ||
start = end + 1 | ||
result += value.slice(start, end) + '"'; | ||
start = end + 1; | ||
} | ||
@@ -340,6 +326,6 @@ } | ||
// Appends the remaining string. | ||
result += value.slice(start, end) + '"' | ||
result += value.slice(start, end) + '"'; | ||
} | ||
return result | ||
return result; | ||
} | ||
@@ -349,32 +335,50 @@ | ||
* @type {import('.').contentsToString} | ||
* @returns {any} | ||
*/ | ||
function contentsToString (contents, escape) { | ||
const length = contents.length | ||
function contentsToString(contents, escape) { | ||
const length = contents.length; | ||
if (length === 0) { | ||
return '' | ||
} | ||
let result = ''; | ||
let content; | ||
let index = 0; | ||
let result = '' | ||
let content | ||
let index = 0 | ||
for (; index < length; index++) { | ||
content = contents[index] | ||
content = contents[index]; | ||
// Ignores non 0 falsy values | ||
if (!content && content !== 0) { | ||
continue | ||
if (typeof content !== 'string') { | ||
// Ignores non 0 falsy values | ||
if (!content && content !== 0) { | ||
continue; | ||
} | ||
if (Array.isArray(content)) { | ||
content = contentsToString(content); | ||
} | ||
// @ts-expect-error - Also accepts thenable objects, not only promises | ||
// https://jsperf.app/zipuvi | ||
if (content.then) { | ||
// @ts-expect-error - this is a promise | ||
return content.then(function resolveAsyncContent(resolved) { | ||
return contentsToString( | ||
[result, resolved] | ||
// if we also pass escape here, it would double escape this result | ||
// with the above call. | ||
.concat(contents.slice(index + 1)), | ||
escape | ||
); | ||
}); | ||
} | ||
} | ||
if (Array.isArray(content)) { | ||
result += contentsToString(content, escape) | ||
} else if (escape === true) { | ||
result += escapeHtml(content) | ||
} else { | ||
result += content | ||
} | ||
result += content; | ||
} | ||
return result | ||
// escapeHtml is faster with longer strings, that's | ||
// why we escape the entire result once | ||
if (escape === true) { | ||
return escapeHtml(result); | ||
} | ||
return result; | ||
} | ||
@@ -384,10 +388,11 @@ | ||
* Just to stop TS from complaining about the type. | ||
* @param {any} name | ||
* | ||
* @type {import('.').createElement} | ||
* @param {any} name | ||
* @returns {any} | ||
*/ | ||
function createElement (name, attrs, ...children) { | ||
function createElement(name, attrs, ...children) { | ||
// Adds the children to the attributes if it is not present. | ||
if (attrs === null) { | ||
attrs = { children } | ||
attrs = { children }; | ||
} | ||
@@ -401,49 +406,51 @@ | ||
if (children.length > 1) { | ||
attrs.children = children | ||
attrs.children = children; | ||
} else { | ||
attrs.children = children[0] | ||
attrs.children = children[0]; | ||
} | ||
} | ||
return name(attrs) | ||
return name(attrs); | ||
} | ||
if (name === Fragment) { | ||
return contentsToString(children) | ||
} | ||
// Switches the tag name when this custom `tag` is present. | ||
if (name === 'tag') { | ||
name = String(attrs.of) | ||
delete attrs.of | ||
name = String(attrs.of); | ||
delete attrs.of; | ||
} | ||
if (children.length === 0 && isVoidElement(name)) { | ||
return '<' + name + attributesToString(attrs) + '/>' | ||
return '<' + name + attributesToString(attrs) + '/>'; | ||
} | ||
return ( | ||
'<' + | ||
name + | ||
attributesToString(attrs) + | ||
'>' + | ||
contentsToString(children, attrs.safe) + | ||
'</' + | ||
name + | ||
'>' | ||
) | ||
let contents = contentsToString(children, attrs.safe); | ||
// Faster than checking if `children instanceof Promise` | ||
// https://jsperf.app/zipuvi | ||
if (typeof contents === 'string') { | ||
return '<' + name + attributesToString(attrs) + '>' + contents + '</' + name + '>'; | ||
} | ||
return contents.then(function asyncChildren(child) { | ||
return '<' + name + attributesToString(attrs) + '>' + child + '</' + name + '>'; | ||
}); | ||
} | ||
/** @type {import('.').Fragment} */ | ||
function Fragment(props) { | ||
return Html.contentsToString([props.children]); | ||
} | ||
/** | ||
* Just to stop TS from complaining about the type. | ||
* @returns {Function} | ||
* | ||
* @type {import('.').compile} | ||
* @returns {Function} | ||
*/ | ||
function compile (htmlFn, strict = true, separator = '/*\x00*/') { | ||
function compile(htmlFn, strict = true, separator = '/*\x00*/') { | ||
if (typeof htmlFn !== 'function') { | ||
throw new Error('The first argument must be a function.') | ||
throw new Error('The first argument must be a function.'); | ||
} | ||
const properties = new Set() | ||
const properties = new Set(); | ||
@@ -455,12 +462,12 @@ const html = htmlFn( | ||
{ | ||
get (_, name) { | ||
get(_, name) { | ||
// Adds the property to the set of known properties. | ||
properties.add(name) | ||
properties.add(name); | ||
const isChildren = name === 'children' | ||
let access = `args[${separator}\`${name.toString()}\`${separator}]` | ||
const isChildren = name === 'children'; | ||
let access = `args[${separator}\`${name.toString()}\`${separator}]`; | ||
// Adds support to render multiple children | ||
if (isChildren) { | ||
access = `Array.isArray(${access}) ? ${access}.join(${separator}\`\`${separator}) : ${access}` | ||
access = `Array.isArray(${access}) ? ${access}.join(${separator}\`\`${separator}) : ${access}`; | ||
} | ||
@@ -473,15 +480,19 @@ | ||
: `${separator}\`\`${separator}` | ||
}) + ${separator}\`` | ||
}) + ${separator}\``; | ||
} | ||
} | ||
) | ||
) | ||
); | ||
const sepLength = separator.length | ||
const length = html.length | ||
if (typeof html !== 'string') { | ||
throw new Error('You cannot use compile() with async components.'); | ||
} | ||
const sepLength = separator.length; | ||
const length = html.length; | ||
// Adds the throwPropertyNotFound function if strict | ||
let body = '' | ||
let nextStart = 0 | ||
let index = 0 | ||
let body = ''; | ||
let nextStart = 0; | ||
let index = 0; | ||
@@ -497,21 +508,12 @@ // Escapes every ` without separator | ||
) { | ||
body += html.slice(nextStart, index) + '\\`' | ||
nextStart = index + 1 | ||
continue | ||
body += html.slice(nextStart, index) + '\\`'; | ||
nextStart = index + 1; | ||
continue; | ||
} | ||
// Escapes the backslash character because it will be used to escape the | ||
// backtick character. | ||
if (html[index] === '\\') { | ||
body += html.slice(nextStart, index) + '\\\\' | ||
nextStart = index + 1 | ||
continue | ||
} | ||
} | ||
// Adds the remaining string | ||
body += html.slice(nextStart) | ||
body += html.slice(nextStart); | ||
if (strict) { | ||
// eslint-disable-next-line no-new-func | ||
return Function( | ||
@@ -525,28 +527,40 @@ 'args', | ||
`return \`${body}\`` | ||
) | ||
); | ||
} | ||
// eslint-disable-next-line no-new-func | ||
return Function( | ||
'args', | ||
// Adds a empty args object when it is not present | ||
'if (args === undefined) { args = Object.create(null) };\n' + | ||
`return \`${body}\`` | ||
) | ||
'if (args === undefined) { args = Object.create(null) };\n' + `return \`${body}\`` | ||
); | ||
} | ||
module.exports.escapeHtml = escapeHtml | ||
module.exports.isVoidElement = isVoidElement | ||
module.exports.attributesToString = attributesToString | ||
module.exports.toKebabCase = toKebabCase | ||
module.exports.isUpper = isUpper | ||
module.exports.styleToString = styleToString | ||
module.exports.createElement = createElement | ||
module.exports.h = createElement | ||
module.exports.contentsToString = contentsToString | ||
module.exports.compile = compile | ||
module.exports.Fragment = Fragment | ||
const Html = { | ||
escapeHtml, | ||
isVoidElement, | ||
attributesToString, | ||
toKebabCase, | ||
isUpper, | ||
styleToString, | ||
createElement, | ||
h: createElement, | ||
contentsToString, | ||
compile, | ||
Fragment | ||
}; | ||
// esModule interop | ||
Object.defineProperty(exports, '__esModule', { value: true }) | ||
module.exports.default = Object.assign({}, module.exports) | ||
/** | ||
* These export configurations enable JS and TS developers to consumer @kitajs/html in | ||
* whatever way best suits their needs. Some examples of supported import syntax | ||
* includes: | ||
* | ||
* - `const Html = require('@kitajs/html')` | ||
* - `const { Html } = require('@kitajs/html')` | ||
* - `import * as Fastify from '@kitajs/html'` | ||
* - `import { Html, TSC_definition } from '@kitajs/html'` | ||
* - `import Html from '@kitajs/html'` | ||
* - `import Html, { TSC_definition } from '@kitajs/html'` | ||
*/ | ||
module.exports = Html; | ||
module.exports.Html = Html; | ||
module.exports.default = Html; |
960
jsx.d.ts
@@ -7,8 +7,17 @@ // This file is a result from many sources, including: RFCs, typescript dom lib, w3schools, and others. | ||
declare namespace JSX { | ||
type Element = string | ||
/** | ||
* A {@linkcode JSX.Element} will always be a string, unless one of its children is a | ||
* promise, in which case all of its subsequent children will also be promises. | ||
* | ||
* Direct calls of `Html.createElement` uses correct return type based on its children. | ||
* However, when using JSX syntax, typescript does not support this yet. | ||
* | ||
* @see https://github.com/microsoft/TypeScript/issues/14729 | ||
*/ | ||
type Element = string | Promise<string>; | ||
/** | ||
* The index signature was removed to enable closed typing for style | ||
* using CSSType. You're able to use type assertion or module augmentation | ||
* to add properties or an index signature of your own. | ||
* The index signature was removed to enable closed typing for style using CSSType. | ||
* You're able to use type assertion or module augmentation to add properties or an | ||
* index signature of your own. | ||
* | ||
@@ -18,45 +27,36 @@ * For examples and more information, visit: | ||
*/ | ||
type CSSProperties = import('csstype').Properties<string | number | boolean> | ||
type CSSProperties = import('csstype').Properties<string | number | boolean>; | ||
interface HtmlTag extends ElementChildrenAttribute, IntrinsicAttributes { | ||
accesskey?: undefined | string | ||
class?: undefined | string | ||
contenteditable?: undefined | string | ||
dir?: undefined | string | ||
hidden?: undefined | string | boolean | ||
id?: undefined | string | ||
role?: undefined | string | ||
lang?: undefined | string | ||
draggable?: undefined | string | boolean | ||
spellcheck?: undefined | string | boolean | ||
tabindex?: undefined | number | string | ||
title?: undefined | string | ||
translate?: undefined | string | boolean | ||
accesskey?: undefined | string; | ||
class?: undefined | string; | ||
contenteditable?: undefined | string; | ||
dir?: undefined | string; | ||
hidden?: undefined | string | boolean; | ||
id?: undefined | string; | ||
role?: undefined | string; | ||
lang?: undefined | string; | ||
draggable?: undefined | string | boolean; | ||
spellcheck?: undefined | string | boolean; | ||
tabindex?: undefined | number | string; | ||
title?: undefined | string; | ||
translate?: undefined | string | boolean; | ||
/** | ||
* A css style attribute which also supports a `csstype` object. | ||
*/ | ||
style?: undefined | string | CSSProperties | ||
/** A css style attribute which also supports a `csstype` object. */ | ||
style?: undefined | string | CSSProperties; | ||
/** | ||
* Tells if any inner html should be escaped. | ||
* When set to true, all inner content (html or not) of this tag will be escaped when | ||
* evaluated. | ||
* | ||
* **Warning: This also escapes inner jsx tags. You should only use this in the inner tag.** | ||
* **Warning: This will escape even inner jsx tags. You should only use this in the | ||
* most inner tag of the html tree.** | ||
* | ||
* @example | ||
* ```tsx | ||
* <div>{'<script />'}</div> | ||
* '<div><script /></div>' | ||
* @example ;```tsx <div>{'<script />'}</div> '<div><script /></div>' <div | ||
* safe>{'<script />'}</div> '<div><script /></div>' <div><div>{'<script | ||
* />'}</div></div> '<div><div><script /></div></div>' | ||
* | ||
* <div safe>{'<script />'}</div> | ||
* '<div><script /></div>' | ||
* | ||
* <div><div>{'<script />'}</div></div> | ||
* // Escapes even inner jsx tags <div safe><div>{'<script />'}</div></div> | ||
* '<div><div><script /></div></div>' | ||
* | ||
* // Escapes even inner jsx tags | ||
* <div safe><div>{'<script />'}</div></div> | ||
* '<div><div><script /></div></div>' | ||
* ``` | ||
* | ||
* @default false | ||
@@ -66,3 +66,3 @@ * | ||
*/ | ||
safe?: undefined | boolean | ||
safe?: undefined | boolean; | ||
@@ -72,49 +72,49 @@ /** | ||
* | ||
* @deprecated please use `class`. | ||
* @deprecated Please use `class`. | ||
*/ | ||
className?: undefined | string | ||
className?: undefined | string; | ||
} | ||
interface HtmlAnchorTag extends HtmlTag { | ||
href?: undefined | string | ||
hreflang?: undefined | string | ||
target?: undefined | string | ||
download?: undefined | string | ||
referrerpolicy?: undefined | string | ||
ping?: undefined | string | ||
rel?: undefined | string | ||
media?: undefined | string | ||
type?: undefined | string | ||
href?: undefined | string; | ||
hreflang?: undefined | string; | ||
target?: undefined | string; | ||
download?: undefined | string; | ||
referrerpolicy?: undefined | string; | ||
ping?: undefined | string; | ||
rel?: undefined | string; | ||
media?: undefined | string; | ||
type?: undefined | string; | ||
} | ||
interface HtmlAreaTag extends HtmlTag { | ||
alt?: undefined | string | ||
coords?: undefined | string | ||
shape?: undefined | string | ||
href?: undefined | string | ||
target?: undefined | string | ||
ping?: undefined | string | ||
rel?: undefined | string | ||
media?: undefined | string | ||
hreflang?: undefined | string | ||
type?: undefined | string | ||
alt?: undefined | string; | ||
coords?: undefined | string; | ||
shape?: undefined | string; | ||
href?: undefined | string; | ||
target?: undefined | string; | ||
ping?: undefined | string; | ||
rel?: undefined | string; | ||
media?: undefined | string; | ||
hreflang?: undefined | string; | ||
type?: undefined | string; | ||
} | ||
interface HtmlAudioTag extends HtmlTag { | ||
src?: undefined | string | ||
autobuffer?: undefined | string | ||
autoplay?: undefined | string | boolean | ||
preload?: undefined | string | ||
muted?: undefined | string | boolean | ||
loop?: undefined | string | boolean | ||
controls?: undefined | string | ||
src?: undefined | string; | ||
autobuffer?: undefined | string; | ||
autoplay?: undefined | string | boolean; | ||
preload?: undefined | string; | ||
muted?: undefined | string | boolean; | ||
loop?: undefined | string | boolean; | ||
controls?: undefined | string; | ||
} | ||
interface BaseTag extends HtmlTag { | ||
href?: undefined | string | ||
target?: undefined | string | ||
href?: undefined | string; | ||
target?: undefined | string; | ||
} | ||
interface HtmlQuoteTag extends HtmlTag { | ||
cite?: undefined | string | ||
cite?: undefined | string; | ||
} | ||
@@ -125,13 +125,13 @@ | ||
interface HtmlButtonTag extends HtmlTag { | ||
action?: undefined | string | ||
autofocus?: undefined | string | ||
disabled?: undefined | boolean | ||
enctype?: undefined | string | ||
form?: undefined | string | ||
method?: undefined | string | ||
name?: undefined | string | ||
novalidate?: undefined | string | boolean | ||
target?: undefined | string | ||
type?: undefined | string | ||
value?: undefined | string | ||
action?: undefined | string; | ||
autofocus?: undefined | string; | ||
disabled?: undefined | boolean; | ||
enctype?: undefined | string; | ||
form?: undefined | string; | ||
method?: undefined | string; | ||
name?: undefined | string; | ||
novalidate?: undefined | string | boolean; | ||
target?: undefined | string; | ||
type?: undefined | string; | ||
value?: undefined | string; | ||
} | ||
@@ -142,8 +142,8 @@ | ||
interface HtmlCanvasTag extends HtmlTag { | ||
width?: undefined | string | ||
height?: undefined | string | ||
width?: undefined | string; | ||
height?: undefined | string; | ||
} | ||
interface HtmlTableColTag extends HtmlTag { | ||
span?: undefined | string | ||
span?: undefined | string; | ||
} | ||
@@ -156,190 +156,190 @@ | ||
interface DataTag extends HtmlTag { | ||
value?: undefined | string | ||
value?: undefined | string; | ||
} | ||
interface HtmlEmbedTag extends HtmlTag, Record<string, any> { | ||
src?: undefined | string | ||
type?: undefined | string | ||
width?: undefined | string | ||
height?: undefined | string | ||
src?: undefined | string; | ||
type?: undefined | string; | ||
width?: undefined | string; | ||
height?: undefined | string; | ||
} | ||
interface HtmlFieldSetTag extends HtmlTag { | ||
disabled?: undefined | boolean | ||
form?: undefined | string | ||
name?: undefined | string | ||
disabled?: undefined | boolean; | ||
form?: undefined | string; | ||
name?: undefined | string; | ||
} | ||
interface HtmlFormTag extends HtmlTag { | ||
['accept-charset']?: undefined | string | ||
action?: undefined | string | ||
autocomplete?: undefined | string | ||
enctype?: undefined | string | ||
method?: undefined | string | ||
name?: undefined | string | ||
novalidate?: undefined | string | boolean | ||
target?: undefined | string | ||
['accept-charset']?: undefined | string; | ||
action?: undefined | string; | ||
autocomplete?: undefined | string; | ||
enctype?: undefined | string; | ||
method?: undefined | string; | ||
name?: undefined | string; | ||
novalidate?: undefined | string | boolean; | ||
target?: undefined | string; | ||
} | ||
interface HtmlHtmlTag extends HtmlTag { | ||
manifest?: undefined | string | ||
manifest?: undefined | string; | ||
} | ||
interface HtmlIFrameTag extends HtmlTag { | ||
src?: undefined | string | ||
srcdoc?: undefined | string | ||
name?: undefined | string | ||
sandbox?: undefined | string | ||
seamless?: undefined | string | ||
width?: undefined | string | ||
height?: undefined | string | ||
src?: undefined | string; | ||
srcdoc?: undefined | string; | ||
name?: undefined | string; | ||
sandbox?: undefined | string; | ||
seamless?: undefined | string; | ||
width?: undefined | string; | ||
height?: undefined | string; | ||
} | ||
interface HtmlImageTag extends HtmlTag { | ||
alt?: undefined | string | ||
src?: undefined | string | ||
crossorigin?: undefined | string | ||
usemap?: undefined | string | ||
ismap?: undefined | string | ||
width?: undefined | string | ||
height?: undefined | string | ||
alt?: undefined | string; | ||
src?: undefined | string; | ||
crossorigin?: undefined | string; | ||
usemap?: undefined | string; | ||
ismap?: undefined | string; | ||
width?: undefined | number | string; | ||
height?: undefined | number | string; | ||
} | ||
interface HtmlInputTag extends HtmlTag { | ||
accept?: undefined | string | ||
action?: undefined | string | ||
alt?: undefined | string | ||
autocomplete?: undefined | string | ||
autofocus?: undefined | string | ||
checked?: undefined | string | boolean | ||
disabled?: undefined | boolean | ||
enctype?: undefined | string | ||
form?: undefined | string | ||
height?: undefined | string | ||
list?: undefined | string | ||
max?: undefined | string | ||
maxlength?: undefined | string | ||
method?: undefined | string | ||
min?: undefined | string | ||
multiple?: undefined | string | ||
name?: undefined | string | ||
novalidate?: undefined | string | boolean | ||
pattern?: undefined | string | ||
placeholder?: undefined | string | ||
readonly?: undefined | string | ||
required?: undefined | string | ||
size?: undefined | string | ||
src?: undefined | string | ||
step?: undefined | string | ||
target?: undefined | string | ||
type?: undefined | string | ||
value?: undefined | string | ||
width?: undefined | string | ||
accept?: undefined | string; | ||
action?: undefined | string; | ||
alt?: undefined | string; | ||
autocomplete?: undefined | string; | ||
autofocus?: undefined | string; | ||
checked?: undefined | string | boolean; | ||
disabled?: undefined | boolean; | ||
enctype?: undefined | string; | ||
form?: undefined | string; | ||
height?: undefined | string; | ||
list?: undefined | string; | ||
max?: undefined | string; | ||
maxlength?: undefined | string; | ||
method?: undefined | string; | ||
min?: undefined | string; | ||
multiple?: undefined | string; | ||
name?: undefined | string; | ||
novalidate?: undefined | string | boolean; | ||
pattern?: undefined | string; | ||
placeholder?: undefined | string; | ||
readonly?: undefined | string; | ||
required?: undefined | string; | ||
size?: undefined | string; | ||
src?: undefined | string; | ||
step?: undefined | string; | ||
target?: undefined | string; | ||
type?: undefined | string; | ||
value?: undefined | string; | ||
width?: undefined | string; | ||
} | ||
interface HtmlModTag extends HtmlTag { | ||
cite?: undefined | string | ||
datetime?: undefined | string | Date | ||
cite?: undefined | string; | ||
datetime?: undefined | string | Date; | ||
} | ||
interface KeygenTag extends HtmlTag { | ||
autofocus?: undefined | string | ||
challenge?: undefined | string | ||
disabled?: undefined | boolean | ||
form?: undefined | string | ||
keytype?: undefined | string | ||
name?: undefined | string | ||
autofocus?: undefined | string; | ||
challenge?: undefined | string; | ||
disabled?: undefined | boolean; | ||
form?: undefined | string; | ||
keytype?: undefined | string; | ||
name?: undefined | string; | ||
} | ||
interface HtmlLabelTag extends HtmlTag { | ||
form?: undefined | string | ||
for?: undefined | string | ||
form?: undefined | string; | ||
for?: undefined | string; | ||
} | ||
interface HtmlLITag extends HtmlTag { | ||
value?: undefined | string | number | ||
value?: undefined | string | number; | ||
} | ||
interface HtmlLinkTag extends HtmlTag { | ||
href?: undefined | string | ||
crossorigin?: undefined | string | ||
rel?: undefined | string | ||
media?: undefined | string | ||
hreflang?: undefined | string | ||
type?: undefined | string | ||
sizes?: undefined | string | ||
integrity?: undefined | string | ||
href?: undefined | string; | ||
crossorigin?: undefined | string; | ||
rel?: undefined | string; | ||
media?: undefined | string; | ||
hreflang?: undefined | string; | ||
type?: undefined | string; | ||
sizes?: undefined | string; | ||
integrity?: undefined | string; | ||
} | ||
interface HtmlMapTag extends HtmlTag { | ||
name?: undefined | string | ||
name?: undefined | string; | ||
} | ||
interface HtmlMetaTag extends HtmlTag { | ||
name?: undefined | string | ||
['http-equiv']?: undefined | string | ||
content?: undefined | string | ||
charset?: undefined | string | ||
name?: undefined | string; | ||
['http-equiv']?: undefined | string; | ||
content?: undefined | string; | ||
charset?: undefined | string; | ||
} | ||
interface HtmlMeterTag extends HtmlTag { | ||
value?: undefined | string | number | ||
min?: undefined | string | number | ||
max?: undefined | string | number | ||
low?: undefined | string | number | ||
high?: undefined | string | number | ||
optimum?: undefined | string | number | ||
value?: undefined | string | number; | ||
min?: undefined | string | number; | ||
max?: undefined | string | number; | ||
low?: undefined | string | number; | ||
high?: undefined | string | number; | ||
optimum?: undefined | string | number; | ||
} | ||
interface HtmlObjectTag extends HtmlTag { | ||
data?: undefined | string | ||
type?: undefined | string | ||
name?: undefined | string | ||
usemap?: undefined | string | ||
form?: undefined | string | ||
width?: undefined | string | ||
height?: undefined | string | ||
data?: undefined | string; | ||
type?: undefined | string; | ||
name?: undefined | string; | ||
usemap?: undefined | string; | ||
form?: undefined | string; | ||
width?: undefined | string; | ||
height?: undefined | string; | ||
} | ||
interface HtmlOListTag extends HtmlTag { | ||
reversed?: undefined | string | ||
start?: undefined | string | number | ||
reversed?: undefined | string; | ||
start?: undefined | string | number; | ||
} | ||
interface HtmlOptgroupTag extends HtmlTag { | ||
disabled?: undefined | boolean | ||
label?: undefined | string | ||
disabled?: undefined | boolean; | ||
label?: undefined | string; | ||
} | ||
interface HtmlOptionTag extends HtmlTag { | ||
disabled?: undefined | boolean | ||
label?: undefined | string | ||
selected?: undefined | string | ||
value?: undefined | string | ||
disabled?: undefined | boolean; | ||
label?: undefined | string; | ||
selected?: undefined | string; | ||
value?: undefined | string; | ||
} | ||
interface HtmlOutputTag extends HtmlTag { | ||
for?: undefined | string | ||
form?: undefined | string | ||
name?: undefined | string | ||
for?: undefined | string; | ||
form?: undefined | string; | ||
name?: undefined | string; | ||
} | ||
interface HtmlParamTag extends HtmlTag { | ||
name?: undefined | string | ||
value?: undefined | string | ||
name?: undefined | string; | ||
value?: undefined | string; | ||
} | ||
interface HtmlProgressTag extends HtmlTag { | ||
value?: undefined | string | number | ||
max?: undefined | string | number | ||
value?: undefined | string | number; | ||
max?: undefined | string | number; | ||
} | ||
interface HtmlCommandTag extends HtmlTag { | ||
type?: undefined | string | ||
label?: undefined | string | ||
icon?: undefined | string | ||
disabled?: undefined | boolean | ||
checked?: undefined | string | ||
radiogroup?: undefined | string | ||
default?: undefined | string | ||
type?: undefined | string; | ||
label?: undefined | string; | ||
icon?: undefined | string; | ||
disabled?: undefined | boolean; | ||
checked?: undefined | string; | ||
radiogroup?: undefined | string; | ||
default?: undefined | string; | ||
} | ||
@@ -350,46 +350,46 @@ | ||
interface HtmlBrowserButtonTag extends HtmlTag { | ||
type?: undefined | string | ||
type?: undefined | string; | ||
} | ||
interface HtmlMenuTag extends HtmlTag { | ||
type?: undefined | string | ||
label?: undefined | string | ||
type?: undefined | string; | ||
label?: undefined | string; | ||
} | ||
interface HtmlScriptTag extends HtmlTag { | ||
src?: undefined | string | ||
type?: undefined | string | ||
charset?: undefined | string | ||
async?: undefined | string | boolean | ||
defer?: undefined | string | boolean | ||
crossorigin?: undefined | string | ||
integrity?: undefined | string | ||
text?: undefined | string | ||
src?: undefined | string; | ||
type?: undefined | string; | ||
charset?: undefined | string; | ||
async?: undefined | string | boolean; | ||
defer?: undefined | string | boolean; | ||
crossorigin?: undefined | string; | ||
integrity?: undefined | string; | ||
text?: undefined | string; | ||
} | ||
interface HtmlDetailsTag extends HtmlTag { | ||
open?: undefined | string | ||
open?: undefined | string; | ||
} | ||
interface HtmlSelectTag extends HtmlTag { | ||
autofocus?: undefined | string | ||
disabled?: undefined | boolean | ||
form?: undefined | string | ||
multiple?: undefined | string | ||
name?: undefined | string | ||
required?: undefined | string | ||
size?: undefined | string | ||
autofocus?: undefined | string; | ||
disabled?: undefined | boolean; | ||
form?: undefined | string; | ||
multiple?: undefined | string; | ||
name?: undefined | string; | ||
required?: undefined | string; | ||
size?: undefined | string; | ||
} | ||
interface HtmlSourceTag extends HtmlTag { | ||
src?: undefined | string | ||
type?: undefined | string | ||
media?: undefined | string | ||
src?: undefined | string; | ||
type?: undefined | string; | ||
media?: undefined | string; | ||
} | ||
interface HtmlStyleTag extends HtmlTag { | ||
media?: undefined | string | ||
type?: undefined | string | ||
disabled?: undefined | boolean | ||
scoped?: undefined | string | ||
media?: undefined | string; | ||
type?: undefined | string; | ||
disabled?: undefined | boolean; | ||
scoped?: undefined | string; | ||
} | ||
@@ -400,51 +400,51 @@ | ||
interface HtmlTableDataCellTag extends HtmlTag { | ||
colspan?: undefined | string | number | ||
rowspan?: undefined | string | number | ||
headers?: undefined | string | ||
colspan?: undefined | string | number; | ||
rowspan?: undefined | string | number; | ||
headers?: undefined | string; | ||
} | ||
interface HtmlTextAreaTag extends HtmlTag { | ||
autofocus?: undefined | string | ||
cols?: undefined | string | ||
dirname?: undefined | string | ||
disabled?: undefined | boolean | ||
form?: undefined | string | ||
maxlength?: undefined | string | ||
minlength?: undefined | string | ||
name?: undefined | string | ||
placeholder?: undefined | string | ||
readonly?: undefined | string | ||
required?: undefined | string | ||
rows?: undefined | string | ||
wrap?: undefined | string | ||
autofocus?: undefined | string; | ||
cols?: undefined | string; | ||
dirname?: undefined | string; | ||
disabled?: undefined | boolean; | ||
form?: undefined | string; | ||
maxlength?: undefined | string; | ||
minlength?: undefined | string; | ||
name?: undefined | string; | ||
placeholder?: undefined | string; | ||
readonly?: undefined | string; | ||
required?: undefined | string; | ||
rows?: undefined | string; | ||
wrap?: undefined | string; | ||
} | ||
interface HtmlTableHeaderCellTag extends HtmlTag { | ||
colspan?: undefined | string | number | ||
rowspan?: undefined | string | number | ||
headers?: undefined | string | ||
scope?: undefined | string | ||
colspan?: undefined | string | number; | ||
rowspan?: undefined | string | number; | ||
headers?: undefined | string; | ||
scope?: undefined | string; | ||
} | ||
interface HtmlTimeTag extends HtmlTag { | ||
datetime?: undefined | string | Date | ||
datetime?: undefined | string | Date; | ||
} | ||
interface HtmlTrackTag extends HtmlTag { | ||
default?: undefined | string | ||
kind?: undefined | string | ||
label?: undefined | string | ||
src?: undefined | string | ||
srclang?: undefined | string | ||
default?: undefined | string; | ||
kind?: undefined | string; | ||
label?: undefined | string; | ||
src?: undefined | string; | ||
srclang?: undefined | string; | ||
} | ||
interface HtmlVideoTag extends HtmlTag { | ||
src?: undefined | string | ||
poster?: undefined | string | ||
autobuffer?: undefined | string | ||
autoplay?: undefined | string | ||
loop?: undefined | string | ||
controls?: undefined | string | ||
width?: undefined | string | ||
height?: undefined | string | ||
src?: undefined | string; | ||
poster?: undefined | string; | ||
autobuffer?: undefined | string; | ||
autoplay?: undefined | string; | ||
loop?: undefined | string; | ||
controls?: undefined | string; | ||
width?: undefined | string; | ||
height?: undefined | string; | ||
} | ||
@@ -456,60 +456,60 @@ | ||
interface HtmlUnspecifiedTag extends HtmlTag, Record<string, any> { | ||
of: string | ||
of: string; | ||
} | ||
interface HtmlBodyTag { | ||
onafterprint?: undefined | string | ||
onbeforeprint?: undefined | string | ||
onbeforeonload?: undefined | string | ||
onblur?: undefined | string | ||
onerror?: undefined | string | ||
onfocus?: undefined | string | ||
onhaschange?: undefined | string | ||
onload?: undefined | string | ||
onmessage?: undefined | string | ||
onoffline?: undefined | string | ||
ononline?: undefined | string | ||
onpagehide?: undefined | string | ||
onpageshow?: undefined | string | ||
onpopstate?: undefined | string | ||
onredo?: undefined | string | ||
onresize?: undefined | string | ||
onstorage?: undefined | string | ||
onundo?: undefined | string | ||
onunload?: undefined | string | ||
onafterprint?: undefined | string; | ||
onbeforeprint?: undefined | string; | ||
onbeforeonload?: undefined | string; | ||
onblur?: undefined | string; | ||
onerror?: undefined | string; | ||
onfocus?: undefined | string; | ||
onhaschange?: undefined | string; | ||
onload?: undefined | string; | ||
onmessage?: undefined | string; | ||
onoffline?: undefined | string; | ||
ononline?: undefined | string; | ||
onpagehide?: undefined | string; | ||
onpageshow?: undefined | string; | ||
onpopstate?: undefined | string; | ||
onredo?: undefined | string; | ||
onresize?: undefined | string; | ||
onstorage?: undefined | string; | ||
onundo?: undefined | string; | ||
onunload?: undefined | string; | ||
} | ||
interface HtmlTag { | ||
oncontextmenu?: undefined | string | ||
onkeydown?: undefined | string | ||
onkeypress?: undefined | string | ||
onkeyup?: undefined | string | ||
onclick?: undefined | string | ||
ondblclick?: undefined | string | ||
ondrag?: undefined | string | ||
ondragend?: undefined | string | ||
ondragenter?: undefined | string | ||
ondragleave?: undefined | string | ||
ondragover?: undefined | string | ||
ondragstart?: undefined | string | ||
ondrop?: undefined | string | ||
onmousedown?: undefined | string | ||
onmousemove?: undefined | string | ||
onmouseout?: undefined | string | ||
onmouseover?: undefined | string | ||
onmouseup?: undefined | string | ||
onmousewheel?: undefined | string | ||
onscroll?: undefined | string | ||
oncontextmenu?: undefined | string; | ||
onkeydown?: undefined | string; | ||
onkeypress?: undefined | string; | ||
onkeyup?: undefined | string; | ||
onclick?: undefined | string; | ||
ondblclick?: undefined | string; | ||
ondrag?: undefined | string; | ||
ondragend?: undefined | string; | ||
ondragenter?: undefined | string; | ||
ondragleave?: undefined | string; | ||
ondragover?: undefined | string; | ||
ondragstart?: undefined | string; | ||
ondrop?: undefined | string; | ||
onmousedown?: undefined | string; | ||
onmousemove?: undefined | string; | ||
onmouseout?: undefined | string; | ||
onmouseover?: undefined | string; | ||
onmouseup?: undefined | string; | ||
onmousewheel?: undefined | string; | ||
onscroll?: undefined | string; | ||
} | ||
interface FormEvents { | ||
onblur?: undefined | string | ||
onchange?: undefined | string | ||
onfocus?: undefined | string | ||
onformchange?: undefined | string | ||
onforminput?: undefined | string | ||
oninput?: undefined | string | ||
oninvalid?: undefined | string | ||
onselect?: undefined | string | ||
onsubmit?: undefined | string | ||
onblur?: undefined | string; | ||
onchange?: undefined | string; | ||
onfocus?: undefined | string; | ||
onformchange?: undefined | string; | ||
onforminput?: undefined | string; | ||
oninput?: undefined | string; | ||
oninvalid?: undefined | string; | ||
onselect?: undefined | string; | ||
onsubmit?: undefined | string; | ||
} | ||
@@ -524,25 +524,25 @@ | ||
interface MediaEvents { | ||
onabort?: undefined | string | ||
oncanplay?: undefined | string | ||
oncanplaythrough?: undefined | string | ||
ondurationchange?: undefined | string | ||
onemptied?: undefined | string | ||
onended?: undefined | string | ||
onerror?: undefined | string | ||
onloadeddata?: undefined | string | ||
onloadedmetadata?: undefined | string | ||
onloadstart?: undefined | string | ||
onpause?: undefined | string | ||
onplay?: undefined | string | ||
onplaying?: undefined | string | ||
onprogress?: undefined | string | ||
onratechange?: undefined | string | ||
onreadystatechange?: undefined | string | ||
onseeked?: undefined | string | ||
onseeking?: undefined | string | ||
onstalled?: undefined | string | ||
onsuspend?: undefined | string | ||
ontimeupdate?: undefined | string | ||
onvolumechange?: undefined | string | ||
onwaiting?: undefined | string | ||
onabort?: undefined | string; | ||
oncanplay?: undefined | string; | ||
oncanplaythrough?: undefined | string; | ||
ondurationchange?: undefined | string; | ||
onemptied?: undefined | string; | ||
onended?: undefined | string; | ||
onerror?: undefined | string; | ||
onloadeddata?: undefined | string; | ||
onloadedmetadata?: undefined | string; | ||
onloadstart?: undefined | string; | ||
onpause?: undefined | string; | ||
onplay?: undefined | string; | ||
onplaying?: undefined | string; | ||
onprogress?: undefined | string; | ||
onratechange?: undefined | string; | ||
onreadystatechange?: undefined | string; | ||
onseeked?: undefined | string; | ||
onseeking?: undefined | string; | ||
onstalled?: undefined | string; | ||
onsuspend?: undefined | string; | ||
ontimeupdate?: undefined | string; | ||
onvolumechange?: undefined | string; | ||
onwaiting?: undefined | string; | ||
} | ||
@@ -563,123 +563,181 @@ | ||
interface ElementChildrenAttribute { | ||
children?: undefined | any | ||
children?: undefined | any; | ||
} | ||
interface IntrinsicElements { | ||
a: HtmlAnchorTag | ||
abbr: HtmlTag | ||
address: HtmlTag | ||
area: HtmlAreaTag | ||
article: HtmlTag | ||
aside: HtmlTag | ||
audio: HtmlAudioTag | ||
b: HtmlTag | ||
bb: HtmlBrowserButtonTag | ||
base: BaseTag | ||
bdi: HtmlTag | ||
bdo: HtmlTag | ||
blockquote: HtmlQuoteTag | ||
body: HtmlBodyTag | ||
br: HtmlTag | ||
button: HtmlButtonTag | ||
canvas: HtmlCanvasTag | ||
caption: HtmlTag | ||
cite: HtmlTag | ||
code: HtmlTag | ||
col: HtmlTableColTag | ||
colgroup: HtmlTableColTag | ||
commands: HtmlCommandTag | ||
data: DataTag | ||
datalist: HtmlDataListTag | ||
dd: HtmlTag | ||
del: HtmlModTag | ||
details: HtmlDetailsTag | ||
dfn: HtmlTag | ||
div: HtmlTag | ||
dl: HtmlTag | ||
dt: HtmlTag | ||
em: HtmlTag | ||
embed: HtmlEmbedTag | ||
fieldset: HtmlFieldSetTag | ||
figcaption: HtmlTag | ||
figure: HtmlTag | ||
footer: HtmlTag | ||
form: HtmlFormTag | ||
hgroup: HtmlTag | ||
h1: HtmlTag | ||
h2: HtmlTag | ||
h3: HtmlTag | ||
h4: HtmlTag | ||
h5: HtmlTag | ||
h6: HtmlTag | ||
head: HtmlTag | ||
header: HtmlTag | ||
hr: HtmlTag | ||
html: HtmlHtmlTag | ||
i: HtmlTag | ||
iframe: HtmlIFrameTag | ||
img: HtmlImageTag | ||
input: HtmlInputTag | ||
ins: HtmlModTag | ||
kbd: HtmlTag | ||
keygen: KeygenTag | ||
label: HtmlLabelTag | ||
legend: HtmlLegendTag | ||
li: HtmlLITag | ||
link: HtmlLinkTag | ||
main: HtmlTag | ||
map: HtmlMapTag | ||
mark: HtmlTag | ||
menu: HtmlMenuTag | ||
meta: HtmlMetaTag | ||
meter: HtmlMeterTag | ||
nav: HtmlTag | ||
noscript: HtmlTag | ||
object: HtmlObjectTag | ||
ol: HtmlOListTag | ||
optgroup: HtmlOptgroupTag | ||
option: HtmlOptionTag | ||
output: HtmlOutputTag | ||
p: HtmlTag | ||
param: HtmlParamTag | ||
pre: HtmlTag | ||
progress: HtmlProgressTag | ||
q: HtmlQuoteTag | ||
rb: HtmlTag | ||
rp: HtmlTag | ||
rt: HtmlTag | ||
rtc: HtmlTag | ||
ruby: HtmlTag | ||
s: HtmlTag | ||
samp: HtmlTag | ||
script: HtmlScriptTag | ||
section: HtmlTag | ||
select: HtmlSelectTag | ||
small: HtmlTag | ||
summary: HtmlTag | ||
source: HtmlSourceTag | ||
span: HtmlTag | ||
strong: HtmlTag | ||
style: HtmlStyleTag | ||
sub: HtmlTag | ||
sup: HtmlTag | ||
svg: HtmlSvgTag | ||
table: HtmlTableTag | ||
tbody: HtmlTag | ||
td: HtmlTableDataCellTag | ||
template: HtmlTag | ||
textarea: HtmlTextAreaTag | ||
tfoot: HtmlTableSectionTag | ||
th: HtmlTableHeaderCellTag | ||
thead: HtmlTableSectionTag | ||
time: HtmlTimeTag | ||
title: HtmlTag | ||
tr: HtmlTableRowTag | ||
track: HtmlTrackTag | ||
u: HtmlTag | ||
ul: HtmlTag | ||
var: HtmlTag | ||
video: HtmlVideoTag | ||
wbr: HtmlTag | ||
tag: HtmlUnspecifiedTag | ||
a: HtmlAnchorTag; | ||
abbr: HtmlTag; | ||
address: HtmlTag; | ||
animate: HtmlSvgTag; | ||
animateMotion: HtmlSvgTag; | ||
animateTransform: HtmlSvgTag; | ||
area: HtmlAreaTag; | ||
article: HtmlTag; | ||
aside: HtmlTag; | ||
audio: HtmlAudioTag; | ||
b: HtmlTag; | ||
base: BaseTag; | ||
bb: HtmlBrowserButtonTag; | ||
bdi: HtmlTag; | ||
bdo: HtmlTag; | ||
blockquote: HtmlQuoteTag; | ||
body: HtmlBodyTag; | ||
br: HtmlTag; | ||
button: HtmlButtonTag; | ||
canvas: HtmlCanvasTag; | ||
caption: HtmlTag; | ||
circle: HtmlSvgTag; | ||
cite: HtmlTag; | ||
clipPath: HtmlSvgTag; | ||
code: HtmlTag; | ||
col: HtmlTableColTag; | ||
colgroup: HtmlTableColTag; | ||
commands: HtmlCommandTag; | ||
data: DataTag; | ||
datalist: HtmlDataListTag; | ||
dd: HtmlTag; | ||
defs: HtmlSvgTag; | ||
del: HtmlModTag; | ||
desc: HtmlSvgTag; | ||
details: HtmlDetailsTag; | ||
dfn: HtmlTag; | ||
div: HtmlTag; | ||
dl: HtmlTag; | ||
dt: HtmlTag; | ||
ellipse: HtmlSvgTag; | ||
em: HtmlTag; | ||
embed: HtmlEmbedTag; | ||
feBlend: HtmlSvgTag; | ||
feColorMatrix: HtmlSvgTag; | ||
feComponentTransfer: HtmlSvgTag; | ||
feComposite: HtmlSvgTag; | ||
feConvolveMatrix: HtmlSvgTag; | ||
feDiffuseLighting: HtmlSvgTag; | ||
feDisplacementMap: HtmlSvgTag; | ||
feDistantLight: HtmlSvgTag; | ||
feDropShadow: HtmlSvgTag; | ||
feFlood: HtmlSvgTag; | ||
feFuncA: HtmlSvgTag; | ||
feFuncB: HtmlSvgTag; | ||
feFuncG: HtmlSvgTag; | ||
feFuncR: HtmlSvgTag; | ||
feGaussianBlur: HtmlSvgTag; | ||
feImage: HtmlSvgTag; | ||
feMerge: HtmlSvgTag; | ||
feMergeNode: HtmlSvgTag; | ||
feMorphology: HtmlSvgTag; | ||
feOffset: HtmlSvgTag; | ||
fePointLight: HtmlSvgTag; | ||
feSpecularLighting: HtmlSvgTag; | ||
feSpotLight: HtmlSvgTag; | ||
feTile: HtmlSvgTag; | ||
feTurbulence: HtmlSvgTag; | ||
fieldset: HtmlFieldSetTag; | ||
figcaption: HtmlTag; | ||
figure: HtmlTag; | ||
filter: HtmlSvgTag; | ||
footer: HtmlTag; | ||
foreignObject: HtmlSvgTag; | ||
form: HtmlFormTag; | ||
g: HtmlSvgTag; | ||
h1: HtmlTag; | ||
h2: HtmlTag; | ||
h3: HtmlTag; | ||
h4: HtmlTag; | ||
h5: HtmlTag; | ||
h6: HtmlTag; | ||
head: HtmlTag; | ||
header: HtmlTag; | ||
hgroup: HtmlTag; | ||
hr: HtmlTag; | ||
html: HtmlHtmlTag; | ||
i: HtmlTag; | ||
iframe: HtmlIFrameTag; | ||
image: HtmlSvgTag; | ||
img: HtmlImageTag; | ||
input: HtmlInputTag; | ||
ins: HtmlModTag; | ||
kbd: HtmlTag; | ||
keygen: KeygenTag; | ||
label: HtmlLabelTag; | ||
legend: HtmlLegendTag; | ||
li: HtmlLITag; | ||
line: HtmlSvgTag; | ||
linearGradient: HtmlSvgTag; | ||
link: HtmlLinkTag; | ||
main: HtmlTag; | ||
map: HtmlMapTag; | ||
mark: HtmlTag; | ||
marker: HtmlSvgTag; | ||
mask: HtmlSvgTag; | ||
menu: HtmlMenuTag; | ||
meta: HtmlMetaTag; | ||
metadata: HtmlSvgTag; | ||
meter: HtmlMeterTag; | ||
mpath: HtmlSvgTag; | ||
nav: HtmlTag; | ||
noscript: HtmlTag; | ||
object: HtmlObjectTag; | ||
ol: HtmlOListTag; | ||
optgroup: HtmlOptgroupTag; | ||
option: HtmlOptionTag; | ||
output: HtmlOutputTag; | ||
p: HtmlTag; | ||
param: HtmlParamTag; | ||
path: HtmlSvgTag; | ||
pattern: HtmlSvgTag; | ||
polygon: HtmlSvgTag; | ||
polyline: HtmlSvgTag; | ||
pre: HtmlTag; | ||
progress: HtmlProgressTag; | ||
q: HtmlQuoteTag; | ||
radialGradient: HtmlSvgTag; | ||
rb: HtmlTag; | ||
rect: HtmlSvgTag; | ||
rp: HtmlTag; | ||
rt: HtmlTag; | ||
rtc: HtmlTag; | ||
ruby: HtmlTag; | ||
s: HtmlTag; | ||
samp: HtmlTag; | ||
script: HtmlScriptTag; | ||
section: HtmlTag; | ||
select: HtmlSelectTag; | ||
set: HtmlSvgTag; | ||
small: HtmlTag; | ||
source: HtmlSourceTag; | ||
span: HtmlTag; | ||
stop: HtmlSvgTag; | ||
strong: HtmlTag; | ||
style: HtmlStyleTag; | ||
sub: HtmlTag; | ||
summary: HtmlTag; | ||
sup: HtmlTag; | ||
svg: HtmlSvgTag; | ||
switch: HtmlSvgTag; | ||
symbol: HtmlSvgTag; | ||
table: HtmlTableTag; | ||
tag: HtmlUnspecifiedTag; | ||
tbody: HtmlTag; | ||
td: HtmlTableDataCellTag; | ||
template: HtmlTag; | ||
text: HtmlSvgTag; | ||
textarea: HtmlTextAreaTag; | ||
textPath: HtmlSvgTag; | ||
tfoot: HtmlTableSectionTag; | ||
th: HtmlTableHeaderCellTag; | ||
thead: HtmlTableSectionTag; | ||
time: HtmlTimeTag; | ||
title: HtmlTag; | ||
tr: HtmlTableRowTag; | ||
track: HtmlTrackTag; | ||
tspan: HtmlSvgTag; | ||
u: HtmlTag; | ||
ul: HtmlTag; | ||
use: HtmlSvgTag; | ||
var: HtmlTag; | ||
video: HtmlVideoTag; | ||
view: HtmlSvgTag; | ||
wbr: HtmlTag; | ||
} | ||
} |
{ | ||
"name": "@kitajs/html", | ||
"version": "2.2.2", | ||
"version": "3.0.0", | ||
"description": "Fast and type safe HTML templates using TypeScript.", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"bugs": "https://github.com/kitajs/html/issues", | ||
"repository": "https://github.com/kitajs/html", | ||
"bugs": "https://github.com/kitajs/html/issues", | ||
"funding": "https://github.com/kitajs/html?sponsor=1", | ||
"license": "Apache-2.0", | ||
"author": "arthurfiorette <npm@arthur.place>", | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"dependencies": { | ||
@@ -17,6 +17,7 @@ "csstype": "^3.1.2" | ||
"devDependencies": { | ||
"@arthurfiorette/prettier-config": "^1.0.9", | ||
"@types/node": "^20.4.2", | ||
"benny": "^3.7.1", | ||
"c8": "^8.0.1", | ||
"prettier": "^3.0.0", | ||
"standard": "^17.1.0", | ||
"tslib": "^2.6.1", | ||
@@ -26,2 +27,5 @@ "typed-html": "^3.0.1", | ||
}, | ||
"peerDependencies": { | ||
"@kitajs/ts-html-plugin": ">=1.1" | ||
}, | ||
"packageManager": "pnpm@8.7.5", | ||
@@ -32,7 +36,7 @@ "engines": { | ||
"scripts": { | ||
"test": "tsc && node --test dist/test", | ||
"bench": "tsc && node --expose-gc dist/benchmark", | ||
"format": "prettier --write .", | ||
"lint": "standard" | ||
"test": "tsc && c8 --reporter lcov --reporter text node --trace-warnings --test dist/test", | ||
"test:only": "tsc; node --trace-warnings --test-only --test dist/test" | ||
} | ||
} |
781
README.md
@@ -0,46 +1,55 @@ | ||
<p align="center"> | ||
<b>Using this package?</b> Please consider <a href="https://github.com/sponsors/arthurfiorette" target="_blank">donating</a> to support my open source work ❤️ | ||
<br /> | ||
<sup> | ||
Help @kitajs/html grow! Star and share this amazing repository with your friends and co-workers! | ||
</sup> | ||
</p> | ||
<br /> | ||
[![Issues](https://img.shields.io/github/issues/kitajs/html?logo=github&label=Issues)](https://github.com/kitajs/html/issues) | ||
[![Stars](https://img.shields.io/github/stars/kitajs/html?logo=github&label=Stars)](https://github.com/kitajs/html/stargazers) | ||
[![License](https://img.shields.io/github/license/kitajs/html?logo=githu&label=License)](https://github.com/kitajs/html/blob/master/LICENSE) | ||
[![Speed Blazing](https://img.shields.io/badge/speed-blazing%20%F0%9F%94%A5-brightgreen.svg)](https://twitter.com/acdlite/status/974390255393505280) | ||
<p align="center" > | ||
<a href="https://kita.js.org" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://kita.js.org/logo.png" width="180" alt="Kita JS logo" /> | ||
</a> | ||
</p> | ||
[![Latest Version](https://img.shields.io/npm/v/@kitajs/html)](https://www.npmjs.com/package/@kitajs/html) | ||
[![Downloads](https://img.shields.io/npm/dw/@kitajs/html)](https://www.npmjs.com/package/@kitajs/html) | ||
[![JsDelivr](https://data.jsdelivr.com/v1/package/npm/@kitajs/html/badge?style=rounded)](https://www.jsdelivr.com/package/npm/@kitajs/html) | ||
[![Bundlephobia](https://img.shields.io/bundlephobia/minzip/@kitajs/html/latest?style=flat)](https://bundlephobia.com/package/@kitajs/html@latest) | ||
[![Packagephobia](https://packagephobia.com/badge?p=@kitajs/html@latest)](https://packagephobia.com/result?p=@kitajs/html@latest) | ||
<br /> | ||
<div align="center"> | ||
<pre> | ||
<h1>🏛️<br />KitaJS Html</h1> | ||
</pre> | ||
<br /> | ||
<a title="MIT license" target="_blank" href="https://github.com/kitajs/html/blob/master/LICENSE"><img alt="License" src="https://img.shields.io/github/license/kitajs/html"></a> | ||
<a title="Codecov" target="_blank" href="https://app.codecov.io/gh/kitajs/html"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/kitajs/html?token=ML0KGCU0VM"></a> | ||
<a title="NPM Package" target="_blank" href="https://www.npmjs.com/package/@kitajs/html"><img alt="Downloads" src="https://img.shields.io/npm/dw/@kitajs/html?style=flat"></a> | ||
<a title="Bundle size" target="_blank" href="https://bundlephobia.com/package/@kitajs/html@latest"><img alt="Bundlephobia" src="https://img.shields.io/bundlephobia/minzip/@kitajs/html/latest?style=flat"></a> | ||
<a title="Last Commit" target="_blank" href="https://github.com/kitajs/html/commits/master"><img alt="Last commit" src="https://img.shields.io/github/last-commit/kitajs/html"></a> | ||
<a href="https://github.com/kitajs/html/stargazers"><img src="https://img.shields.io/github/stars/kitajs/html?logo=github&label=Stars" alt="Stars"></a> | ||
</div> | ||
<h3 align="center"> | ||
<br /> | ||
<br /> | ||
<h1>🏛️ KitaJS Html</h1> | ||
<p align="center"> | ||
<code>@kitajs/html</code> is a no dependencies, fast and concise package to generate HTML through JavaScript with JSX syntax. | ||
<br /> | ||
<br /> | ||
</h3> | ||
</p> | ||
<br /> | ||
> [!WARNING] | ||
> Learn how to [sanitize](#sanitization) and avoid [xss](https://owasp.org/www-community/attacks/xss) vulnerabilities in your code! | ||
<br /> | ||
## Table of Contents | ||
- [Table of Contents](#table-of-contents) | ||
- [Preview](#preview) | ||
- [Installing](#installing) | ||
- [Getting Started](#getting-started) | ||
- [Sanitization](#sanitization) | ||
- [The safe attribute](#the-safe-attribute) | ||
- [The Safe Attribute](#the-safe-attribute) | ||
- [Editor Intellisense and CLI tool](#editor-intellisense-and-cli-tool) | ||
- [Async Components](#async-components) | ||
- [Suspense component](#suspense-component) | ||
- [Error boundaries](#error-boundaries) | ||
- [Why JSX.Element is a Promise?](#why-jsxelement-is-a-promise) | ||
- [Migrating from HTML](#migrating-from-html) | ||
- [Htmx](#htmx) | ||
- [Hotwire Turbo](#hotwire-turbo) | ||
- [Base HTML templates](#base-html-templates) | ||
- [Htmx](#htmx) | ||
- [Compiling HTML](#compiling-html) | ||
@@ -51,3 +60,2 @@ - [Clean Components](#clean-components) | ||
- [The `tag` tag](#the-tag-tag) | ||
- [Async Components](#async-components) | ||
- [Extending types](#extending-types) | ||
@@ -61,99 +69,123 @@ - [Allow everything!](#allow-everything) | ||
<br /> | ||
<br /> | ||
## Installing | ||
## Preview | ||
```sh | ||
npm install @kitajs/html # or yarn add @kitajs/html | ||
``` | ||
<img align="center" src="assets/preview.png" alt="Example of an error thrown by this LSP plugin." /> | ||
<br /> | ||
## Getting Started | ||
## Installing | ||
Install `@kitajs/html` with your favorite package manager, import it into the top of your `jsx`/`tsx` file and change your tsconfig.json to transpile jsx syntax. | ||
To use the `@kitajs/html` package, follow these steps: | ||
```js | ||
// tsconfig.json | ||
1. Install the required npm packages, `@kitajs/html` and `@kitajs/ts-html-plugin`, to | ||
enable editor intellisense. Open your terminal and run: | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react", | ||
"jsxFactory": "Html.createElement", | ||
"jsxFragmentFactory": "Html.Fragment" | ||
} | ||
} | ||
``` | ||
```sh | ||
npm install @kitajs/html @kitajs/ts-html-plugin | ||
``` | ||
```jsx | ||
// Unique import to the top of your main.ts file. | ||
import '@kitajs/html/register' | ||
// Or import it directly everywhere you need it. | ||
import Html from '@kitajs/html' | ||
2. Configure your TypeScript project to transpile TSX/JSX into JavaScript and using our | ||
[LSP Plugin](#editor-intellisense-and-cli-tool). Update your `tsconfig.json` file with | ||
the following settings: | ||
// Using as a simple html builder | ||
console.log(<div>Hello World</div>) // '<div>Hello World</div>' | ||
```jsonc | ||
// tsconfig.json | ||
// Maybe your own server-side html frontend | ||
function route(request, response) { | ||
return response | ||
.header('Content-Type', 'text/html') | ||
.send(<div>Hello World</div>) | ||
} | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react", | ||
"jsxFactory": "Html.createElement", | ||
"jsxFragmentFactory": "Html.Fragment", | ||
"plugins": [{ "name": "@kitajs/ts-html-plugin" }] | ||
} | ||
} | ||
``` | ||
// What about generating a static html file? | ||
fs.writeFileSync( | ||
'index.html', | ||
<html> | ||
<head> | ||
<title>Hello World</title> | ||
</head> | ||
<body> | ||
<div>Hello World</div> | ||
</body> | ||
</html> | ||
) | ||
3. Append the | ||
[`xss-scan`](https://github.com/kitajs/ts-html-plugin/tree/main#running-as-cli) command | ||
into your test script. This CLI comes from @kitajs/ts-html-plugin, which catches XSS | ||
vulnerabilities that mey be introduced into your codebase. Open your `package.json` | ||
file and add the following script: | ||
// Also as a component library | ||
function Layout({ name, children }: Html.PropsWithChildren<{ name: string }>) { | ||
return ( | ||
<html> | ||
<head> | ||
<title>Hello World</title> | ||
</head> | ||
<body> | ||
<div>Hello {name}</div> | ||
{children} | ||
</body> | ||
</html> | ||
) | ||
} | ||
```jsonc | ||
// package.json | ||
console.log(<Layout name="World">I'm in the body!</Layout>) | ||
{ | ||
"scripts": { | ||
"test": "xss-scan" | ||
} | ||
} | ||
``` | ||
// Anywhere you want! All JSX becomes a string | ||
typeof (<div>Hello World</div>) === 'string' | ||
``` | ||
4. Ensure that your code editor is using the TypeScript version from your project's | ||
`node_modules` instead of the globally installed TypeScript. For Visual Studio Code, | ||
you can configure this in your workspace settings: | ||
This package just provides functions to transpile JSX to a HTML string, you can imagine doing something like this before, but now with type checking and intellisense: | ||
```jsonc | ||
// .vscode/settings.json | ||
```ts | ||
// without @kitajs/html | ||
const html = `<div> Hello World!<div>` ❌ | ||
``` | ||
{ | ||
"typescript.tsdk": "node_modules/typescript/lib" | ||
} | ||
``` | ||
```jsx | ||
// with @kitajs/html | ||
const html = <div>Hello World!<div> ✅ | ||
// Also results into a string, but with type checks. | ||
``` | ||
> [!WARNING] | ||
> Make sure to verify that your setup is correct by writing `console.log(<div>{'<' + '/div>'}</div>);` | ||
> in your editor. If it **PRODUCE ERRORS**, your setup is correct. Refer to the [@kitajs/ts-html-plugin](https://github.com/kitajs/ts-html-plugin) | ||
> repository for more details on setting up editor intellisense. | ||
<br /> | ||
## Getting Started | ||
After successfully installing and configuring your project, you can start using JSX syntax | ||
to generate HTML. Here are two options for importing the `@kitajs/html` package: | ||
1. **Import it manually**: Import the package in your TypeScript files where you need it, | ||
avoiding global scope pollution. | ||
```tsx | ||
// my-file.tsx | ||
console.log(<div>Html import needs to be in scope!</div>); | ||
``` | ||
2. **Use the register to add a global namespace**: Import the `register` to globally | ||
register all necessary functions for convenience. | ||
```tsx | ||
// Import the register to globally register all needed functions | ||
import '@kitajs/html/register'; | ||
// another-file.tsx | ||
console.log(<div>It works without importing!</div>); | ||
``` | ||
Now you can use JSX to generate HTML throughout your project. Always use the `safe` | ||
attribute or manually call `Html.escapeHtml` to protect against XSS vulnerabilities when | ||
rendering user input. | ||
Ensuring XSS prevention is vital to guarantee your application's security. You can employ | ||
the [`@kitajs/ts-html-plugin`](https://github.com/kitajs/ts-html-plugin) to catch XSS | ||
issues in your code editor and enhance your code's security. | ||
<br /> | ||
## Sanitization | ||
This package sanitizes every attribute by default. However, as the result is always a string, we cannot differentiate a html element created by a `<tag>` or from a user input. This forces you to use the provided [`safe`](#the-safe-attribute) or manually call `Html.escapeHtml`. | ||
> [!IMPORTANT] | ||
> Please utilize our `@kitajs/ts-html-plugin` to emit TypeScript errors wherever you are | ||
> exposed to XSS. Refer to [Getting Started](#getting-started) for installation | ||
> instructions. | ||
```jsx | ||
<div>⚠️ This will NOT be escaped. and WILL expose you to XSS</div> | ||
This package sanitizes every attribute by default. However, since the resulting element is | ||
always a string, it's impossible to differentiate an HTML element created by a `<tag>` or | ||
from user input. This necessitates the use of the provided [`safe`](#the-safe-attribute) | ||
or manual invocation of `Html.escapeHtml`. | ||
```tsx | ||
<div>⚠️ This will NOT be escaped and WILL expose you to XSS</div> | ||
<div attr="This WILL be escaped"></div> | ||
@@ -164,5 +196,5 @@ <div safe>This WILL be escaped</div> | ||
Here's an example of how this is **DANGEROUS** to your application: | ||
Here's an example of how this is **DANGEROUS** for your application: | ||
```jsx | ||
```tsx | ||
user = { | ||
@@ -174,8 +206,12 @@ name: 'Bad guy', | ||
<div class="user-card">{user.description}</div> | ||
// Renders this html which will execute malicious code: | ||
<div class="user-card"><script>getStoredPasswordAndSentToBadGuysServer()</script></div> | ||
// Renders this HTML, which will execute malicious code: | ||
<div class="user-card"> | ||
<script>getStoredPasswordAndSentToBadGuysServer()</script> | ||
</div> | ||
<div class="user-card" safe>{user.description}</div> | ||
// Renders this safe html, which will NOT execute any malicious code: | ||
<div></div><script>getStoredPasswordAndSentToBadGuysServer()</script></div> | ||
// Renders this safe HTML, which will NOT execute any malicious code: | ||
<div class="user-card"> | ||
</div><script>getStoredPasswordAndSentToBadGuysServer()</script> | ||
</div> | ||
``` | ||
@@ -185,7 +221,8 @@ | ||
### The safe attribute | ||
### The Safe Attribute | ||
You should always use the `safe` attribute when you are rendering uncontrolled user input. This will sanitize its contents and avoid XSS attacks. | ||
Always use the `safe` attribute when rendering uncontrolled user input. This will sanitize | ||
the contents and prevent XSS attacks. | ||
```jsx | ||
```tsx | ||
function UserCard({ name, description, date, about }) { | ||
@@ -198,3 +235,3 @@ return ( | ||
<br /> | ||
// controlled input, no need to sanitize | ||
// Controlled input, no need to sanitize | ||
<time datetime={date.toISOString()}>{date.toDateString()}</time> | ||
@@ -204,17 +241,246 @@ <br /> | ||
</div> | ||
) | ||
); | ||
} | ||
``` | ||
Note that only at the very bottom of the HTML tree is where you should use the `safe` attribute, to only escape where its needed. | ||
Note that you should only use the `safe` attribute at the very bottom of the HTML tree | ||
where it's needed. | ||
<br /> | ||
## Editor Intellisense and CLI tool | ||
<p align="center"> | ||
<img align="center" src="https://github.com/kitajs/ts-html-plugin/blob/main/assets/preview.png?raw=true" alt="Example of an error thrown by @kitajs/ts-html-plugin." width="75%" /> | ||
</p> | ||
<h2>⚠️</h2> | ||
**Note:** This section has been relocated to the | ||
[@kitajs/ts-html-plugin](https://github.com/kitajs/ts-html-plugin) repository. | ||
Please consult their | ||
"[Getting Started](https://github.com/kitajs/ts-html-plugin#getting-started)" section for | ||
instructions on enabling editor IntelliSense and using the CLI tool. | ||
<br /> | ||
<br /> | ||
<br /> | ||
<br /> | ||
## Async Components | ||
Async components are supported. When any child or sub child of a component tree is a | ||
promise, the whole tree will return a promise of the html string. | ||
If no async components are found, the result will be simply a string, and you can safely | ||
cast it into a string. | ||
```tsx | ||
async function Async() { | ||
await callApi(); | ||
return <div>Async!</div>; | ||
} | ||
function Sync() { | ||
return <div>Sync!</div>; | ||
} | ||
const async = ( | ||
<div> | ||
<Async /> | ||
</div> | ||
); | ||
async instanceof Promise; | ||
const sync: string = ( | ||
<div> | ||
<Sync /> | ||
</div> | ||
); | ||
typeof sync === 'string'; | ||
``` | ||
A `JSX.Element` will always be a string. Once a children element is a async component, the | ||
entire upper tree will also be async. | ||
[Learn when JSX.Element is a Promise](#why-jsxelement-is-a-promise). | ||
<br /> | ||
### Suspense component | ||
The only problem when rendering templates is that you must wait the whole template to be | ||
rendered before sending it to the client. This is not a problem for small templates, but | ||
it can be a problem for large templates. | ||
To solve this problem, we provide a `Suspense` component that combined with | ||
`renderToStream()` rendering method, will stream a fallback component while it waits for | ||
his children to be rendered. This is a perfect combo to use with | ||
[async components](#async-components). | ||
```tsx | ||
import { Suspense, renderToStream } from '@kitajs/html/suspense'; | ||
async function MyAsyncComponent() { | ||
const data = await database.query(); | ||
return <User name={data.username} />; | ||
} | ||
function renderUserPage(rid: number) { | ||
return ( | ||
<Suspense | ||
rid={rid} | ||
fallback={<div>Loading username...</div>} | ||
catch={(err) => <div>Error: {err.stack}</div>} | ||
> | ||
<MyAsyncComponent /> | ||
</Suspense> | ||
); | ||
} | ||
// Html is a string readable stream that can be piped to the client | ||
const html = renderToStream(renderUserPage); | ||
``` | ||
> [!NOTE] | ||
> There's an open issue to integrate this within a typescript plugin to emit warnings and alerts to use the safe attribute everywhere a variable is used. Wanna help? Check [this issue](https://github.com/kitajs/html/issues/2). | ||
> The `renderToStream()` is returns a native node/bun stream, head over to our | ||
> [suspense-server](examples/suspense-server.tsx) example to see how to use it with | ||
> node:http, Express or Fastify servers. | ||
The above example would render `<div>Loading username...</div>` while waiting for the | ||
`MyAsyncComponent` to be rendered. | ||
When using `Suspense`, you cannot just call the component and get the html string, you | ||
need to use the `renderToStream` function to get a stream that can be piped to the client | ||
with updates. Otherwise, the fallback would render forever. | ||
As the result of any JSX component is always a string, you must use the `rid` provided by | ||
`renderToStream` into all your suspense components, this way we can identify which | ||
suspense is for which request and be able to render concurrent requests. | ||
Suspense also accepts async fallbacks, but it blocks rendering until the fallback is | ||
resolved. | ||
```tsx | ||
function renderTemplate(rid: number) { | ||
return ( | ||
<Suspense | ||
rid={rid} | ||
fallback={<MyAsyncFallback />} | ||
catch={(err) => <div>Error: {err.stack}</div>} | ||
> | ||
<MyAsyncComponent /> | ||
</Suspense> | ||
); | ||
} | ||
``` | ||
The above example would only return anything after `MyAsyncFallback` is resolved. To catch | ||
async fallback errors, you must wrap it into a [`ErrorBoundary`](#error-boundaries). | ||
<br /> | ||
### Error boundaries | ||
The same way as promises must be awaited to resolve its own html, errors must be caught. | ||
Outside of suspense components, you can use the provided error boundaries to catch errors. | ||
```tsx | ||
import { ErrorBoundary } from '@kitajs/html/error-boundary'; | ||
async function MyAsyncComponent() { | ||
const data = await database.query(); // this promise may reject | ||
return <User name={data.username} />; | ||
} | ||
function renderTemplate() { | ||
return ( | ||
<ErrorBoundary catch={(err) => <div>Error: {err.stack}</div>}> | ||
<MyAsyncComponent /> | ||
</ErrorBoundary> | ||
); | ||
} | ||
// If MyAsyncComponent throws an error, it will render <div>Error: ...</div> | ||
const html = await renderTemplate(); | ||
``` | ||
Error boundaries will only work for errors thrown inside async components, for sync | ||
components you must use try/catch. | ||
```tsx | ||
function MySyncComponent() { | ||
try { | ||
const data = database.query(); // this may throw an error | ||
return <User name={data.username} />; | ||
} catch (err) { | ||
return <div>Error: {err.stack}</div>; | ||
} | ||
} | ||
``` | ||
Error boundaries outside suspense components will only catch errors thrown by the fallback | ||
component. You must use the Suspense's `catch` property to handle errors thrown by its | ||
children components. | ||
```tsx | ||
function renderTemplate(rid: number) { | ||
return ( | ||
<ErrorBoundary catch={<div>fallback error</div>}> | ||
<Suspense | ||
rid={rid} | ||
fallback={<MyAsyncFallback />} | ||
catch={<div>Children error</div>} | ||
> | ||
<MyAsyncComponent /> | ||
</Suspense> | ||
</ErrorBoundary> | ||
); | ||
} | ||
const html = renderToStream(renderTemplate); | ||
``` | ||
The above example would render `<div>Children error</div>` if `MyAsyncComponent` throws an | ||
error, or `<div>fallback error</div>` if `MyAsyncFallback` throws an error. If both throws | ||
an error, the first error will be changed to the second error as soon as the children | ||
error is thrown. | ||
<br /> | ||
### Why JSX.Element is a Promise? | ||
> ℹ️ Until [#14729](https://github.com/microsoft/TypeScript/issues/14729) gets | ||
> implemented, you need to manually cast `JSX.Element` into strings if you are sure there | ||
> is no inner async components in your component tree. | ||
JSX elements are mostly strings everywhere. However, as the nature of this package, once a | ||
children element is a async component, the entire upper tree will also be async. Unless | ||
you are sure that no other component in your entire codebase is async, you should always | ||
handle both string and promise cases. | ||
```tsx | ||
// It may or may not have inner async components. | ||
const html = <Layout />; | ||
if (html instanceof Promise) { | ||
// I'm a promise, I should be awaited | ||
console.log(await html); | ||
} else { | ||
// I'm a string, I can be used as is | ||
console.log(html); | ||
} | ||
``` | ||
<br /> | ||
## Migrating from HTML | ||
Migrating from plain HTML to JSX can be a pain to convert it all manually, as you will find yourself hand placing quotes and closing void elements. Luckily for us, there's a tool called [htmltojsx](https://magic.reactjs.net/htmltojsx.htm) that can help us with that. | ||
Migrating from plain HTML to JSX can be a pain to convert it all manually, as you will | ||
find yourself hand placing quotes and closing void elements. | ||
You can use [**Html To Jsx**](https://magic.reactjs.net/htmltojsx.htm). | ||
```html | ||
@@ -229,5 +495,5 @@ <!-- Hello world --> | ||
Generates: | ||
Results into: | ||
```jsx | ||
```tsx | ||
<> | ||
@@ -245,38 +511,20 @@ {/* Hello world */} | ||
### Base HTML templates | ||
### Htmx | ||
Often you will have a "template" html with doctype, things on the head, body and so on... The layout is also a very good component to be compiled. Here is a effective example on how to do it:. | ||
The usage of [htmx.org](https://htmx.org/) is super common with this project, this is why | ||
we also provide type definitions for all HTMX attributes. | ||
You just need to add this triple slash directive to the top of your file: | ||
```tsx | ||
export const Layout = html.compile( | ||
(p: Html.PropsWithChildren<{ head: string }>) => ( | ||
<> | ||
{'<!doctype html>'} | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta | ||
name="viewport" | ||
content="width=device-width, initial-scale=1.0" | ||
/> | ||
<title>Document</title> | ||
{p.head} | ||
</head> | ||
<body>{p.children}</body> | ||
</html> | ||
</> | ||
) | ||
) | ||
/// <reference types="@kitajs/html/htmx.d.ts" /> | ||
import '@kitajs/html/register'; | ||
const html = ( | ||
<Layout | ||
head={ | ||
<> | ||
<link rel="stylesheet" href="/style.css" /> | ||
<script src="/script.js" /> | ||
</> | ||
}> | ||
<div>Hello World</div> | ||
</Layout> | ||
) | ||
// Type checking and intellisense for all HTMX attributes | ||
<div hx-get="/api" hx-trigger="click" hx-target="#target"> | ||
Click me! | ||
</div> | ||
); | ||
``` | ||
@@ -286,5 +534,7 @@ | ||
### Htmx | ||
### Hotwire Turbo | ||
The usage of [htmx.org](https://htmx.org/) is super common with this project, this is why we also provide type definitions for all HTMX attributes. | ||
This project supports the usage of [Turbo Hotwire](https://turbo.hotwired.dev/). We | ||
provide a separate export that you can use to provide type definitions for the elements | ||
and attributes used with Turbo Hotwire. | ||
@@ -294,12 +544,14 @@ You just need to add this triple slash directive to the top of your file: | ||
```tsx | ||
/// <reference types="@kitajs/html/htmx.d.ts" /> | ||
/// <reference types="@kitajs/html/hotwire-turbo.d.ts" /> | ||
import '@kitajs/html/register' | ||
import '@kitajs/html/register'; | ||
const html = ( | ||
// Type checking and intellisense for all HTMX attributes | ||
<div hx-get="/api" hx-trigger="click" hx-target="#target"> | ||
Click me! | ||
</div> | ||
) | ||
<turbo-frame id="messages"> | ||
<a href="/messages/expanded">Show all expanded messages in this frame.</a> | ||
<form action="/messages">Show response from this form within this frame.</form> | ||
</turbo-frame> | ||
); | ||
``` | ||
@@ -309,28 +561,69 @@ | ||
### Base HTML templates | ||
Often you will have a "template" html with doctype, things on the head, body and so on... | ||
The layout is also a very good component to be compiled. Here is a effective example on | ||
how to do it:. | ||
```tsx | ||
export const Layout = html.compile((p: Html.PropsWithChildren<{ head: string }>) => ( | ||
<> | ||
{'<!doctype html>'} | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Document</title> | ||
{p.head} | ||
</head> | ||
<body>{p.children}</body> | ||
</html> | ||
</> | ||
)); | ||
const html = ( | ||
<Layout | ||
head={ | ||
<> | ||
<link rel="stylesheet" href="/style.css" /> | ||
<script src="/script.js" /> | ||
</> | ||
} | ||
> | ||
<div>Hello World</div> | ||
</Layout> | ||
); | ||
``` | ||
<br /> | ||
## Compiling HTML | ||
Compiles a **clean component** into a super fast component. This does not | ||
support unclean components / props processing. | ||
Compiles a **clean component** into a super fast component. This does not support unclean | ||
components / props processing. | ||
This mode works just like prepared statements in SQL. Compiled components can give up to [**2000**](#performance) times faster html generation. This is a opt-in feature that you may not be able to use everywhere! | ||
This mode works just like prepared statements in SQL. Compiled components can give up to | ||
[**2000**](#performance) times faster html generation. This is a opt-in feature that you | ||
may not be able to use everywhere! | ||
```tsx | ||
import Html from '@kitajs/html' | ||
import Html from '@kitajs/html'; | ||
function Component(props: Html.PropsWithChildren<{ name: string }>) { | ||
return <div>Hello {props.name}</div> | ||
return <div>Hello {props.name}</div>; | ||
} | ||
const compiled = Html.compile<typeof Component>(Component) | ||
const compiled = Html.compile<typeof Component>(Component); | ||
compiled({ name: 'World' }) | ||
compiled({ name: 'World' }); | ||
// <div>Hello World</div> | ||
const compiled = Html.compile((p) => <div>Hello {p.name}</div>) | ||
const compiled = Html.compile((p) => <div>Hello {p.name}</div>); | ||
compiled({ name: 'World' }) | ||
compiled({ name: 'World' }); | ||
// <div>Hello World</div> | ||
``` | ||
Properties passed for compiled components **ARE NOT** what will be passed as argument to the generated function. | ||
Properties passed for compiled components **ARE NOT** what will be passed as argument to | ||
the generated function. | ||
@@ -340,10 +633,11 @@ ```tsx | ||
// THIS WILL NOT print 123, but a string used by .compile instead | ||
console.log(t.asd) | ||
return <div></div> | ||
}) | ||
console.log(t.asd); | ||
return <div></div>; | ||
}); | ||
compiled({ asd: 123 }) | ||
compiled({ asd: 123 }); | ||
``` | ||
That's the reason on why you cannot compile unclean components, as they need to process the props before rendering. | ||
That's the reason on why you cannot compile unclean components, as they need to process | ||
the props before rendering. | ||
@@ -354,6 +648,5 @@ <br /> | ||
A **clean component** is a component that does not process props before | ||
applying them to the element. This means that the props are applied to the | ||
element as is, and you need to process them before passing them to the | ||
component. | ||
A **clean component** is a component that does not process props before applying them to | ||
the element. This means that the props are applied to the element as is, and you need to | ||
process them before passing them to the component. | ||
@@ -363,15 +656,15 @@ ```tsx | ||
function Clean(props: PropsWithChildren<{ repeated: string }>) { | ||
return <div>{props.repeated}</div> | ||
return <div>{props.repeated}</div>; | ||
} | ||
// Calculation is done before passing to the component | ||
html = <Clean name={'a'.repeat(5)} /> | ||
html = <Clean name={'a'.repeat(5)} />; | ||
// Unclean component, process before render | ||
function Unclean(props: { repeat: string; n: number }) { | ||
return <div>{props.repeat.repeat(props.n)}</div> | ||
return <div>{props.repeat.repeat(props.n)}</div>; | ||
} | ||
// Calculation is done inside the component, thus cannot be used with .compile() | ||
html = <Unclean repeat="a" n={5} /> | ||
html = <Unclean repeat="a" n={5} />; | ||
``` | ||
@@ -383,5 +676,6 @@ | ||
JSX does not allow multiple root elements, but you can use a fragment to group multiple elements: | ||
JSX does not allow multiple root elements, but you can use a fragment to group multiple | ||
elements: | ||
```jsx | ||
```tsx | ||
const html = ( | ||
@@ -392,3 +686,3 @@ <> | ||
</> | ||
) | ||
); | ||
``` | ||
@@ -407,3 +701,4 @@ | ||
**Missing an element or attribute?** Please create an issue or a PR to add it. It's easy to add. | ||
**Missing an element or attribute?** Please create an issue or a PR to add it. It's easy | ||
to add. | ||
@@ -414,3 +709,4 @@ <br /> | ||
The `<tag of="">` tag is a custom internal tag that allows you to render any runtime selected tag you want. Possibly reasons to prefer this tag over extending types: | ||
The `<tag of="">` tag is a custom internal tag that allows you to render any runtime | ||
selected tag you want. Possibly reasons to prefer this tag over extending types: | ||
@@ -423,3 +719,3 @@ - You want to render a tag that is chosen at runtime. | ||
```jsx | ||
```tsx | ||
<tag of="asd" /> | ||
@@ -432,28 +728,11 @@ // <asd></asd> | ||
We do recommend using [extending types](#extending-types) instead, as it will give you intellisense and type checking. | ||
We do recommend using [extending types](#extending-types) instead, as it will give you | ||
intellisense and type checking. | ||
<br /> | ||
## Async Components | ||
Sadly, we cannot allow async components in JSX and keep the same string type for everything else. Even though it should be possible to write async components you will have no real benefit from it, as you will always have to await the whole html generation | ||
to complete before you can render it. | ||
You should fetch async data in the following way: | ||
```jsx | ||
// Fetches all async code beforehand and passes its contents to the component. | ||
async function render(name) { | ||
const data = await api.data(name) | ||
const otherData = await api.otherData(name) | ||
return <Layout data={data} otherData={data} /> | ||
} | ||
``` | ||
<br /> | ||
## Extending types | ||
Just as exemplified above, you may also want to add custom properties to your elements. You can do this by extending the `JSX` namespace. | ||
Just as exemplified above, you may also want to add custom properties to your elements. | ||
You can do this by extending the `JSX` namespace. | ||
@@ -467,6 +746,6 @@ ```tsx | ||
// Changes properties to the math-power element | ||
['my-exponential']: number | ||
['my-exponential']: number; | ||
// this property becomes the <>{children}</> type | ||
children: number | ||
} | ||
children: number; | ||
}; | ||
} | ||
@@ -476,3 +755,3 @@ | ||
interface HtmlTag { | ||
['hx-boost']: boolean | ||
['hx-boost']: boolean; | ||
// TIP: We already provide HTMX types, check them out! | ||
@@ -487,3 +766,3 @@ } | ||
</mathPower> | ||
) | ||
); | ||
// Becomes <math-power my-exponential="2" hx-boost>3</math-power> | ||
@@ -494,3 +773,4 @@ ``` | ||
We also provide a way to allow any tag/attribute combination, altough we **do not recommend using it**. | ||
We also provide a way to allow any tag/attribute combination, altough we **do not | ||
recommend using it**. | ||
@@ -507,5 +787,8 @@ Just add this triple slash directive to the top of your file: | ||
This package is just a string builder on steroids, as you can see [how this works](#how-it-works). This means that most way to isolate performance differences is to micro benchmark. | ||
This package is just a string builder on steroids, as you can see | ||
[how this works](#how-it-works). This means that most way to isolate performance | ||
differences is to micro benchmark. | ||
You can run this yourself by running `pnpm bench`. The bench below was with a Apple M1 Pro 8gb. | ||
You can run this yourself by running `pnpm bench`. The benchmark below ran with a 13600k | ||
@5.1GHz machine. | ||
@@ -515,5 +798,5 @@ ```markdown | ||
- 2023-09-14T20:06:09.655Z | ||
- Node: v18.15.0 | ||
- V8: 10.2.154.26-node.25 | ||
- 2023-09-23T19:26:24.506Z | ||
- Node: v20.7.0 | ||
- V8: 11.3.244.8-node.15 | ||
- OS: linux | ||
@@ -526,29 +809,29 @@ - Arch: x64 | ||
| ------ | ------------ | ---------- | ----- | ---------- | ---------------- | -------------- | | ||
| 10 | 0.0088ms | 0.0177ms | 2.02x | 0.0066ms | 1.33x | 2.68x | | ||
| 10000 | 0.9501ms | 3.2714ms | 3.44x | 0.6968ms | 1.36x | 4.7x | | ||
| 100000 | 12.2114ms | 19.6453ms | 1.61x | 2.7776ms | 4.4x | 7.07x | | ||
| 10 | 0.0071ms | 0.0123ms | 1.72x | 0.0459ms | 0.16x | 0.27x | | ||
| 10000 | 0.4891ms | 1.9689ms | 4.03x | 0.4315ms | 1.13x | 4.56x | | ||
| 100000 | 4.717ms | 13.653ms | 2.89x | 1.5551ms | 3.03x | 8.78x | | ||
## Many Props | ||
## Mdn Homepage | ||
| Runs | @kitajs/html | typed-html | + | .compile() | + / @kitajs/html | + / typed-html | | ||
| ------ | ------------ | ------------ | ----- | ---------- | ---------------- | -------------- | | ||
| 10 | 0.2479ms | 1.3757ms | 5.55x | 0.0031ms | 79.8x | 442.91x | | ||
| 10000 | 240.6256ms | 982.6665ms | 4.08x | 0.7723ms | 311.56x | 1272.37x | | ||
| 100000 | 2436.4599ms | 10844.2049ms | 4.45x | 4.4179ms | 551.49x | 2454.59x | | ||
| 10 | 0.6441ms | 3.0466ms | 4.73x | 0.0029ms | 221.57x | 1048.03x | | ||
| 10000 | 601.5763ms | 3013.5356ms | 5.01x | 0.2293ms | 2623.21x | 13140.72x | | ||
| 100000 | 5953.055ms | 30076.7341ms | 5.05x | 1.1917ms | 4995.36x | 25238.17x | | ||
## Many Components | ||
## Many Props | ||
| Runs | @kitajs/html | typed-html | + | .compile() | + / @kitajs/html | + / typed-html | | ||
| ------ | ------------ | ----------- | ----- | ---------- | ---------------- | -------------- | | ||
| 10 | 0.2174ms | 0.5805ms | 2.67x | 0.0058ms | 37.43x | 99.91x | | ||
| 10000 | 169.6069ms | 665.6724ms | 3.92x | 1.5865ms | 106.91x | 419.6x | | ||
| 100000 | 1747.2244ms | 5227.3891ms | 2.99x | 7.5666ms | 230.91x | 690.85x | | ||
| 10 | 0.1971ms | 0.9631ms | 4.89x | 0.0026ms | 76.26x | 372.58x | | ||
| 10000 | 164.2186ms | 698.8376ms | 4.26x | 0.682ms | 240.78x | 1024.67x | | ||
| 100000 | 1688.9518ms | 7065.3811ms | 4.18x | 2.8688ms | 588.73x | 2462.82x | | ||
## Big Component | ||
## Many Components | ||
| Runs | @kitajs/html | typed-html | + | .compile() | + / @kitajs/html | + / typed-html | | ||
| ------ | ------------ | ----------- | ----- | ---------- | ---------------- | -------------- | | ||
| 10 | 0.2235ms | 0.8053ms | 3.6x | 0.0048ms | 46.53x | 167.64x | | ||
| 10000 | 175.1073ms | 735.6298ms | 4.2x | 0.9891ms | 177.03x | 743.7x | | ||
| 100000 | 1841.2463ms | 7872.5854ms | 4.28x | 5.1064ms | 360.58x | 1541.72x | | ||
| 10 | 0.2367ms | 0.4481ms | 1.89x | 0.0041ms | 57.64x | 109.14x | | ||
| 10000 | 148.0245ms | 368.8733ms | 2.49x | 1.2694ms | 116.61x | 290.6x | | ||
| 100000 | 1548.758ms | 3748.9902ms | 2.42x | 6.009ms | 257.74x | 623.9x | | ||
``` | ||
@@ -560,5 +843,7 @@ | ||
This package just aims to be a drop in replacement syntax for JSX, and it works because you [tell tsc to transpile](#getting-started) JSX syntax to calls to our own `html` namespace. | ||
This package just aims to be a drop in replacement syntax for JSX, and it works because | ||
you [tell tsc to transpile](#getting-started) JSX syntax to calls to our own `html` | ||
namespace. | ||
```jsx | ||
```tsx | ||
<ol start={2}> | ||
@@ -578,3 +863,3 @@ {[1, 2].map((i) => ( | ||
[1, 2].map((i) => Html.createElement('li', null, i)) | ||
) | ||
); | ||
``` | ||
@@ -585,3 +870,3 @@ | ||
```js | ||
'<ol start="2"><li>1</li><li>2</li></ol>' | ||
'<ol start="2"><li>1</li><li>2</li></ol>'; | ||
``` | ||
@@ -593,8 +878,9 @@ | ||
This package emits HTML as a compact string, useful for over the wire environments. However, if your use case really needs the output | ||
HTML to be pretty printed, you can use an external JS library to do so, like [html-prettify](https://www.npmjs.com/package/html-prettify). | ||
This package emits HTML as a compact string, useful for over the wire environments. | ||
However, if your use case really needs the output HTML to be pretty printed, you can use | ||
an external JS library to do so, like | ||
[html-prettify](https://www.npmjs.com/package/html-prettify). | ||
```jsx | ||
import Html from '@kitajs/html' | ||
import prettify from 'html-prettify' | ||
```tsx | ||
import prettify from 'html-prettify'; | ||
@@ -606,8 +892,8 @@ const html = ( | ||
</div> | ||
) | ||
); | ||
console.log(html) | ||
console.log(html); | ||
// <div><div>1</div><div>2</div></div> | ||
console.log(prettify(html)) | ||
console.log(prettify(html)); | ||
// <div> | ||
@@ -619,3 +905,4 @@ // <div>1</div> | ||
👉 There's an open PR to implement this feature natively, wanna work on it? Check [this PR](https://github.com/kitajs/html/pull/1). | ||
👉 There's an open PR to implement this feature natively, wanna work on it? Check | ||
[this PR](https://github.com/kitajs/html/pull/1). | ||
@@ -626,5 +913,9 @@ <br /> | ||
This repository was initially forked from [typed-html](https://github.com/nicojs/typed-html) and modified to add some features and increase performance. | ||
This repository was initially forked from | ||
[typed-html](https://github.com/nicojs/typed-html) and modified to add some features and | ||
increase performance. | ||
Initial credits to [nicojs](https://github.com/nicojs) and [contributors](https://github.com/nicojs/typed-html/graphs/contributors) for the amazing work. | ||
Initial credits to [nicojs](https://github.com/nicojs) and | ||
[contributors](https://github.com/nicojs/typed-html/graphs/contributors) for the amazing | ||
work. | ||
@@ -631,0 +922,0 @@ Licensed under the [Apache License, Version 2.0](LICENSE). |
declare global { | ||
/** | ||
* The html factory namespace. | ||
*/ | ||
var Html: typeof import('./index') | ||
/** The html factory namespace. */ | ||
var Html: typeof import('./index'); | ||
} | ||
export {} | ||
export {}; |
@@ -1,17 +0,20 @@ | ||
'use strict' | ||
'use strict'; | ||
// Finds the global object (window in browsers) | ||
let root | ||
let root; | ||
try { | ||
// eslint-disable-next-line no-new-func | ||
root = Function('return this')() | ||
root = Function('return this')(); | ||
/* c8 ignore next 3 */ | ||
} catch (_) { | ||
root = window | ||
root = window; | ||
} | ||
root.Html = require('./index') | ||
// Avoids multiple registrations | ||
if (!root.Html) { | ||
root.Html = require('./index'); | ||
} | ||
// Removes the default export wrapper | ||
if (root.Html.default) { | ||
root.Html = root.Html.default | ||
root.Html = root.Html.default; | ||
} |
## Security | ||
Due to the nature of this project, security is a top priority. If you believe you have | ||
found security issues like XSS attacks or similar, please report them to me ([`security@arthur.place`](mailto:security@arthur.place)) in a | ||
responsible manner. You will receive a response from | ||
me as soon as possible. | ||
found security issues like XSS attacks or similar, please report them to me | ||
([`security@arthur.place`](mailto:security@arthur.place)) in a responsible manner. You | ||
will receive a response from me as soon as possible. | ||
If the issue is confirmed, I will release a patch as soon as possible depending on | ||
complexity but historically within a few days. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
112541
21
0
2336
890
2
8
2