Skip to content

Commit 987ec03

Browse files
authored
Merge pull request #50 from w-masahiro-ct/fix-and-improve-implementations
Fix and improve implementation
2 parents 8662a14 + 531b367 commit 987ec03

File tree

9 files changed

+98
-130
lines changed

9 files changed

+98
-130
lines changed

lib/tailwind_merge.rb

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Merger
1919
SPLIT_CLASSES_REGEX = /\s+/
2020

2121
def initialize(config: {})
22-
@config = if config.fetch(:theme, nil)
22+
@config = if config.key?(:theme)
2323
merge_configs(config)
2424
else
2525
TailwindMerge::Config::DEFAULTS.merge(config)
@@ -30,12 +30,10 @@ def initialize(config: {})
3030
end
3131

3232
def merge(classes)
33-
if classes.is_a?(Array)
34-
classes = classes.compact.join(" ")
35-
end
33+
normalized = classes.is_a?(Array) ? classes.compact.join(" ") : classes.to_s
3634

37-
@cache.getset(classes) do
38-
merge_class_list(classes).freeze
35+
@cache.getset(normalized) do
36+
merge_class_list(normalized).freeze
3937
end
4038
end
4139

@@ -45,37 +43,38 @@ def merge(classes)
4543
# @example 'float'
4644
# @example 'hover:focus:bg-color'
4745
# @example 'md:!pr'
48-
class_groups_in_conflict = []
49-
class_names = class_list.strip.split(SPLIT_CLASSES_REGEX)
50-
51-
result = ""
46+
trimmed = class_list.strip
47+
return "" if trimmed.empty?
5248

53-
i = class_names.length - 1
49+
class_groups_in_conflict = Set.new
5450

55-
loop do
56-
break if i < 0
51+
merged_classes = []
5752

58-
original_class_name = class_names[i]
53+
trimmed.split(SPLIT_CLASSES_REGEX).reverse_each do |original_class_name|
54+
modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position =
55+
split_modifiers(original_class_name, separator: @config[:separator])
5956

60-
modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position = split_modifiers(original_class_name, separator: @config[:separator])
57+
actual_base_class_name = if maybe_postfix_modifier_position
58+
base_class_name[0...maybe_postfix_modifier_position]
59+
else
60+
base_class_name
61+
end
6162

62-
actual_base_class_name = maybe_postfix_modifier_position ? base_class_name[0...maybe_postfix_modifier_position] : base_class_name
63+
has_postfix_modifier = maybe_postfix_modifier_position ? true : false
6364
class_group_id = @class_utils.class_group_id(actual_base_class_name)
6465

6566
unless class_group_id
66-
unless maybe_postfix_modifier_position
67-
# not a Tailwind class
68-
result = original_class_name + (!result.empty? ? " " + result : result)
69-
i -= 1
67+
unless has_postfix_modifier
68+
# Not a Tailwind class
69+
merged_classes << original_class_name
7070
next
7171
end
7272

7373
class_group_id = @class_utils.class_group_id(base_class_name)
7474

7575
unless class_group_id
76-
# not a Tailwind class
77-
result = original_class_name + (!result.empty? ? " " + result : result)
78-
i -= 1
76+
# Not a Tailwind class
77+
merged_classes << original_class_name
7978
next
8079
end
8180

@@ -87,25 +86,20 @@ def merge(classes)
8786
modifier_id = has_important_modifier ? "#{variant_modifier}#{IMPORTANT_MODIFIER}" : variant_modifier
8887
class_id = "#{modifier_id}#{class_group_id}"
8988

90-
# Tailwind class omitted due to pre-existing conflict
91-
if class_groups_in_conflict.include?(class_id)
92-
i -= 1
93-
next
94-
end
89+
# Tailwind class omitted due to conflict
90+
next if class_groups_in_conflict.include?(class_id)
9591

96-
class_groups_in_conflict.push(class_id)
92+
class_groups_in_conflict << class_id
9793

98-
@class_utils.get_conflicting_class_group_ids(class_group_id, has_postfix_modifier).each do |group|
99-
class_groups_in_conflict.push("#{modifier_id}#{group}")
94+
@class_utils.get_conflicting_class_group_ids(class_group_id, has_postfix_modifier).each do |conflicting_id|
95+
class_groups_in_conflict << "#{modifier_id}#{conflicting_id}"
10096
end
10197

102-
# no conflict!
103-
result = original_class_name + (!result.empty? ? " " + result : result)
104-
105-
i -= 1
98+
# Tailwind class not in conflict
99+
merged_classes << original_class_name
106100
end
107101

108-
result
102+
merged_classes.reverse.join(" ")
109103
end
110104
end
111105
end

lib/tailwind_merge/class_utils.rb

Lines changed: 39 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def initialize(config)
1616
def class_group_id(class_name)
1717
class_parts = class_name.split(CLASS_PART_SEPARATOR)
1818

19-
# Classes like `-inset-1` produce an empty string as first classPart.
19+
# Classes like `-inset-1` produce an empty string as first class_part.
2020
# Assume that classes for negative values are used correctly and remove it from class_parts.
2121
class_parts.shift if class_parts.first == "" && class_parts.length != 1
2222

@@ -30,52 +30,47 @@ def get_group_recursive(class_parts, class_part_object)
3030

3131
next_class_part_object = class_part_object[:next_part][current_class_part]
3232

33-
class_group_from_next_class_part = next_class_part_object ? get_group_recursive(class_parts[1..-1], next_class_part_object) : nil
34-
35-
return class_group_from_next_class_part if class_group_from_next_class_part
33+
if next_class_part_object
34+
class_group_from_next_class_part = get_group_recursive(class_parts.drop(1), next_class_part_object)
35+
return class_group_from_next_class_part if class_group_from_next_class_part
36+
end
3637

3738
return if class_part_object[:validators].empty?
3839

3940
class_rest = class_parts.join(CLASS_PART_SEPARATOR)
4041

4142
result = class_part_object[:validators].find do |v|
4243
validator = v[:validator]
43-
44-
if from_theme?(validator)
45-
validator.call(@config)
46-
else
47-
validator.call(class_rest)
48-
end
44+
from_theme?(validator) ? validator.call(@config) : validator.call(class_rest)
4945
end
5046

51-
result.nil? ? result : result[:class_group_id]
47+
result&.fetch(:class_group_id, nil)
5248
end
5349

5450
def get_conflicting_class_group_ids(class_group_id, has_postfix_modifier)
5551
conflicts = @config[:conflicting_class_groups][class_group_id] || []
5652

5753
if has_postfix_modifier && @config[:conflicting_class_group_modifiers][class_group_id]
58-
return [...conflicts, ...@config[:conflicting_class_group_modifiers][class_group_id]]
54+
return [*conflicts, *@config[:conflicting_class_group_modifiers][class_group_id]]
5955
end
6056

6157
conflicts
6258
end
6359

6460
private def create_class_map(config)
65-
theme = config[:theme]
6661
prefix = config[:prefix]
6762
class_map = {
6863
next_part: {},
6964
validators: [],
7065
}
7166

7267
prefixed_class_group_entries = get_prefixed_class_group_entries(
73-
config[:class_groups].map { |cg| [cg[0], cg[1]] },
68+
config[:class_groups].map { |group_id, group_classes| [group_id, group_classes] },
7469
prefix,
7570
)
7671

77-
prefixed_class_group_entries.each do |(class_group_id, class_group)|
78-
process_classes_recursively(class_group, class_map, class_group_id, theme)
72+
prefixed_class_group_entries.each do |class_group_id, class_group|
73+
process_classes_recursively(class_group, class_map, class_group_id)
7974
end
8075

8176
class_map
@@ -84,53 +79,43 @@ def get_conflicting_class_group_ids(class_group_id, has_postfix_modifier)
8479
private def get_prefixed_class_group_entries(class_group_entries, prefix)
8580
return class_group_entries if prefix.nil?
8681

87-
class_group_entries.map do |(class_group_id, class_group)|
82+
class_group_entries.map do |class_group_id, class_group|
8883
prefixed_class_group = class_group.map do |class_definition|
89-
next("#{prefix}#{class_definition}") if class_definition.is_a?(String)
90-
91-
next(class_definition.transform_keys { |key| "#{prefix}#{key}" }) if class_definition.is_a?(Hash)
92-
93-
class_definition
84+
if class_definition.is_a?(String)
85+
"#{prefix}#{class_definition}"
86+
elsif class_definition.is_a?(Hash)
87+
class_definition.transform_keys { |key| "#{prefix}#{key}" }
88+
else
89+
class_definition
90+
end
9491
end
9592

9693
[class_group_id, prefixed_class_group]
9794
end
9895
end
9996

100-
private def process_classes_recursively(class_group, class_part_object, class_group_id, theme)
97+
private def process_classes_recursively(class_group, class_part_object, class_group_id)
10198
class_group.each do |class_definition|
10299
if class_definition.is_a?(String)
103100
class_part_object_to_edit = class_definition.empty? ? class_part_object : get_class_part(class_part_object, class_definition)
104101
class_part_object_to_edit[:class_group_id] = class_group_id
105-
next
106-
end
107-
108-
if class_definition.is_a?(Proc)
102+
elsif class_definition.is_a?(Proc)
109103
if from_theme?(class_definition)
104+
process_classes_recursively(class_definition.call(@config), class_part_object, class_group_id)
105+
else
106+
class_part_object[:validators] << {
107+
validator: class_definition,
108+
class_group_id: class_group_id,
109+
}
110+
end
111+
else
112+
class_definition.each do |key, nested_class_group|
110113
process_classes_recursively(
111-
class_definition.call(@config),
112-
class_part_object,
114+
nested_class_group,
115+
get_class_part(class_part_object, key),
113116
class_group_id,
114-
theme,
115117
)
116-
next
117118
end
118-
119-
class_part_object[:validators].push({
120-
validator: class_definition,
121-
class_group_id: class_group_id,
122-
})
123-
124-
next
125-
end
126-
127-
class_definition.each do |(key, class_group)|
128-
process_classes_recursively(
129-
class_group,
130-
get_class_part(class_part_object, key),
131-
class_group_id,
132-
theme,
133-
)
134119
end
135120
end
136121
end
@@ -153,16 +138,13 @@ def get_conflicting_class_group_ids(class_group_id, has_postfix_modifier)
153138
end
154139

155140
private def get_group_id_for_arbitrary_property(class_name)
156-
if ARBITRARY_PROPERTY_REGEX.match?(class_name)
157-
match = ARBITRARY_PROPERTY_REGEX.match(class_name) || ""
158-
arbitrary_property_class_name = match[1] || ""
159-
property = arbitrary_property_class_name[0...arbitrary_property_class_name.index(":")]
160-
161-
if !property.nil? && !property.empty?
162-
# uses two dots here because one dot is used as prefix for class groups in plugins
163-
"arbitrary..#{property}"
164-
end
165-
end
141+
match = ARBITRARY_PROPERTY_REGEX.match(class_name)
142+
return unless match
143+
144+
property = match[1].to_s.split(":", 2).first
145+
146+
# Use two dots here because one dot is used as prefix for class groups in plugins
147+
"arbitrary..#{property}" if property && !property.empty?
166148
end
167149

168150
private def from_theme?(validator)

lib/tailwind_merge/config.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "set"
4+
35
module TailwindMerge
46
module Config
57
include Validators
@@ -30,7 +32,7 @@ module Config
3032
"skew",
3133
"space",
3234
"translate",
33-
]
35+
].freeze
3436
THEME_KEYS.each do |key|
3537
const_set(key.upcase.tr("-", "_"), ->(config) { config[:theme].fetch(key, nil) })
3638
end

lib/tailwind_merge/modifier_utils.rb

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ module TailwindMerge
44
module ModifierUtils
55
IMPORTANT_MODIFIER = "!"
66

7-
def split_modifiers(class_name, separator: nil)
8-
separator ||= ":"
7+
def split_modifiers(class_name, separator: ":")
98
separator_length = separator.length
10-
seperator_is_single_char = separator_length == 1
11-
first_seperator_char = separator[0]
9+
separator_is_single_char = (separator_length == 1)
10+
first_separator_char = separator[0]
1211

1312
modifiers = []
1413
bracket_depth = 0
1514
modifier_start = 0
16-
postfix_modifier_position = 0
15+
postfix_modifier_position = nil
1716

1817
class_name.each_char.with_index do |char, index|
1918
if bracket_depth.zero?
20-
if char == first_seperator_char && (seperator_is_single_char || class_name[index..(index + separator_length - 1)] == separator)
21-
modifiers << class_name[modifier_start..index]
19+
if char == first_separator_char && (separator_is_single_char || class_name[index, separator_length] == separator)
20+
modifiers << class_name[modifier_start...index]
2221
modifier_start = index + separator_length
2322
next
2423
elsif char == "/"
@@ -27,17 +26,17 @@ def split_modifiers(class_name, separator: nil)
2726
end
2827
end
2928

30-
if char == "["
31-
bracket_depth += 1
32-
elsif char == "]"
33-
bracket_depth -= 1
34-
end
29+
bracket_depth += 1 if char == "["
30+
bracket_depth -= 1 if char == "]"
3531
end
3632

37-
base_class_name_with_important_modifier = modifiers.empty? ? class_name : class_name[modifier_start..-1]
33+
base_class_name_with_important_modifier = modifiers.empty? ? class_name : class_name[modifier_start..]
3834
has_important_modifier = base_class_name_with_important_modifier.start_with?(IMPORTANT_MODIFIER)
39-
base_class_name = has_important_modifier ? base_class_name_with_important_modifier[1..-1] : base_class_name_with_important_modifier
40-
maybe_postfix_modifier_position = postfix_modifier_position && postfix_modifier_position > modifier_start ? postfix_modifier_position - modifier_start : false
35+
base_class_name = has_important_modifier ? base_class_name_with_important_modifier[1..] : base_class_name_with_important_modifier
36+
37+
maybe_postfix_modifier_position = if postfix_modifier_position && postfix_modifier_position > modifier_start
38+
postfix_modifier_position - modifier_start
39+
end
4140

4241
[modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position]
4342
end
@@ -46,25 +45,22 @@ def split_modifiers(class_name, separator: nil)
4645
# - Predefined modifiers are sorted alphabetically
4746
# - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
4847
def sort_modifiers(modifiers)
49-
if modifiers.length <= 1
50-
return modifiers
51-
end
48+
return modifiers if modifiers.size <= 1
5249

5350
sorted_modifiers = []
5451
unsorted_modifiers = []
5552

5653
modifiers.each do |modifier|
57-
is_arbitrary_variant = modifier[0] == "["
58-
59-
if is_arbitrary_variant
60-
sorted_modifiers.push(unsorted_modifiers.sort, modifier)
61-
unsorted_modifiers = []
54+
if modifier.start_with?("[")
55+
sorted_modifiers.concat(unsorted_modifiers.sort)
56+
sorted_modifiers << modifier
57+
unsorted_modifiers.clear
6258
else
63-
unsorted_modifiers.push(modifier)
59+
unsorted_modifiers << modifier
6460
end
6561
end
6662

67-
sorted_modifiers.push(...unsorted_modifiers.sort)
63+
sorted_modifiers.concat(unsorted_modifiers.sort)
6864

6965
sorted_modifiers
7066
end

0 commit comments

Comments
 (0)