import type { Twind } from './types' import { changed } from './internal/changed' import { tw as tw$ } from './runtime' import { identity } from './utils' import { stringify } from './sheets' import { fixClassList, parseHTML } from './internal/parse-html' /** * Options for {@link inline} */ export interface InlineOptions { /** * {@link Twind} instance to use (default: {@link @twind/core.tw}) */ tw?: Twind /** * Allows to minify the resulting CSS. */ minify?: InlineMinify } export interface InlineMinify { /** * Called to minify the CSS. * * @param css the CSS to minify * @param html the HTML that will be used — allows to only include above-the-fold CSS * @return the resulting CSS */ (css: string, html: string): string } /** * Used for static HTML processing (usually to provide SSR support for your javascript-powered web apps) * * 1. parse the markup and process element classes with the provided Twind instance * 2. update the class attributes _if_ necessary * 3. inject a style element with the CSS as last element into the head * 4. return the HTML string with the final element classes * * ```js * import { inline } from '@twind/core' * * function render() { * return inline(renderApp()) * } * ``` * * Minify CSS with [@parcel/css](https://www.npmjs.com/package/@parcel/css): * * ```js * import { inline } from '@twind/core' * import { transform } from '@parcel/css' * * function render() { * return inline(renderApp(), { minify: (css) => transform({ filename: 'twind.css', code: Buffer.from(css), minify: true }) }) * } * ``` * * You can provide your own Twind instance: * * ```js * import { inline } from '@twind/core' * import { tw } from './custom/twind/instance' * * function render() { * return inline(renderApp(), { tw }) * } * ``` * * @group Static Extraction * @param markup HTML to process * @param options to customize the processing * @returns the resulting HTML */ export function inline(markup: string, options: InlineOptions['tw'] | InlineOptions = {}): string { const { tw = tw$, minify = identity } = typeof options == 'function' ? ({ tw: options } as InlineOptions) : options const { html, css } = extract(markup, tw) // inject as last element into the head return html.replace('', ``) } /** * Result of {@link extract} */ export interface ExtractResult { /** The possibly modified HTML */ html: string /** The generated CSS */ css: string } /** * Used for static HTML processing (usually to provide SSR support for your javascript-powered web apps) * * **Note**: Consider using {@link inline} instead. * * 1. parse the markup and process element classes with the provided Twind instance * 2. update the class attributes _if_ necessary * 3. return the HTML string with the final element classes * * ```js * import { extract } from '@twind/core' * * function render() { * const { html, css } = extract(renderApp()) * * // inject as last element into the head * return html.replace('', ``) * } * ``` * * You can provide your own Twind instance: * * ```js * import { extract } from '@twind/core' * import { tw } from './custom/twind/instance' * * function render() { * const { html, css } = extract(renderApp(), tw) * * // inject as last element into the head * return html.replace('', ``) * } * ``` * * @group Static Extraction * @param markup HTML to process * @param tw a {@link Twind} instance (default: twind managed tw) * @returns the possibly modified html and css */ export function extract(html: string, tw: Twind = tw$): ExtractResult { const restore = tw.snapshot() const result = { html: consume(html, tw), css: stringify(tw.target) } restore() return result } /** * Used for static HTML processing (usually to provide SSR support for your javascript-powered web apps) * * **Note**: Consider using {@link inline} or {@link extract} instead. * * 1. parse the markup and process element classes with the provided Twind instance * 2. update the class attributes _if_ necessary * 3. return the HTML string with the final element classes * * ```js * import { consume, stringify, tw } from '@twind/core' * * function render() { * const html = renderApp() * * // remember global classes * const restore = tw.snapshot() * * // generated markup * const markup = consume(html) * * // create CSS * const css = stringify(tw.target) * * // restore global classes * restore() * * // inject as last element into the head * return markup.replace('', ``) * } * ``` * * You can provide your own Twind instance: * * ```js * import { consume, stringify } from '@twind/core' * import { tw } from './custom/twind/instance' * * function render() { * const html = renderApp() * * // remember global classes * const restore = snapshot(tw.target) * * // generated markup * const markup = consume(html) * * // restore global classes * restore() * * // create CSS * const css = stringify(tw.target) * * // inject as last element into the head * return markup.replace('', ``) * } * ``` * * @group Static Extraction * @param markup HTML to process * @param tw a {@link Twind} instance * @returns possibly modified HTML */ export function consume(markup: string, tw: (className: string) => string = tw$): string { let result = '' let lastChunkStart = 0 parseHTML(markup, (startIndex, endIndex, quote) => { const value = markup.slice(startIndex, endIndex) const className = tw(fixClassList(value, quote)) // We only need to shift things around if we need to actually change the markup if (changed(value, className)) { // We've hit another mutation boundary // Add quote if necessary quote = quote ? '' : '"' result += markup.slice(lastChunkStart, startIndex) + quote + className + quote lastChunkStart = endIndex } }) // Combine the current result with the tail-end of the input return result + markup.slice(lastChunkStart, markup.length) }