aliaset
Version:
twind monorepo
233 lines (214 loc) • 6.37 kB
text/typescript
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<any, any>
/**
* 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('</head>', `<style data-twind>${minify(css, html)}</style></head>`)
}
/**
* 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('</head>', `<style data-twind>${css}</style></head>`)
* }
* ```
*
* 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('</head>', `<style data-twind>${css}</style></head>`)
* }
* ```
*
* @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<any, any> = 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('</head>', `<style data-twind>${css}</style></head>`)
* }
* ```
*
* 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('</head>', `<style data-twind>${css}</style></head>`)
* }
* ```
*
* @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)
}