Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@kitajs/html

Package Overview
Dependencies
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@kitajs/html - npm Package Compare versions

Comparing version 1.4.7 to 2.0.0

57

index.d.ts

@@ -108,15 +108,58 @@ /// <reference path="./jsx.d.ts" />

/**
* Compiles html with the given arguments specified with $name syntax.
* Compiles a **clean component** into a super fast component. This does not
* support unclean components / props processing.
*
* @param {string} html
* @returns {function} the compiled function which
* 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: CleanProps<{ repeated: string }>) {
* return <div>{props.repeated}</div>
* }
*
* // 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>
* }
*
* // Calculation is done inside the component, thus cannot be used with .compile()
* html = <Unclean repeat="a" n={5} />
* ```
*
* @param {(proxy: any) => string} htmlFn
* @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}
*/
export function compile<A extends string[] = []>(
export function compile<
P extends
| Record<string, any>
| string[]
| ((args: Record<string, string>) => JSX.Element) = {}
>(
this: void,
html: string
): (args: Record<A[number], number | string | boolean>) => JSX.Element
factory: (
args: Record<
P extends string[]
? P[number]
: P extends (args: infer U) => JSX.Element
? keyof U
: keyof P,
string
>
) => JSX.Element,
strict?: boolean,
separator?: string
): typeof factory
/**
* Here for interop with preact and many build systems.
* Here for interop with `preact` and many build systems.
*/

@@ -123,0 +166,0 @@ export const h: typeof createElement

84

index.js

@@ -106,2 +106,3 @@ /// <reference path="./jsx.d.ts" />

continue
// Non breaking space
case '\u00A0':

@@ -353,19 +354,42 @@ escaped += value.slice(start, end) + '&#32;'

*
* @param {string} html
* @returns {function} the compiled function which
* @param {(proxy: any) => string} htmlFn
* @param {boolean} [strict=true] if we should throw an error when a property is not found.
* @returns {function} the compiled template function
* @this {void}
*/
function compile (html) {
function compile (htmlFn, strict = true, separator = '/*\x00*/') {
if (typeof htmlFn !== 'function') {
throw new Error('The first argument must be a function.')
}
const properties = new Set()
const html = htmlFn(new Proxy({}, {
get (_, name) {
// Adds the property to the set of known properties.
properties.add(name)
// Uses ` to avoid content being escaped.
return `\`${separator} + (args[${separator}\`${name.toString()}\`${separator}] || ${strict ? `throwPropertyNotFound(${separator}\`${name.toString()}\`${separator})` : `${separator}\`\`${separator}`}) + ${separator}\``
}
}))
const sepLength = separator.length
const length = html.length
let body = 'return '
// Adds the throwPropertyNotFound function if strict
let body = ''
let nextStart = 0
let paramEnd = 0
let index = 0
// Escapes every ` without separator
for (; index < length; index++) {
// Escapes the backtick character because it will be used to wrap the string
// in a template literal.
if (html[index] === '`') {
body += '`' + html.slice(nextStart, index) + '\\``+'
if (
html[index] === '`' &&
html.slice(index - sepLength, index) !== separator &&
html.slice(index + 1, index + sepLength + 1) !== separator
) {
body += html.slice(nextStart, index) + '\\`'
nextStart = index + 1

@@ -378,38 +402,28 @@ continue

if (html[index] === '\\') {
body += '`' + html.slice(nextStart, index) + '\\\\`+'
body += html.slice(nextStart, index) + '\\\\'
nextStart = index + 1
continue
}
}
// Skip non $ characters
if (html[index] !== '$') {
continue
}
// Adds the remaining string
body += html.slice(nextStart)
// Finds the end index of the current variable
paramEnd = index
while (
html[++paramEnd] !== undefined &&
// @ts-expect-error - this indexing is safe.
html[paramEnd].match(/[a-zA-Z0-9]/)
);
body +=
'`' +
html.slice(nextStart, index) +
'`+(args["' +
html.slice(index + 1, paramEnd) +
'"] || "' +
html.slice(index, paramEnd) +
'")+'
nextStart = paramEnd
index = paramEnd
if (strict) {
// eslint-disable-next-line no-new-func
return Function('args',
// Checks for args presence
'if (args === undefined) { throw new Error("The arguments object was not provided.") };\n' +
// Function to throw when a property is not found
'function throwPropertyNotFound(name) { throw new Error("Property " + name + " was not provided.") };\n' +
// Concatenates the body
`return \`${body}\``
)
}
// Adds the remaining string
body += '`' + html.slice(nextStart) + '`'
// eslint-disable-next-line no-new-func
return Function('args', body)
return Function('args',
// Adds a empty args object when it is not present
'if (args === undefined) { args = Object.create(null) };\n' + `return \`${body}\``
)
}

@@ -416,0 +430,0 @@

@@ -30,3 +30,3 @@ // This file is a result from many sources, including: RFCs, typescript dom lib, w3schools, and others.

spellcheck?: undefined | string | boolean
tabindex?: undefined | string
tabindex?: undefined | number | string
title?: undefined | string

@@ -33,0 +33,0 @@ translate?: undefined | string | boolean

{
"name": "@kitajs/html",
"version": "1.4.7",
"version": "2.0.0",
"description": "Fast and type safe HTML templates using TypeScript.",

@@ -31,3 +31,3 @@ "main": "index.js",

"test": "tsc && node --test dist/test",
"bench": "tsc && node dist/benchmark",
"bench": "tsc && node --expose-gc dist/benchmark",
"format": "prettier --write .",

@@ -34,0 +34,0 @@ "lint": "standard"

@@ -39,3 +39,5 @@ <br />

- [Migrating from HTML](#migrating-from-html)
- [Compiling html](#compiling-html)
- [Base HTML templates](#base-html-templates)
- [Compiling HTML](#compiling-html)
- [Clean Components](#clean-components)
- [Fragments](#fragments)

@@ -224,34 +226,105 @@ - [Supported HTML](#supported-html)

## Compiling html
### Base HTML templates
When you have static html, is simple to get amazing performances, just save it to a constant and reuse it. However, if you need to hydrate the html with dynamic values in a super fast way, you can use the `compile` property to compile the html and reuse it later.
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
import html from '@kitajs/html'
export const Layout = html.compile<html.PropsWithChildren>((p) => (
<>
{'<!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 compiled = html.compile<['param1', 'param2']>(
<div>
<div>$param1</div>
<div>$param2</div>
<div>$notFound</div>
</div>,
// or
<MyComponent param1="$param1" param2="$param2" />
const html = (
<Layout
head={
<>
<link rel="stylesheet" href="/style.css" />
<script src="/script.js" />
</>
}>
<div>Hello World</div>
</Layout>
)
```
const html = compiled({ param1: 'Hello', param2: 'World!' })
// formatted html to make it easier to read
// <div>
// <div>Hello</div>
// <div>World!</div>
// <div>$notFound</div>
// </div>
<br />
## Compiling HTML
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!
```tsx
import html from '@kitajs/html'
function Component(props: PropsWithChildren<{ name: string }>) {
return <div>Hello {props.name}</div>
}
compiled = html.compile<typeof Component>(Component)
compiled({ name: 'World' })
// <div>Hello World</div>
compiled = html.compile((p) => <div>Hello {p.name}</div>)
compiled({ name: 'World' })
// <div>Hello World</div>
```
This makes the html generation around [**_1500_**](#performance) times faster than just using normal jsx.
Properties passed for compiled components **ARE NOT** what will be passed as argument to the generated function.
Variables that were not passed to the `compile` function are ignored **silently**, this way you can reuse the result into another `compile` function or just because the your _"`$val`"_ was supposed to be a static value.
```tsx
compiled = html.compile((t) => {
// THIS WILL NOT print 123, but a string used by .compile instead
console.log(t.asd)
return <div></div>
})
compiled({ asd: 123 })
```
That's the reason on why you cannot compile unclean components, as they need to process the props before rendering.
<br />
### Clean Components
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.
```tsx
// Clean component, render as is
function Clean(props: CleanProps<{ repeated: string }>) {
return <div>{props.repeated}</div>
}
// 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>
}
// Calculation is done inside the component, thus cannot be used with .compile()
html = <Unclean repeat="a" n={5} />
```
<br />
## Fragments

@@ -364,17 +437,38 @@

This package is just a string builder on steroids, as you can see [how this works](#how-it-works). However we are running a benchmark with an JSX HTML with about 10K characters to see how it performs.
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`.
You can run this yourself by running `pnpm bench`. The bench below was with a Apple M1 Pro 8gb.
```java
// Apple M1 Pro 8gb
```markdown
# Benchmark
@kitajs/html:
44 767 ops/s, ±0.17% | 99.91% slower
- 2023-09-11T00:53:49.607Z
- Node: v18.16.0
- V8: 10.2.154.26-node.26
- OS: darwin
- Arch: arm64
@kitajs/html - compiled:
48 124 728 ops/s, ±0.48% | fastest
## Hello World
typed-html:
19 199 ops/s, ±0.45% | slowest, 99.96% slower
| Runs | @kitajs/html | typed-html | + | .compile() | + / @kitajs/html | + / typed-html |
| ------ | ------------ | ---------- | ----- | ---------- | ---------------- | -------------- |
| 10 | 0.0063ms | 0.0107ms | 1.68x | 0.0013ms | 5.07x | 8.53x |
| 10000 | 1.632ms | 4.848ms | 2.97x | 0.9131ms | 1.79x | 5.31x |
| 100000 | 9.4629ms | 19.367ms | 2.05x | 2.3115ms | 4.09x | 8.38x |
## Many Props
| Runs | @kitajs/html | typed-html | + | .compile() | + / @kitajs/html | + / typed-html |
| ------ | ------------ | ----------- | ----- | ---------- | ---------------- | -------------- |
| 10 | 0.4629ms | 1.3898ms | 3x | 0.0025ms | 182.19x | 547.04x |
| 10000 | 372.5842ms | 840.7459ms | 2.26x | 0.6308ms | 590.66x | 1332.84x |
| 100000 | 3438.7935ms | 7706.0509ms | 2.24x | 3.7163ms | 925.32x | 2073.56x |
## Big Component
| Runs | @kitajs/html | typed-html | + | .compile() | + / @kitajs/html | + / typed-html |
| ------ | ------------ | ----------- | ----- | ---------- | ---------------- | -------------- |
| 10 | 0.3075ms | 0.8844ms | 2.88x | 0.0037ms | 81.99x | 235.85x |
| 10000 | 222.5096ms | 521.0473ms | 2.34x | 0.7118ms | 312.61x | 732.02x |
| 100000 | 2211.6316ms | 5229.3416ms | 2.36x | 4.1123ms | 537.82x | 1271.65x |
```

@@ -381,0 +475,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc