From 6790818c4c694a9816bf7ccddc39779fa3afc8d1 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Sun, 5 May 2024 12:56:11 -0300 Subject: [PATCH] feat: added esm support --- .changeset/makes_tsx_happy.md | 6 +- .changeset/polite-rivers-run.md | 5 + .changeset/ten-pandas-impress.md | 6 + README.md | 10 +- packages/fastify-html-plugin/index.js | 16 ++- packages/html/README.md | 121 +++++--------------- packages/html/index.d.ts | 49 --------- packages/html/index.js | 153 ++++---------------------- packages/html/jsx-dev-runtime.js | 14 +-- packages/html/jsx-runtime.js | 17 +-- packages/html/package.json | 8 ++ packages/html/suspense.js | 12 +- 12 files changed, 104 insertions(+), 313 deletions(-) create mode 100644 .changeset/polite-rivers-run.md create mode 100644 .changeset/ten-pandas-impress.md diff --git a/.changeset/makes_tsx_happy.md b/.changeset/makes_tsx_happy.md index 3a7b1a08d..c301daa6a 100644 --- a/.changeset/makes_tsx_happy.md +++ b/.changeset/makes_tsx_happy.md @@ -1,5 +1,7 @@ --- -'@kitajs/html': minor +'@kitajs/html': patch --- -Makes [tsx](https://www.npmjs.com/package/tsx) and the underling [esbuild](https://esbuild.github.io/) ecosystem happy by exporting the `Fragment`, `jsx` and `jsxs` from `@kitajs/html/jsx-runtime` +Makes [tsx](https://www.npmjs.com/package/tsx) and the underling +[esbuild](https://esbuild.github.io/) ecosystem happy by exporting the `Fragment`, `jsx` +and `jsxs` from `@kitajs/html/jsx-runtime` diff --git a/.changeset/polite-rivers-run.md b/.changeset/polite-rivers-run.md new file mode 100644 index 000000000..0016948fa --- /dev/null +++ b/.changeset/polite-rivers-run.md @@ -0,0 +1,5 @@ +--- +'@kitajs/html': patch +--- + +Removed Html.compile support diff --git a/.changeset/ten-pandas-impress.md b/.changeset/ten-pandas-impress.md new file mode 100644 index 000000000..96acb6c98 --- /dev/null +++ b/.changeset/ten-pandas-impress.md @@ -0,0 +1,6 @@ +--- +'@kitajs/fastify-html-plugin': patch +'@kitajs/html': patch +--- + +Added ESM support diff --git a/README.md b/README.md index 954982bb9..2003a3999 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ![image](https://github.com/kitajs/html/assets/47537704/edd85ef1-9469-475e-83f8-c6819cf2cc1b)

- Please consider donating to support my open source work ❤️ -
- - Help KitaJS grow! Star and share this amazing repository with your friends and co-workers! - +Please consider +donating to +support my open source work ❤️
Help KitaJS grow! Star and share this amazing +repository with your friends and co-workers! +


diff --git a/packages/fastify-html-plugin/index.js b/packages/fastify-html-plugin/index.js index e239d78cc..fe67ac172 100644 --- a/packages/fastify-html-plugin/index.js +++ b/packages/fastify-html-plugin/index.js @@ -17,21 +17,20 @@ const kAutoDoctype = Symbol.for('fastify-kita-html.autoDoctype'); * >} */ function plugin(fastify, opts, next) { - // Good defaults - opts.autoDoctype ??= true; - - fastify.decorateReply(kAutoDoctype, opts.autoDoctype); + fastify.decorateReply(kAutoDoctype, (opts.autoDoctype ??= true)); fastify.decorateReply('html', html); - return next(); } /** @type {import('fastify').FastifyReply['html']} */ function html(htmlStr) { + if (typeof htmlStr === 'string') { + // @ts-expect-error - generics break the type inference here + return handleHtml(htmlStr, this); + } + // @ts-expect-error - generics break the type inference here - return typeof htmlStr === 'string' - ? handleHtml(htmlStr, this) - : handleAsyncHtml(htmlStr, this); + return handleAsyncHtml(htmlStr, this); } /** @@ -99,5 +98,4 @@ const fastifyKitaHtml = fp(plugin, { module.exports = fastifyKitaHtml; module.exports.default = fastifyKitaHtml; // supersedes fastifyKitaHtml.default = fastifyKitaHtml module.exports.fastifyKitaHtml = fastifyKitaHtml; // supersedes fastifyKitaHtml.fastifyKitaHtml = fastifyKitaHtml - module.exports.kAutoDoctype = kAutoDoctype; diff --git a/packages/html/README.md b/packages/html/README.md index b7c7263b5..001b7fce4 100644 --- a/packages/html/README.md +++ b/packages/html/README.md @@ -58,8 +58,6 @@ - [Alpinejs](#alpinejs) - [Hotwire Turbo](#hotwire-turbo) - [Base HTML templates](#base-html-templates) -- [Compiling HTML](#compiling-html) - - [Clean Components](#clean-components) - [Fragments](#fragments) - [Supported HTML](#supported-html) - [The `tag` tag](#the-tag-tag) @@ -71,6 +69,7 @@ - [Serialization table](#serialization-table) - [Format HTML output](#format-html-output) - [Deprecating global register](#deprecating-global-register) +- [Deprecating importing type extensions](#deprecating-importing-type-extensions) - [Fork credits](#fork-credits)
@@ -743,93 +742,6 @@ const html = (
-## Compiling HTML - -`Html.compile` interface compiles a [clean component](#clean-components) into a super fast -component. This does not support unclean components / props processing. - -
- -> [!WARNING] -> This feature is a special use case for rendering **entire page templates** like what you -> would do with handlebars or nunjucks. -> -> It does not works with mostly JSX components and, for small components, -> [it will be slower than the normal](benchmark.md) JSX syntax. - -
- -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! - -Due to the nature of -[`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) -objects, the spread operator (`...`) will not work with compiled components. You need to -manually pass all props to their components. - -```tsx -import Html from '@kitajs/html'; - -function Component(props: Html.PropsWithChildren<{ name: string }>) { - return
Hello {props.name}
; -} - -const compiled = Html.compile(Component); - -compiled({ name: 'World' }); -//
Hello World
- -const compiled = Html.compile((p) =>
Hello {p.name}
); - -compiled({ name: 'World' }); -//
Hello World
-``` - -Properties passed for compiled components **ARE NOT** what will be passed as argument to -the generated function. - -```tsx -const compiled = Html.compile((t) => { - // THIS WILL NOT print 123, but a string used by .compile instead - console.log(t.asd); - return
; -}); - -compiled({ asd: 123 }); -``` - -That's the reason on why you cannot compile unclean components, as they need to process -the props before rendering. - -
- -### 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: PropsWithChildren<{ sum: number }>) { - return
{props.sum}
; -} - -// Calculation is done before passing to the component -html = ; - -// Unclean component, process before render -function Unclean(props: { a: number; b: number }) { - return
{props.a * props.b}
; -} - -// Calculation is done inside the component, thus cannot be used with .compile() -html = ; -``` - -
- ## Fragments JSX does not allow multiple root elements, but you can use a fragment to group multiple @@ -1108,8 +1020,8 @@ console.log(prettify(html)); ## Deprecating global register -The `@kitajs/html/register` import has been deprecated and will be removed in the next -major version. Please change the way you have configured your project to use this library. +The `@kitajs/html/register` in favour of the `react-jsx` target `@kitajs/html` supports, +which automatically registers the JSX runtime globally. Please update your tsconfig to use the new `jsxImportSource` option and remove all references to `'@kitajs/html/register'` from your codebase. @@ -1136,6 +1048,33 @@ codebase.
+## Deprecating importing type extensions + +Importing type extensions like `import '@kitajs/html/htmx'` and +`import '@kitajs/html/alpine'` have been deprecated and will be removed in the next major +version. + +Please change the way you import them to either use `/// ` +[triple slash directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) +or the [`types`](https://www.typescriptlang.org/tsconfig/#types) option in your tsconfig. + +```diff +- import '@kitajs/html/htmx'; ++ /// +``` + +**Or** add them in the `types` option present in your tsconfig: + +```diff +{ + "compilerOptions": { ++ "types": ["@kitajs/html/htmx"] + } +} +``` + +
+ ## Fork credits This repository was initially forked from diff --git a/packages/html/index.d.ts b/packages/html/index.d.ts index dec2c94ae..e95a7293a 100644 --- a/packages/html/index.d.ts +++ b/packages/html/index.d.ts @@ -118,45 +118,6 @@ export function contentToString( escape?: boolean ): JSX.Element; -/** - * 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. - * - * @example - * - * ```tsx - * // Clean component, render as is - * function Clean(props: PropsWithChildren<{ repeated: string }>) { return
{props.repeated}
} - * - * // Calculation is done before passing to the component - * html = - * - * // Unclean component, process before render - * function Unclean(props: { repeat: string; n: number }) { return
{props.repeat.repeat(props.n)}
} - * - * // Calculation is done inside the component, thus cannot be used with .compile() html = - * - * ``` - * - * @param htmlComponent The _clean_ component to compile. - * @param strict If it should throw an error when a property is not found. Default is - * `true` - * @param separator The string used to interpolate and separate parameters - * @returns The compiled template function - */ -export function compile< - P extends { [K in keyof P]: K extends 'children' ? Children : string } ->( - this: void, - cleanComponent: Component

, - strict?: boolean, - separator?: string -): Component

; - /** Here for interop with `preact` and many build systems. */ export const h: typeof createElement; @@ -211,13 +172,3 @@ export type Component = (this: void, props: PropsWithChildren) => JSX * @link https://www.npmjs.com/package/@kitajs/html */ export const Html: Omit; - -/** - * 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 - */ -export as namespace Html; diff --git a/packages/html/index.js b/packages/html/index.js index 3615cd939..5f8f53bba 100644 --- a/packages/html/index.js +++ b/packages/html/index.js @@ -462,17 +462,16 @@ function createElement(name, attrs, ...children) { // We at least need to pass the children to the function component. We may receive null if this // component was called without any children. if (!hasAttrs) { - attrs = { children: children.length > 1 ? children : children[0] }; - } else if (attrs.children === undefined) { - attrs.children = children.length > 1 ? children : children[0]; + return name({ children: children.length > 1 ? children : children[0] }); } + attrs.children = children.length > 1 ? children : children[0]; return name(attrs); } // Switches the tag name when this custom `tag` is present. if (hasAttrs && name === 'tag') { - name = String(attrs.of); + name = /** @type {string} */ (attrs.of); } const attributes = hasAttrs ? attributesToString(attrs) : ''; @@ -485,138 +484,30 @@ function createElement(name, attrs, ...children) { const contents = contentsToString(children, hasAttrs && attrs.safe); - if (contents instanceof Promise) { - return contents.then(function resolveContents(child) { - return '<' + name + attributes + '>' + child + ''; - }); + if (typeof contents === 'string') { + return '<' + name + attributes + '>' + contents + ''; } - return '<' + name + attributes + '>' + contents + ''; + return contents.then(function resolveContents(contents) { + return '<' + name + attributes + '>' + contents + ''; + }); } /** @type {import('.').Fragment} */ function Fragment(props) { - return Html.contentsToString([props.children]); + return contentsToString([props.children]); } -/** - * Just to stop TS from complaining about the type. - * - * @type {import('.').compile} - * @returns {Function} - */ -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( - // @ts-expect-error - this proxy will meet the props with children requirements. - new Proxy( - {}, - { - get(_, name) { - // Adds the property to the set of known properties. - properties.add(name); - - 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}`; - } - - // Uses ` to avoid content being escaped. - return `\`${separator} + (${access} || ${ - strict && !isChildren - ? `throwPropertyNotFound(${separator}\`${name.toString()}\`${separator})` - : `${separator}\`\`${separator}` - }) + ${separator}\``; - } - } - ) - ); - - 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; - - // 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] === '`' && - html.slice(index - sepLength, index) !== separator && - html.slice(index + 1, index + sepLength + 1) !== separator - ) { - body += html.slice(nextStart, index) + '\\`'; - nextStart = index + 1; - } - } - - // Adds the remaining string - body += html.slice(nextStart); - - if (strict) { - 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}\`` - ); - } - - return Function( - 'args', - // Adds a empty args object when it is not present - 'if (args === undefined) { args = Object.create(null) };\n' + `return \`${body}\`` - ); -} - -const Html = { - escape, - e: escape, - escapeHtml, - isVoidElement, - attributesToString, - toKebabCase, - isUpper, - styleToString, - createElement, - h: createElement, - contentsToString, - contentToString, - compile, - Fragment -}; - -/** - * 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 Html from '@kitajs/html'` - * - `import { Html, type ComponentWithChildren } from '@kitajs/html'` - * - `import Html from '@kitajs/html'` - * - `import Html, { type ComponentWithChildren } from '@kitajs/html'` - */ -module.exports = Html; -module.exports.Html = Html; -module.exports.default = Html; +exports.escape = escape; +exports.e = escape; +exports.escapeHtml = escapeHtml; +exports.isVoidElement = isVoidElement; +exports.attributesToString = attributesToString; +exports.toKebabCase = toKebabCase; +exports.isUpper = isUpper; +exports.styleToString = styleToString; +exports.createElement = createElement; +exports.h = createElement; +exports.contentsToString = contentsToString; +exports.contentToString = contentToString; +exports.Fragment = Fragment; diff --git a/packages/html/jsx-dev-runtime.js b/packages/html/jsx-dev-runtime.js index cc50faf54..014acd53f 100644 --- a/packages/html/jsx-dev-runtime.js +++ b/packages/html/jsx-dev-runtime.js @@ -1,12 +1,10 @@ /// +/// +/// const { Fragment, jsx, jsxs } = require('./jsx-runtime'); -const JsxRuntime = { - jsxDEV: jsx, - jsxs, - Fragment -}; - -module.exports = JsxRuntime; -module.exports.default = JsxRuntime; +exports.jsx = jsx; +exports.jsxs = jsxs; +exports.jsxDEV = jsx; +exports.Fragment = Fragment; diff --git a/packages/html/jsx-runtime.js b/packages/html/jsx-runtime.js index 475f2db83..73e1a388e 100644 --- a/packages/html/jsx-runtime.js +++ b/packages/html/jsx-runtime.js @@ -1,4 +1,6 @@ /// +/// +/// const { Fragment, @@ -17,7 +19,7 @@ function jsx(name, attrs) { // Switches the tag name when this custom `tag` is present. if (name === 'tag') { - name = String(attrs.of); + name = /** @type {string} */ (attrs.of); } const attributes = attributesToString(attrs); @@ -48,7 +50,7 @@ function jsxs(name, attrs) { // Switches the tag name when this custom `tag` is present. if (name === 'tag') { - name = String(attrs.of); + name = /** @type {string} */ (attrs.of); } const attributes = attributesToString(attrs); @@ -70,16 +72,7 @@ function jsxs(name, attrs) { return '<' + name + attributes + '>' + contents + ''; } -const JsxRuntime = { - jsx, - jsxs, - - // According to the jsx-runtime spec we must export the fragment element also - Fragment -}; - -module.exports = JsxRuntime; exports.jsx = jsx; exports.jsxs = jsxs; +// According to the jsx-runtime spec we must export the fragment element also exports.Fragment = Fragment; -module.exports.default = JsxRuntime; diff --git a/packages/html/package.json b/packages/html/package.json index bd34f3fc1..a653a1793 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -15,7 +15,15 @@ "sideEffects": [ "register.js" ], + "exports": { + ".": "./index.js", + "./jsx-runtime": "./jsx-runtime.js", + "./jsx-dev-runtime": "./jsx-runtime.js", + "./package.json": "./package.json", + "./*": "./*" + }, "main": "index.js", + "module": "index.js", "types": "index.d.ts", "scripts": { "bench": "pnpm --filter \"benchmark-*\" build && pnpm --filter \"benchmark-runner\" start", diff --git a/packages/html/suspense.js b/packages/html/suspense.js index 5d1ab7c3a..f4ea1347a 100644 --- a/packages/html/suspense.js +++ b/packages/html/suspense.js @@ -169,11 +169,11 @@ function Suspense(props) { // Keeps string return type if (typeof fallback === 'string') { - return `

${fallback}
`; + return '
' + fallback + '
'; } return fallback.then(function resolveCallback(resolved) { - return `
${resolved}
`; + return '
' + resolved + '
'; }); /** @@ -287,7 +287,7 @@ async function renderToString(factory, rid) { return chunks.join(''); } -module.exports.Suspense = Suspense; -module.exports.renderToStream = renderToStream; -module.exports.renderToString = renderToString; -module.exports.SuspenseScript = SuspenseScript; +exports.Suspense = Suspense; +exports.renderToStream = renderToStream; +exports.renderToString = renderToString; +exports.SuspenseScript = SuspenseScript;