tailwind-merge 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <div align="center">
4
4
  <br />
5
5
  <a href="https://github.com/dcastil/tailwind-merge">
6
- <img src="https://github.com/dcastil/tailwind-merge/raw/v3.3.1/assets/logo.svg" alt="tailwind-merge" height="150px" />
6
+ <img src="https://github.com/dcastil/tailwind-merge/raw/v3.4.0/assets/logo.svg" alt="tailwind-merge" height="150px" />
7
7
  </a>
8
8
  </div>
9
9
 
@@ -25,14 +25,14 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
25
25
 
26
26
  ## Get started
27
27
 
28
- - [What is it for](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/what-is-it-for.md)
29
- - [When and how to use it](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/when-and-how-to-use-it.md)
30
- - [Features](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/features.md)
31
- - [Limitations](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/limitations.md)
32
- - [Configuration](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/configuration.md)
33
- - [Recipes](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/recipes.md)
34
- - [API reference](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/api-reference.md)
35
- - [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/writing-plugins.md)
36
- - [Versioning](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/versioning.md)
37
- - [Contributing](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/contributing.md)
38
- - [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v3.3.1/docs/similar-packages.md)
28
+ - [What is it for](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/what-is-it-for.md)
29
+ - [When and how to use it](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/when-and-how-to-use-it.md)
30
+ - [Features](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/features.md)
31
+ - [Limitations](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/limitations.md)
32
+ - [Configuration](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/configuration.md)
33
+ - [Recipes](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/recipes.md)
34
+ - [API reference](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/api-reference.md)
35
+ - [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/writing-plugins.md)
36
+ - [Versioning](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/versioning.md)
37
+ - [Contributing](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/contributing.md)
38
+ - [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v3.4.0/docs/similar-packages.md)
@@ -3,7 +3,37 @@
3
3
  Object.defineProperty(exports, Symbol.toStringTag, {
4
4
  value: 'Module'
5
5
  });
6
+
7
+ /**
8
+ * Concatenates two arrays faster than the array spread operator.
9
+ */
10
+ const concatArrays = (array1, array2) => {
11
+ // Pre-allocate for better V8 optimization
12
+ const combinedArray = new Array(array1.length + array2.length);
13
+ for (let i = 0; i < array1.length; i++) {
14
+ combinedArray[i] = array1[i];
15
+ }
16
+ for (let i = 0; i < array2.length; i++) {
17
+ combinedArray[array1.length + i] = array2[i];
18
+ }
19
+ return combinedArray;
20
+ };
21
+
22
+ // Factory function ensures consistent object shapes
23
+ const createClassValidatorObject = (classGroupId, validator) => ({
24
+ classGroupId,
25
+ validator
26
+ });
27
+ // Factory ensures consistent ClassPartObject shape
28
+ const createClassPartObject = (nextPart = new Map(), validators = null, classGroupId) => ({
29
+ nextPart,
30
+ validators,
31
+ classGroupId
32
+ });
6
33
  const CLASS_PART_SEPARATOR = '-';
34
+ const EMPTY_CONFLICTS = [];
35
+ // I use two dots here because one dot is used as prefix for class groups in plugins
36
+ const ARBITRARY_PROPERTY_PREFIX = 'arbitrary..';
7
37
  const createClassGroupUtils = config => {
8
38
  const classMap = createClassMap(config);
9
39
  const {
@@ -11,54 +41,73 @@ const createClassGroupUtils = config => {
11
41
  conflictingClassGroupModifiers
12
42
  } = config;
13
43
  const getClassGroupId = className => {
14
- const classParts = className.split(CLASS_PART_SEPARATOR);
15
- // Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and remove it from classParts.
16
- if (classParts[0] === '' && classParts.length !== 1) {
17
- classParts.shift();
44
+ if (className.startsWith('[') && className.endsWith(']')) {
45
+ return getGroupIdForArbitraryProperty(className);
18
46
  }
19
- return getGroupRecursive(classParts, classMap) || getGroupIdForArbitraryProperty(className);
47
+ const classParts = className.split(CLASS_PART_SEPARATOR);
48
+ // Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and skip it.
49
+ const startIndex = classParts[0] === '' && classParts.length > 1 ? 1 : 0;
50
+ return getGroupRecursive(classParts, startIndex, classMap);
20
51
  };
21
52
  const getConflictingClassGroupIds = (classGroupId, hasPostfixModifier) => {
22
- const conflicts = conflictingClassGroups[classGroupId] || [];
23
- if (hasPostfixModifier && conflictingClassGroupModifiers[classGroupId]) {
24
- return [...conflicts, ...conflictingClassGroupModifiers[classGroupId]];
53
+ if (hasPostfixModifier) {
54
+ const modifierConflicts = conflictingClassGroupModifiers[classGroupId];
55
+ const baseConflicts = conflictingClassGroups[classGroupId];
56
+ if (modifierConflicts) {
57
+ if (baseConflicts) {
58
+ // Merge base conflicts with modifier conflicts
59
+ return concatArrays(baseConflicts, modifierConflicts);
60
+ }
61
+ // Only modifier conflicts
62
+ return modifierConflicts;
63
+ }
64
+ // Fall back to without postfix if no modifier conflicts
65
+ return baseConflicts || EMPTY_CONFLICTS;
25
66
  }
26
- return conflicts;
67
+ return conflictingClassGroups[classGroupId] || EMPTY_CONFLICTS;
27
68
  };
28
69
  return {
29
70
  getClassGroupId,
30
71
  getConflictingClassGroupIds
31
72
  };
32
73
  };
33
- const getGroupRecursive = (classParts, classPartObject) => {
34
- if (classParts.length === 0) {
74
+ const getGroupRecursive = (classParts, startIndex, classPartObject) => {
75
+ const classPathsLength = classParts.length - startIndex;
76
+ if (classPathsLength === 0) {
35
77
  return classPartObject.classGroupId;
36
78
  }
37
- const currentClassPart = classParts[0];
79
+ const currentClassPart = classParts[startIndex];
38
80
  const nextClassPartObject = classPartObject.nextPart.get(currentClassPart);
39
- const classGroupFromNextClassPart = nextClassPartObject ? getGroupRecursive(classParts.slice(1), nextClassPartObject) : undefined;
40
- if (classGroupFromNextClassPart) {
41
- return classGroupFromNextClassPart;
81
+ if (nextClassPartObject) {
82
+ const result = getGroupRecursive(classParts, startIndex + 1, nextClassPartObject);
83
+ if (result) return result;
42
84
  }
43
- if (classPartObject.validators.length === 0) {
85
+ const validators = classPartObject.validators;
86
+ if (validators === null) {
44
87
  return undefined;
45
88
  }
46
- const classRest = classParts.join(CLASS_PART_SEPARATOR);
47
- return classPartObject.validators.find(({
48
- validator
49
- }) => validator(classRest))?.classGroupId;
50
- };
51
- const arbitraryPropertyRegex = /^\[(.+)\]$/;
52
- const getGroupIdForArbitraryProperty = className => {
53
- if (arbitraryPropertyRegex.test(className)) {
54
- const arbitraryPropertyClassName = arbitraryPropertyRegex.exec(className)[1];
55
- const property = arbitraryPropertyClassName?.substring(0, arbitraryPropertyClassName.indexOf(':'));
56
- if (property) {
57
- // I use two dots here because one dot is used as prefix for class groups in plugins
58
- return 'arbitrary..' + property;
89
+ // Build classRest string efficiently by joining from startIndex onwards
90
+ const classRest = startIndex === 0 ? classParts.join(CLASS_PART_SEPARATOR) : classParts.slice(startIndex).join(CLASS_PART_SEPARATOR);
91
+ const validatorsLength = validators.length;
92
+ for (let i = 0; i < validatorsLength; i++) {
93
+ const validatorObj = validators[i];
94
+ if (validatorObj.validator(classRest)) {
95
+ return validatorObj.classGroupId;
59
96
  }
60
97
  }
98
+ return undefined;
61
99
  };
100
+ /**
101
+ * Get the class group ID for an arbitrary property.
102
+ *
103
+ * @param className - The class name to get the group ID for. Is expected to be string starting with `[` and ending with `]`.
104
+ */
105
+ const getGroupIdForArbitraryProperty = className => className.slice(1, -1).indexOf(':') === -1 ? undefined : (() => {
106
+ const content = className.slice(1, -1);
107
+ const colonIndex = content.indexOf(':');
108
+ const property = content.slice(0, colonIndex);
109
+ return property ? ARBITRARY_PROPERTY_PREFIX + property : undefined;
110
+ })();
62
111
  /**
63
112
  * Exported for testing only
64
113
  */
@@ -67,54 +116,77 @@ const createClassMap = config => {
67
116
  theme,
68
117
  classGroups
69
118
  } = config;
70
- const classMap = {
71
- nextPart: new Map(),
72
- validators: []
73
- };
119
+ return processClassGroups(classGroups, theme);
120
+ };
121
+ // Split into separate functions to maintain monomorphic call sites
122
+ const processClassGroups = (classGroups, theme) => {
123
+ const classMap = createClassPartObject();
74
124
  for (const classGroupId in classGroups) {
75
- processClassesRecursively(classGroups[classGroupId], classMap, classGroupId, theme);
125
+ const group = classGroups[classGroupId];
126
+ processClassesRecursively(group, classMap, classGroupId, theme);
76
127
  }
77
128
  return classMap;
78
129
  };
79
130
  const processClassesRecursively = (classGroup, classPartObject, classGroupId, theme) => {
80
- classGroup.forEach(classDefinition => {
81
- if (typeof classDefinition === 'string') {
82
- const classPartObjectToEdit = classDefinition === '' ? classPartObject : getPart(classPartObject, classDefinition);
83
- classPartObjectToEdit.classGroupId = classGroupId;
84
- return;
85
- }
86
- if (typeof classDefinition === 'function') {
87
- if (isThemeGetter(classDefinition)) {
88
- processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme);
89
- return;
90
- }
91
- classPartObject.validators.push({
92
- validator: classDefinition,
93
- classGroupId
94
- });
95
- return;
96
- }
97
- Object.entries(classDefinition).forEach(([key, classGroup]) => {
98
- processClassesRecursively(classGroup, getPart(classPartObject, key), classGroupId, theme);
99
- });
100
- });
131
+ const len = classGroup.length;
132
+ for (let i = 0; i < len; i++) {
133
+ const classDefinition = classGroup[i];
134
+ processClassDefinition(classDefinition, classPartObject, classGroupId, theme);
135
+ }
136
+ };
137
+ // Split into separate functions for each type to maintain monomorphic call sites
138
+ const processClassDefinition = (classDefinition, classPartObject, classGroupId, theme) => {
139
+ if (typeof classDefinition === 'string') {
140
+ processStringDefinition(classDefinition, classPartObject, classGroupId);
141
+ return;
142
+ }
143
+ if (typeof classDefinition === 'function') {
144
+ processFunctionDefinition(classDefinition, classPartObject, classGroupId, theme);
145
+ return;
146
+ }
147
+ processObjectDefinition(classDefinition, classPartObject, classGroupId, theme);
148
+ };
149
+ const processStringDefinition = (classDefinition, classPartObject, classGroupId) => {
150
+ const classPartObjectToEdit = classDefinition === '' ? classPartObject : getPart(classPartObject, classDefinition);
151
+ classPartObjectToEdit.classGroupId = classGroupId;
152
+ };
153
+ const processFunctionDefinition = (classDefinition, classPartObject, classGroupId, theme) => {
154
+ if (isThemeGetter(classDefinition)) {
155
+ processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme);
156
+ return;
157
+ }
158
+ if (classPartObject.validators === null) {
159
+ classPartObject.validators = [];
160
+ }
161
+ classPartObject.validators.push(createClassValidatorObject(classGroupId, classDefinition));
162
+ };
163
+ const processObjectDefinition = (classDefinition, classPartObject, classGroupId, theme) => {
164
+ const entries = Object.entries(classDefinition);
165
+ const len = entries.length;
166
+ for (let i = 0; i < len; i++) {
167
+ const [key, value] = entries[i];
168
+ processClassesRecursively(value, getPart(classPartObject, key), classGroupId, theme);
169
+ }
101
170
  };
102
171
  const getPart = (classPartObject, path) => {
103
- let currentClassPartObject = classPartObject;
104
- path.split(CLASS_PART_SEPARATOR).forEach(pathPart => {
105
- if (!currentClassPartObject.nextPart.has(pathPart)) {
106
- currentClassPartObject.nextPart.set(pathPart, {
107
- nextPart: new Map(),
108
- validators: []
109
- });
172
+ let current = classPartObject;
173
+ const parts = path.split(CLASS_PART_SEPARATOR);
174
+ const len = parts.length;
175
+ for (let i = 0; i < len; i++) {
176
+ const part = parts[i];
177
+ let next = current.nextPart.get(part);
178
+ if (!next) {
179
+ next = createClassPartObject();
180
+ current.nextPart.set(part, next);
110
181
  }
111
- currentClassPartObject = currentClassPartObject.nextPart.get(pathPart);
112
- });
113
- return currentClassPartObject;
182
+ current = next;
183
+ }
184
+ return current;
114
185
  };
115
- const isThemeGetter = func => func.isThemeGetter;
186
+ // Type guard maintains monomorphic check
187
+ const isThemeGetter = func => 'isThemeGetter' in func && func.isThemeGetter === true;
116
188
 
117
- // LRU cache inspired from hashlru (https://github.com/dominictarr/hashlru/blob/v1.0.4/index.js) but object replaced with Map to improve performance
189
+ // LRU cache implementation using plain objects for simplicity
118
190
  const createLruCache = maxCacheSize => {
119
191
  if (maxCacheSize < 1) {
120
192
  return {
@@ -123,31 +195,31 @@ const createLruCache = maxCacheSize => {
123
195
  };
124
196
  }
125
197
  let cacheSize = 0;
126
- let cache = new Map();
127
- let previousCache = new Map();
198
+ let cache = Object.create(null);
199
+ let previousCache = Object.create(null);
128
200
  const update = (key, value) => {
129
- cache.set(key, value);
201
+ cache[key] = value;
130
202
  cacheSize++;
131
203
  if (cacheSize > maxCacheSize) {
132
204
  cacheSize = 0;
133
205
  previousCache = cache;
134
- cache = new Map();
206
+ cache = Object.create(null);
135
207
  }
136
208
  };
137
209
  return {
138
210
  get(key) {
139
- let value = cache.get(key);
211
+ let value = cache[key];
140
212
  if (value !== undefined) {
141
213
  return value;
142
214
  }
143
- if ((value = previousCache.get(key)) !== undefined) {
215
+ if ((value = previousCache[key]) !== undefined) {
144
216
  update(key, value);
145
217
  return value;
146
218
  }
147
219
  },
148
220
  set(key, value) {
149
- if (cache.has(key)) {
150
- cache.set(key, value);
221
+ if (key in cache) {
222
+ cache[key] = value;
151
223
  } else {
152
224
  update(key, value);
153
225
  }
@@ -156,7 +228,15 @@ const createLruCache = maxCacheSize => {
156
228
  };
157
229
  const IMPORTANT_MODIFIER = '!';
158
230
  const MODIFIER_SEPARATOR = ':';
159
- const MODIFIER_SEPARATOR_LENGTH = MODIFIER_SEPARATOR.length;
231
+ const EMPTY_MODIFIERS = [];
232
+ // Pre-allocated result object shape for consistency
233
+ const createResultObject = (modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition, isExternal) => ({
234
+ modifiers,
235
+ hasImportantModifier,
236
+ baseClassName,
237
+ maybePostfixModifierPosition,
238
+ isExternal
239
+ });
160
240
  const createParseClassName = config => {
161
241
  const {
162
242
  prefix,
@@ -169,17 +249,19 @@ const createParseClassName = config => {
169
249
  * @see https://github.com/tailwindlabs/tailwindcss/blob/v3.2.2/src/util/splitAtTopLevelOnly.js
170
250
  */
171
251
  let parseClassName = className => {
252
+ // Use simple array with push for better performance
172
253
  const modifiers = [];
173
254
  let bracketDepth = 0;
174
255
  let parenDepth = 0;
175
256
  let modifierStart = 0;
176
257
  let postfixModifierPosition;
177
- for (let index = 0; index < className.length; index++) {
178
- let currentCharacter = className[index];
258
+ const len = className.length;
259
+ for (let index = 0; index < len; index++) {
260
+ const currentCharacter = className[index];
179
261
  if (bracketDepth === 0 && parenDepth === 0) {
180
262
  if (currentCharacter === MODIFIER_SEPARATOR) {
181
263
  modifiers.push(className.slice(modifierStart, index));
182
- modifierStart = index + MODIFIER_SEPARATOR_LENGTH;
264
+ modifierStart = index + 1;
183
265
  continue;
184
266
  }
185
267
  if (currentCharacter === '/') {
@@ -187,37 +269,31 @@ const createParseClassName = config => {
187
269
  continue;
188
270
  }
189
271
  }
190
- if (currentCharacter === '[') {
191
- bracketDepth++;
192
- } else if (currentCharacter === ']') {
193
- bracketDepth--;
194
- } else if (currentCharacter === '(') {
195
- parenDepth++;
196
- } else if (currentCharacter === ')') {
197
- parenDepth--;
198
- }
272
+ if (currentCharacter === '[') bracketDepth++;else if (currentCharacter === ']') bracketDepth--;else if (currentCharacter === '(') parenDepth++;else if (currentCharacter === ')') parenDepth--;
273
+ }
274
+ const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.slice(modifierStart);
275
+ // Inline important modifier check
276
+ let baseClassName = baseClassNameWithImportantModifier;
277
+ let hasImportantModifier = false;
278
+ if (baseClassNameWithImportantModifier.endsWith(IMPORTANT_MODIFIER)) {
279
+ baseClassName = baseClassNameWithImportantModifier.slice(0, -1);
280
+ hasImportantModifier = true;
281
+ } else if (
282
+ /**
283
+ * In Tailwind CSS v3 the important modifier was at the start of the base class name. This is still supported for legacy reasons.
284
+ * @see https://github.com/dcastil/tailwind-merge/issues/513#issuecomment-2614029864
285
+ */
286
+ baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER)) {
287
+ baseClassName = baseClassNameWithImportantModifier.slice(1);
288
+ hasImportantModifier = true;
199
289
  }
200
- const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.substring(modifierStart);
201
- const baseClassName = stripImportantModifier(baseClassNameWithImportantModifier);
202
- const hasImportantModifier = baseClassName !== baseClassNameWithImportantModifier;
203
290
  const maybePostfixModifierPosition = postfixModifierPosition && postfixModifierPosition > modifierStart ? postfixModifierPosition - modifierStart : undefined;
204
- return {
205
- modifiers,
206
- hasImportantModifier,
207
- baseClassName,
208
- maybePostfixModifierPosition
209
- };
291
+ return createResultObject(modifiers, hasImportantModifier, baseClassName, maybePostfixModifierPosition);
210
292
  };
211
293
  if (prefix) {
212
294
  const fullPrefix = prefix + MODIFIER_SEPARATOR;
213
295
  const parseClassNameOriginal = parseClassName;
214
- parseClassName = className => className.startsWith(fullPrefix) ? parseClassNameOriginal(className.substring(fullPrefix.length)) : {
215
- isExternal: true,
216
- modifiers: [],
217
- hasImportantModifier: false,
218
- baseClassName: className,
219
- maybePostfixModifierPosition: undefined
220
- };
296
+ parseClassName = className => className.startsWith(fullPrefix) ? parseClassNameOriginal(className.slice(fullPrefix.length)) : createResultObject(EMPTY_MODIFIERS, false, className, undefined, true);
221
297
  }
222
298
  if (experimentalParseClassName) {
223
299
  const parseClassNameOriginal = parseClassName;
@@ -228,19 +304,6 @@ const createParseClassName = config => {
228
304
  }
229
305
  return parseClassName;
230
306
  };
231
- const stripImportantModifier = baseClassName => {
232
- if (baseClassName.endsWith(IMPORTANT_MODIFIER)) {
233
- return baseClassName.substring(0, baseClassName.length - 1);
234
- }
235
- /**
236
- * In Tailwind CSS v3 the important modifier was at the start of the base class name. This is still supported for legacy reasons.
237
- * @see https://github.com/dcastil/tailwind-merge/issues/513#issuecomment-2614029864
238
- */
239
- if (baseClassName.startsWith(IMPORTANT_MODIFIER)) {
240
- return baseClassName.substring(1);
241
- }
242
- return baseClassName;
243
- };
244
307
 
245
308
  /**
246
309
  * Sorts modifiers according to following schema:
@@ -248,26 +311,41 @@ const stripImportantModifier = baseClassName => {
248
311
  * - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
249
312
  */
250
313
  const createSortModifiers = config => {
251
- const orderSensitiveModifiers = Object.fromEntries(config.orderSensitiveModifiers.map(modifier => [modifier, true]));
252
- const sortModifiers = modifiers => {
253
- if (modifiers.length <= 1) {
254
- return modifiers;
255
- }
256
- const sortedModifiers = [];
257
- let unsortedModifiers = [];
258
- modifiers.forEach(modifier => {
259
- const isPositionSensitive = modifier[0] === '[' || orderSensitiveModifiers[modifier];
260
- if (isPositionSensitive) {
261
- sortedModifiers.push(...unsortedModifiers.sort(), modifier);
262
- unsortedModifiers = [];
314
+ // Pre-compute weights for all known modifiers for O(1) comparison
315
+ const modifierWeights = new Map();
316
+ // Assign weights to sensitive modifiers (highest priority, but preserve order)
317
+ config.orderSensitiveModifiers.forEach((mod, index) => {
318
+ modifierWeights.set(mod, 1000000 + index); // High weights for sensitive mods
319
+ });
320
+ return modifiers => {
321
+ const result = [];
322
+ let currentSegment = [];
323
+ // Process modifiers in one pass
324
+ for (let i = 0; i < modifiers.length; i++) {
325
+ const modifier = modifiers[i];
326
+ // Check if modifier is sensitive (starts with '[' or in orderSensitiveModifiers)
327
+ const isArbitrary = modifier[0] === '[';
328
+ const isOrderSensitive = modifierWeights.has(modifier);
329
+ if (isArbitrary || isOrderSensitive) {
330
+ // Sort and flush current segment alphabetically
331
+ if (currentSegment.length > 0) {
332
+ currentSegment.sort();
333
+ result.push(...currentSegment);
334
+ currentSegment = [];
335
+ }
336
+ result.push(modifier);
263
337
  } else {
264
- unsortedModifiers.push(modifier);
338
+ // Regular modifier - add to current segment for batch sorting
339
+ currentSegment.push(modifier);
265
340
  }
266
- });
267
- sortedModifiers.push(...unsortedModifiers.sort());
268
- return sortedModifiers;
341
+ }
342
+ // Sort and add any remaining segment items
343
+ if (currentSegment.length > 0) {
344
+ currentSegment.sort();
345
+ result.push(...currentSegment);
346
+ }
347
+ return result;
269
348
  };
270
- return sortModifiers;
271
349
  };
272
350
  const createConfigUtils = config => ({
273
351
  cache: createLruCache(config.cacheSize),
@@ -322,10 +400,11 @@ const mergeClassList = (classList, configUtils) => {
322
400
  }
323
401
  hasPostfixModifier = false;
324
402
  }
325
- const variantModifier = sortModifiers(modifiers).join(':');
403
+ // Fast path: skip sorting for empty or single modifier
404
+ const variantModifier = modifiers.length === 0 ? '' : modifiers.length === 1 ? modifiers[0] : sortModifiers(modifiers).join(':');
326
405
  const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier;
327
406
  const classId = modifierId + classGroupId;
328
- if (classGroupsInConflict.includes(classId)) {
407
+ if (classGroupsInConflict.indexOf(classId) > -1) {
329
408
  // Tailwind class omitted due to conflict
330
409
  continue;
331
410
  }
@@ -350,13 +429,13 @@ const mergeClassList = (classList, configUtils) => {
350
429
  *
351
430
  * Original code has MIT license: Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
352
431
  */
353
- function twJoin() {
432
+ const twJoin = (...classLists) => {
354
433
  let index = 0;
355
434
  let argument;
356
435
  let resolvedValue;
357
436
  let string = '';
358
- while (index < arguments.length) {
359
- if (argument = arguments[index++]) {
437
+ while (index < classLists.length) {
438
+ if (argument = classLists[index++]) {
360
439
  if (resolvedValue = toValue(argument)) {
361
440
  string && (string += ' ');
362
441
  string += resolvedValue;
@@ -364,8 +443,9 @@ function twJoin() {
364
443
  }
365
444
  }
366
445
  return string;
367
- }
446
+ };
368
447
  const toValue = mix => {
448
+ // Fast path for strings
369
449
  if (typeof mix === 'string') {
370
450
  return mix;
371
451
  }
@@ -381,20 +461,20 @@ const toValue = mix => {
381
461
  }
382
462
  return string;
383
463
  };
384
- function createTailwindMerge(createConfigFirst, ...createConfigRest) {
464
+ const createTailwindMerge = (createConfigFirst, ...createConfigRest) => {
385
465
  let configUtils;
386
466
  let cacheGet;
387
467
  let cacheSet;
388
- let functionToCall = initTailwindMerge;
389
- function initTailwindMerge(classList) {
468
+ let functionToCall;
469
+ const initTailwindMerge = classList => {
390
470
  const config = createConfigRest.reduce((previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig), createConfigFirst());
391
471
  configUtils = createConfigUtils(config);
392
472
  cacheGet = configUtils.cache.get;
393
473
  cacheSet = configUtils.cache.set;
394
474
  functionToCall = tailwindMerge;
395
475
  return tailwindMerge(classList);
396
- }
397
- function tailwindMerge(classList) {
476
+ };
477
+ const tailwindMerge = classList => {
398
478
  const cachedResult = cacheGet(classList);
399
479
  if (cachedResult) {
400
480
  return cachedResult;
@@ -402,13 +482,13 @@ function createTailwindMerge(createConfigFirst, ...createConfigRest) {
402
482
  const result = mergeClassList(classList, configUtils);
403
483
  cacheSet(classList, result);
404
484
  return result;
405
- }
406
- return function callTailwindMerge() {
407
- return functionToCall(twJoin.apply(null, arguments));
408
485
  };
409
- }
486
+ functionToCall = initTailwindMerge;
487
+ return (...args) => functionToCall(twJoin(...args));
488
+ };
489
+ const fallbackThemeArr = [];
410
490
  const fromTheme = key => {
411
- const themeGetter = theme => theme[key] || [];
491
+ const themeGetter = theme => theme[key] || fallbackThemeArr;
412
492
  themeGetter.isThemeGetter = true;
413
493
  return themeGetter;
414
494
  };