From 33e2f7d89b753791f83ca027f0876e7c6b17f231 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:31:49 +0100 Subject: [PATCH 1/4] improve type naming and JSDocs --- src/lib/class-group-utils.ts | 28 +++++++++---------- src/lib/config-utils.ts | 4 +-- src/lib/create-tailwind-merge.ts | 8 +++--- src/lib/extend-tailwind-merge.ts | 4 +-- src/lib/merge-configs.ts | 4 +-- src/lib/parse-class-name.ts | 4 +-- src/lib/types.ts | 48 ++++++++++++++++++++++++-------- 7 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/lib/class-group-utils.ts b/src/lib/class-group-utils.ts index 23efc785..94184ae8 100644 --- a/src/lib/class-group-utils.ts +++ b/src/lib/class-group-utils.ts @@ -1,10 +1,10 @@ import { + AnyClassGroupIds, + AnyConfig, + AnyThemeGroupIds, ClassGroup, ClassValidator, Config, - GenericClassGroupIds, - GenericConfig, - GenericThemeGroupIds, ThemeGetter, ThemeObject, } from './types' @@ -12,17 +12,17 @@ import { export interface ClassPartObject { nextPart: Map validators: ClassValidatorObject[] - classGroupId?: GenericClassGroupIds + classGroupId?: AnyClassGroupIds } interface ClassValidatorObject { - classGroupId: GenericClassGroupIds + classGroupId: AnyClassGroupIds validator: ClassValidator } const CLASS_PART_SEPARATOR = '-' -export const createClassGroupUtils = (config: GenericConfig) => { +export const createClassGroupUtils = (config: AnyConfig) => { const classMap = createClassMap(config) const { conflictingClassGroups, conflictingClassGroupModifiers } = config @@ -38,7 +38,7 @@ export const createClassGroupUtils = (config: GenericConfig) => { } const getConflictingClassGroupIds = ( - classGroupId: GenericClassGroupIds, + classGroupId: AnyClassGroupIds, hasPostfixModifier: boolean, ) => { const conflicts = conflictingClassGroups[classGroupId] || [] @@ -59,7 +59,7 @@ export const createClassGroupUtils = (config: GenericConfig) => { const getGroupRecursive = ( classParts: string[], classPartObject: ClassPartObject, -): GenericClassGroupIds | undefined => { +): AnyClassGroupIds | undefined => { if (classParts.length === 0) { return classPartObject.classGroupId } @@ -103,7 +103,7 @@ const getGroupIdForArbitraryProperty = (className: string) => { /** * Exported for testing only */ -export const createClassMap = (config: Config) => { +export const createClassMap = (config: Config) => { const { theme, prefix } = config const classMap: ClassPartObject = { nextPart: new Map(), @@ -123,10 +123,10 @@ export const createClassMap = (config: Config, + classGroup: ClassGroup, classPartObject: ClassPartObject, - classGroupId: GenericClassGroupIds, - theme: ThemeObject, + classGroupId: AnyClassGroupIds, + theme: ThemeObject, ) => { classGroup.forEach((classDefinition) => { if (typeof classDefinition === 'string') { @@ -187,9 +187,9 @@ const isThemeGetter = (func: ClassValidator | ThemeGetter): func is ThemeGetter (func as ThemeGetter).isThemeGetter const getPrefixedClassGroupEntries = ( - classGroupEntries: Array<[classGroupId: string, classGroup: ClassGroup]>, + classGroupEntries: Array<[classGroupId: string, classGroup: ClassGroup]>, prefix: string | undefined, -): Array<[classGroupId: string, classGroup: ClassGroup]> => { +): Array<[classGroupId: string, classGroup: ClassGroup]> => { if (!prefix) { return classGroupEntries } diff --git a/src/lib/config-utils.ts b/src/lib/config-utils.ts index 1eb9fa01..a31c0923 100644 --- a/src/lib/config-utils.ts +++ b/src/lib/config-utils.ts @@ -1,11 +1,11 @@ import { createClassGroupUtils } from './class-group-utils' import { createLruCache } from './lru-cache' import { createParseClassName } from './parse-class-name' -import { GenericConfig } from './types' +import { AnyConfig } from './types' export type ConfigUtils = ReturnType -export const createConfigUtils = (config: GenericConfig) => ({ +export const createConfigUtils = (config: AnyConfig) => ({ cache: createLruCache(config.cacheSize), parseClassName: createParseClassName(config), ...createClassGroupUtils(config), diff --git a/src/lib/create-tailwind-merge.ts b/src/lib/create-tailwind-merge.ts index 2acab958..59b95387 100644 --- a/src/lib/create-tailwind-merge.ts +++ b/src/lib/create-tailwind-merge.ts @@ -1,10 +1,10 @@ import { createConfigUtils } from './config-utils' import { mergeClassList } from './merge-classlist' import { ClassNameValue, twJoin } from './tw-join' -import { GenericConfig } from './types' +import { AnyConfig } from './types' -type CreateConfigFirst = () => GenericConfig -type CreateConfigSubsequent = (config: GenericConfig) => GenericConfig +type CreateConfigFirst = () => AnyConfig +type CreateConfigSubsequent = (config: AnyConfig) => AnyConfig type TailwindMerge = (...classLists: ClassNameValue[]) => string type ConfigUtils = ReturnType @@ -20,7 +20,7 @@ export function createTailwindMerge( function initTailwindMerge(classList: string) { const config = createConfigRest.reduce( (previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig), - createConfigFirst() as GenericConfig, + createConfigFirst() as AnyConfig, ) configUtils = createConfigUtils(config) diff --git a/src/lib/extend-tailwind-merge.ts b/src/lib/extend-tailwind-merge.ts index dd12f952..6d06891a 100644 --- a/src/lib/extend-tailwind-merge.ts +++ b/src/lib/extend-tailwind-merge.ts @@ -1,9 +1,9 @@ import { createTailwindMerge } from './create-tailwind-merge' import { getDefaultConfig } from './default-config' import { mergeConfigs } from './merge-configs' -import { ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds, GenericConfig } from './types' +import { AnyConfig, ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds } from './types' -type CreateConfigSubsequent = (config: GenericConfig) => GenericConfig +type CreateConfigSubsequent = (config: AnyConfig) => AnyConfig export const extendTailwindMerge = < AdditionalClassGroupIds extends string = never, diff --git a/src/lib/merge-configs.ts b/src/lib/merge-configs.ts index f0dda498..dd28c889 100644 --- a/src/lib/merge-configs.ts +++ b/src/lib/merge-configs.ts @@ -1,11 +1,11 @@ -import { ConfigExtension, GenericConfig } from './types' +import { AnyConfig, ConfigExtension } from './types' /** * @param baseConfig Config where other config will be merged into. This object will be mutated. * @param configExtension Partial config to merge into the `baseConfig`. */ export const mergeConfigs = ( - baseConfig: GenericConfig, + baseConfig: AnyConfig, { cacheSize, prefix, diff --git a/src/lib/parse-class-name.ts b/src/lib/parse-class-name.ts index 049bb31c..9fd428ea 100644 --- a/src/lib/parse-class-name.ts +++ b/src/lib/parse-class-name.ts @@ -1,8 +1,8 @@ -import { GenericConfig } from './types' +import { AnyConfig } from './types' export const IMPORTANT_MODIFIER = '!' -export const createParseClassName = (config: GenericConfig) => { +export const createParseClassName = (config: AnyConfig) => { const { separator, experimentalParseClassName } = config const isSeparatorSingleCharacter = separator.length === 1 const firstSeparatorCharacter = separator[0] diff --git a/src/lib/types.ts b/src/lib/types.ts index 171191c5..2fc5e420 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,8 +1,14 @@ +/** + * Type the tailwind-merge configuration adheres to. + */ export interface Config - extends ConfigStatic, - ConfigGroups {} + extends ConfigStaticPart, + ConfigGroupsPart {} -interface ConfigStatic { +/** + * The static part of the tailwind-merge configuration. When merging multiple configurations, the properties of this interface are always overridden. + */ +interface ConfigStaticPart { /** * Integer indicating size of LRU cache used for memoizing results. * - Cache might be up to twice as big as `cacheSize` @@ -75,7 +81,10 @@ export interface ExperimentalParsedClassName { maybePostfixModifierPosition: number | undefined } -interface ConfigGroups { +/** + * The dynamic part of the tailwind-merge configuration. When merging multiple configurations, the user can choose to either override or extend the properties of this interface. + */ +interface ConfigGroupsPart { /** * Theme scales used in classGroups. * The keys are the same as in the Tailwind config but the values are sometimes defined more broadly. @@ -109,10 +118,13 @@ interface ConfigGroups } +/** + * Type of the configuration object that can be passed to `extendTailwindMerge`. + */ export interface ConfigExtension - extends Partial { - override?: PartialPartial> - extend?: PartialPartial> + extends Partial { + override?: PartialPartial> + extend?: PartialPartial> } type PartialPartial = { @@ -131,7 +143,7 @@ type ClassDefinition = | ClassObject export type ClassValidator = (classPart: string) => boolean export interface ThemeGetter { - (theme: ThemeObject): ClassGroup + (theme: ThemeObject): ClassGroup isThemeGetter: true } type ClassObject = Record< @@ -139,10 +151,16 @@ type ClassObject = Record< readonly ClassDefinition[] > -// Hack from https://stackoverflow.com/questions/56687668/a-way-to-disable-type-argument-inference-in-generics/56688073#56688073 +/** + * Hack from https://stackoverflow.com/questions/56687668/a-way-to-disable-type-argument-inference-in-generics/56688073#56688073 + * + * Could be replaced with NoInfer utility type from TypeScript (https://www.typescriptlang.org/docs/handbook/utility-types.html#noinfertype), but that is only supported in TypeScript 5.4 or higher, so I should wait some time before using it. + */ export type NoInfer = [T][T extends any ? 0 : never] /** + * Theme group IDs included in the default configuration of tailwind-merge. + * * If you want to use a scale that is not supported in the `ThemeObject` type, * consider using `classGroups` instead of `theme`. * @@ -176,6 +194,9 @@ export type DefaultThemeGroupIds = | 'spacing' | 'translate' +/** + * Class group IDs included in the default configuration of tailwind-merge. + */ export type DefaultClassGroupIds = | 'accent' | 'align-content' @@ -458,7 +479,10 @@ export type DefaultClassGroupIds = | 'will-change' | 'z' -export type GenericClassGroupIds = string -export type GenericThemeGroupIds = string +export type AnyClassGroupIds = string +export type AnyThemeGroupIds = string -export type GenericConfig = Config +/** + * type of the tailwind-merge configuration that allows for any possible configuration. + */ +export type AnyConfig = Config From 70b258c0fd68cceecadfe4cbdd5e3229c330c252 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:34:59 +0100 Subject: [PATCH 2/4] add ConfigExtension to types exported from package --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index b9873ab5..688d9504 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { twMerge } from './lib/tw-merge' export { type ClassValidator, type Config, + type ConfigExtension, type DefaultClassGroupIds, type DefaultThemeGroupIds, type ExperimentalParseClassNameParam, From 3be292bc23910239c8a5bce775da96a858f61913 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:39:46 +0100 Subject: [PATCH 3/4] fix test --- tests/type-generics.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/type-generics.test.ts b/tests/type-generics.test.ts index 0b820a7b..f2da063d 100644 --- a/tests/type-generics.test.ts +++ b/tests/type-generics.test.ts @@ -1,7 +1,7 @@ import { expect, test } from 'vitest' import { extendTailwindMerge, fromTheme, getDefaultConfig, mergeConfigs } from '../src' -import { GenericConfig } from '../src/lib/types' +import { AnyConfig } from '../src/lib/types' test('extendTailwindMerge type generics work correctly', () => { const tailwindMerge1 = extendTailwindMerge({ @@ -69,7 +69,7 @@ test('extendTailwindMerge type generics work correctly', () => { expect(tailwindMerge2('')).toBe('') - const tailwindMerge3 = extendTailwindMerge((v: GenericConfig) => v, getDefaultConfig) + const tailwindMerge3 = extendTailwindMerge((v: AnyConfig) => v, getDefaultConfig) expect(tailwindMerge3('')).toBe('') }) From b450bbd32abd814ffb3623af9fb984dfcbfaf7a1 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:40:01 +0100 Subject: [PATCH 4/4] add ConfigExtension type to public API test suite --- tests/public-api.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/public-api.test.ts b/tests/public-api.test.ts index 91f1fda6..06fa4200 100644 --- a/tests/public-api.test.ts +++ b/tests/public-api.test.ts @@ -4,6 +4,7 @@ import { ClassNameValue, ClassValidator, Config, + ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds, createTailwindMerge, @@ -183,7 +184,9 @@ test('mergeConfigs has correct inputs and outputs', () => { }) test('extendTailwindMerge has correct inputs and outputs', () => { - expect(extendTailwindMerge({})).toStrictEqual(expect.any(Function)) + expect(extendTailwindMerge({} satisfies ConfigExtension)).toStrictEqual( + expect.any(Function), + ) }) test('fromTheme has correct inputs and outputs', () => {