aliaset
Version:
twind monorepo
165 lines (131 loc) • 4.24 kB
text/typescript
import type {
BaseTheme,
ExtractThemes,
Preset,
Twind,
Sheet,
TwindConfig,
TwindRule,
TwindUserConfig,
} from './types'
import type { SortableRule } from './internal/sorted-insertion-index'
import { sortedInsertionIndex } from './internal/sorted-insertion-index'
import { stringify } from './internal/stringify'
import { createContext } from './internal/context'
import { translate, translateWith } from './internal/translate'
import { parse } from './parse'
import { defineConfig } from './define-config'
import { asArray } from './utils'
import { serialize } from './internal/serialize'
import { Layer } from './internal/precedence'
/**
* @group Runtime
* @param config
* @param sheet
*/
export function twind<Theme extends BaseTheme = BaseTheme, Target = unknown>(
config: TwindConfig<Theme>,
sheet: Sheet<Target>,
): Twind<Theme, Target>
export function twind<
Theme = BaseTheme,
Presets extends Preset<any>[] = Preset[],
Target = unknown,
>(
config: TwindUserConfig<Theme, Presets>,
sheet: Sheet<Target>,
): Twind<BaseTheme & ExtractThemes<Theme, Presets>, Target>
export function twind(userConfig: TwindConfig<any> | TwindUserConfig<any>, sheet: Sheet): Twind {
const config = defineConfig(userConfig as TwindUserConfig<any>)
const context = createContext(config)
// Map of tokens to generated className
let cache = new Map<string, string>()
// An array of precedence by index within the sheet
// always sorted
let sortedPrecedences: SortableRule[] = []
// Cache for already inserted css rules
// to prevent double insertions
let insertedRules = new Set<string>()
sheet.resume(
(className) => cache.set(className, className),
(cssText, rule) => {
sheet.insert(cssText, sortedPrecedences.length, rule)
sortedPrecedences.push(rule)
insertedRules.add(cssText)
},
)
function insert(rule: TwindRule): string | undefined {
const name = rule.n && context.h(rule.n)
const cssText = stringify(name ? { ...rule, n: name } : rule)
// If not already inserted
if (cssText && !insertedRules.has(cssText)) {
// Mark rule as inserted
insertedRules.add(cssText)
// Find the correct position
const index = sortedInsertionIndex(sortedPrecedences, rule)
// Insert
sheet.insert(cssText, index, rule)
// Update sorted index
sortedPrecedences.splice(index, 0, rule)
}
return name
}
return Object.defineProperties(
function tw(tokens) {
if (!cache.size) {
for (let preflight of asArray(config.preflight)) {
if (typeof preflight == 'function') {
preflight = preflight(context)
}
if (preflight) {
;(typeof preflight == 'string'
? translateWith('', Layer.b, parse(preflight), context, Layer.b, [], false, true)
: serialize(preflight, {}, context, Layer.b)
).forEach(insert)
}
}
}
tokens = '' + tokens
let className = cache.get(tokens)
if (!className) {
const classNames = new Set<string | undefined>()
for (const rule of translate(parse(tokens), context)) {
classNames.add(rule.c).add(insert(rule))
}
className = [...classNames].filter(Boolean).join(' ')
// Remember the generated class name
cache.set(tokens, className).set(className, className)
}
return className
} as Twind,
Object.getOwnPropertyDescriptors({
get target() {
return sheet.target
},
theme: context.theme,
config,
snapshot() {
const restoreSheet = sheet.snapshot()
const insertedRules$ = new Set(insertedRules)
const cache$ = new Map(cache)
const sortedPrecedences$ = [...sortedPrecedences]
return () => {
restoreSheet()
insertedRules = insertedRules$
cache = cache$
sortedPrecedences = sortedPrecedences$
}
},
clear() {
sheet.clear()
insertedRules = new Set()
cache = new Map()
sortedPrecedences = []
},
destroy() {
this.clear()
sheet.destroy()
},
}),
)
}