import type { Config } from 'tailwind-merge'; import { readFileSync, writeFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { dirname, resolve } from 'node:path'; import { getDefaultConfig, validators } from 'tailwind-merge'; type ClassGroup = Config['classGroups'][string]; const config = getDefaultConfig(); const groupNames = Object.keys(config.classGroups); const validatorNames = Object.keys(validators).sort() as Array; const groupIds = new Map(groupNames.map((name, index) => [name, index + 1])); interface IRule { literals: string[]; validators: number; } function visit(definitions: ClassGroup, prefix: string, rules: Map) { const rule = rules.get(prefix) ?? { literals: [], validators: 0 }; rules.set(prefix, rule); for (const definition of definitions) { if (typeof definition === 'string') { rule.literals.push(definition); } else if (typeof definition === 'function') { if ('isThemeGetter' in definition) { visit(definition(config.theme), prefix, rules); continue; } const validator = validatorNames.findIndex((name) => validators[name] === definition); if (validator === -1) { throw new Error(`Unknown Tailwind v3 validator: ${definition.name}`); } rule.validators |= 1 << validator; } else { for (const [part, children] of Object.entries(definition)) { visit(children, prefix + (prefix ? '-' : '') + part, rules); } } } } const classGroups = Object.values(config.classGroups).map((definitions) => { const rules = new Map(); visit(definitions, '', rules); return Array.from(rules) .filter(([, rule]) => rule.literals.length || rule.validators) .map(([prefix, rule]) => `['${prefix}', ${rule.literals.length ? `'${rule.literals.join(' ')}'` : 'null'}, ${rule.validators}]`); }); function serializeConflicts(conflicts: Record) { return Object.entries(conflicts).map(([name, targets]) => ` ${groupIds.get(name)}: [${targets!.map((target) => groupIds.get(target)).join(', ')}],` ).join('\n'); } const require = createRequire(import.meta.url); const packageDir = resolve(dirname(require.resolve('tailwind-merge')), '..'); const license = readFileSync(resolve(packageDir, 'LICENSE.md'), 'utf8').trim(); const header = readFileSync(new URL('../src/helper/clsx.ts', import.meta.url), 'utf8').split(/\r?\n\r?\n/, 1)[0]; const output = `${header} // Generated by pnpm --filter @univerjs/design prepare. Do not edit. // Complete tailwind-merge 2.6.0 / Tailwind CSS 3.4 rules; the runtime supplies the univer- prefix. /*! ${license.split('\n').map((line) => ` *${line ? ` ${line}` : ''}`).join('\n')} */ export const VALIDATOR_NAMES = [${validatorNames.map((name) => `'${name}'`).join(', ')}] as const; // Group IDs are one-based. Each rule contains a prefix, literal suffixes and a validator bitmask. export const CLASS_GROUPS: ReadonlyArray> = [ ${classGroups.map((rules) => ` [${rules.join(', ')}],`).join('\n')} ]; export const CLASS_CONFLICTS: Record = { ${serializeConflicts(config.conflictingClassGroups)} }; export const POSTFIX_CONFLICTS: Record = { ${serializeConflicts(config.conflictingClassGroupModifiers)} }; `; writeFileSync(new URL('../src/helper/clsx.generated.ts', import.meta.url), output);