tail_merge 0.2.0 → 0.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.
- checksums.yaml +4 -4
- data/Cargo.lock +8 -8
- data/Cargo.toml +1 -0
- data/README.md +43 -1
- data/Rakefile +1 -1
- data/benchmark/benchmark.rb +98 -0
- data/ext/tail_merge/Cargo.toml +2 -1
- data/ext/tail_merge/extconf.rb +1 -1
- data/ext/tail_merge/src/lib.rs +24 -3
- data/lib/tail_merge/version.rb +2 -2
- data/lib/tail_merge.rb +21 -3
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df9120474172d53dc79d7592c20832032545e05572a437c63b7d342cfd2bb33a
|
4
|
+
data.tar.gz: 24e6b1bc37558804a3a4fa4afdd06b4a4d21e5ace5ed349def07a5d0b2768584
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 904e825d0f09aace9c811225fa59c455eb473d958843b587be89e91dcf2d080ac373c73147699ed3f93ebfed28f9570e2e58025d95eb48ebce4c92e889c25e53
|
7
|
+
data.tar.gz: 83604251ac57755a2106dfab3544379d8f234fffe3440599ea03a2c217085aa1b9dff583ba3a6041df9e2bffc72713f89aaaf6273c4942d1c1838ee0e622464e
|
data/Cargo.lock
CHANGED
@@ -141,6 +141,14 @@ version = "2.7.4"
|
|
141
141
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
142
142
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
143
143
|
|
144
|
+
[[package]]
|
145
|
+
name = "merger"
|
146
|
+
version = "0.1.0"
|
147
|
+
dependencies = [
|
148
|
+
"magnus",
|
149
|
+
"rustui_merge",
|
150
|
+
]
|
151
|
+
|
144
152
|
[[package]]
|
145
153
|
name = "minimal-lexical"
|
146
154
|
version = "0.2.1"
|
@@ -278,14 +286,6 @@ dependencies = [
|
|
278
286
|
"unicode-ident",
|
279
287
|
]
|
280
288
|
|
281
|
-
[[package]]
|
282
|
-
name = "tail_merge"
|
283
|
-
version = "0.1.0"
|
284
|
-
dependencies = [
|
285
|
-
"magnus",
|
286
|
-
"rustui_merge",
|
287
|
-
]
|
288
|
-
|
289
289
|
[[package]]
|
290
290
|
name = "unicode-ident"
|
291
291
|
version = "1.0.18"
|
data/Cargo.toml
CHANGED
data/README.md
CHANGED
@@ -102,6 +102,48 @@ TailMerge.merge "px-2 py-1 bg-red hover:bg-dark-red p-3 bg-[#B91C1C]"
|
|
102
102
|
=> "hover:bg-dark-red p-3 bg-[#B91C1C]"
|
103
103
|
```
|
104
104
|
|
105
|
+
## More speed?
|
106
|
+
|
107
|
+
You can create an instance of TailMerge and call `merge` on it instead of on the `TailMerge` class. This will cache the results of the merge.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
tail_merge_instance = TailMerge.new
|
111
|
+
tail_merge_instance.merge %w[px-2 py-1 bg-red hover:bg-dark-red p-3 bg-[#B91C1C]]
|
112
|
+
=> "hover:bg-dark-red p-3 bg-[#B91C1C]" # Write to cache, still fast though
|
113
|
+
|
114
|
+
# Second time, same key, read from cache
|
115
|
+
tail_merge_instance.merge %w[px-2 py-1 bg-red hover:bg-dark-red p-3 bg-[#B91C1C]]
|
116
|
+
=> "hover:bg-dark-red p-3 bg-[#B91C1C]" # Read from cache, much faster!
|
117
|
+
|
118
|
+
# Third time, string key instead of array, read from same cache
|
119
|
+
tail_merge_instance.merge "px-2 py-1 bg-red hover:bg-dark-red p-3 bg-[#B91C1C]"
|
120
|
+
=> "hover:bg-dark-red p-3 bg-[#B91C1C]" # Read from cache, much faster!
|
121
|
+
```
|
122
|
+
|
123
|
+
This caching technique was inspired by [[Tailwind Merge](https://github.com/dcastil/tailwind-merge)](https://github.com/gjtorikian/tailwind_merge).
|
124
|
+
|
125
|
+
## Benchmark
|
126
|
+
|
127
|
+
So how fast/much faster is TailMerge?
|
128
|
+
|
129
|
+
I've benchmarked TailMerge with an instance (cached) and without an instance against Tailwind Merge with (cached) and without a merger instance, and these are the results:
|
130
|
+
|
131
|
+
```
|
132
|
+
user system total real
|
133
|
+
Rust: TailMerge.merge (all samples): 0.216178 0.001744 0.217922 ( 0.219441)
|
134
|
+
Rust: Cached TailMerge.merge (all samples): 0.005465 0.000092 0.005557 ( 0.005581)
|
135
|
+
Ruby: TailwindMerge each time (all samples): 50.391383 0.494058 50.885441 ( 52.272354)
|
136
|
+
Ruby:Cached TailwindMerge (all samples): 0.011672 0.000140 0.011812 ( 0.011813)
|
137
|
+
```
|
138
|
+
|
139
|
+
As you can see TailMerge is much faster using pure Ruby to merge classes.
|
140
|
+
|
141
|
+
The benchmark loops through an array of strings and arrays and merges them 1000 times.
|
142
|
+
|
143
|
+
The difference between the cached runs, obviously, is much smaller as we are basically benchmarking the cache lookup and not the actual merge.
|
144
|
+
|
145
|
+
In reality we will not deal with 1000 merges to be done per page and I suspect you'd be much closer to the non-cached runs.
|
146
|
+
|
105
147
|
## Contributing
|
106
148
|
|
107
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/abuisman/tail_merge.
|
149
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/abuisman/tail_merge . Merging will be done at my own pace and discretion.
|
data/Rakefile
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark"
|
4
|
+
require "tailwind_merge"
|
5
|
+
|
6
|
+
require "bundler/setup"
|
7
|
+
require "tail_merge"
|
8
|
+
|
9
|
+
samples = [
|
10
|
+
["relative"],
|
11
|
+
["self-center"],
|
12
|
+
["self-start"],
|
13
|
+
["shadow-inner", "size-4", "text-xs"],
|
14
|
+
["shadow-inner", "size-7"],
|
15
|
+
["size-10"],
|
16
|
+
["static"],
|
17
|
+
["upload-attachment", "flex-none", "rounded-3xl", "min-h-16", "group", "relative"],
|
18
|
+
["w-full", "py-2", "px-2", "rounded-md", "w-44"],
|
19
|
+
"relative",
|
20
|
+
"self-center",
|
21
|
+
"self-start",
|
22
|
+
"shadow-inner size-4 text-xs",
|
23
|
+
"shadow-inner size-7",
|
24
|
+
"size-10",
|
25
|
+
"static",
|
26
|
+
"upload-attachment flex-none rounded-3xl min-h-16 group relative",
|
27
|
+
"w-full py-2 px-2 rounded-md w-44",
|
28
|
+
["p-4", "px-2", "py-6", "m-3", "mx-8", "my-2", "bg-blue-500", "bg-red-600", "text-sm", "text-lg", "font-bold", "font-normal", "rounded-lg", "rounded-xl", "shadow-md", "shadow-lg", "hover:bg-blue-700", "hover:bg-red-800", "focus:ring-2", "focus:ring-4"],
|
29
|
+
["grid", "flex", "inline-flex", "grid-cols-3", "grid-cols-4", "gap-2", "gap-4", "items-center", "items-start", "justify-between", "justify-center", "p-8", "p-4", "bg-gray-100", "bg-white", "border", "border-2", "rounded-full", "rounded-md"],
|
30
|
+
["transform", "scale-100", "scale-110", "rotate-45", "rotate-90", "translate-x-2", "translate-x-4", "skew-y-3", "skew-y-6", "transition", "duration-200", "duration-500", "ease-in", "ease-out", "delay-150", "delay-300"],
|
31
|
+
["w-full", "w-1/2", "w-3/4", "h-screen", "h-full", "h-32", "min-h-0", "min-h-full", "max-w-xs", "max-w-xl", "overflow-hidden", "overflow-scroll", "object-cover", "object-contain", "opacity-75", "opacity-100"],
|
32
|
+
["text-left", "text-center", "text-right", "text-justify", "tracking-wide", "tracking-wider", "leading-tight", "leading-loose", "uppercase", "lowercase", "capitalize", "normal-case", "truncate", "line-clamp-2", "line-clamp-3"],
|
33
|
+
["border-t", "border-b", "border-l", "border-r", "border-solid", "border-dashed", "border-red-500", "border-blue-600", "divide-y", "divide-x", "divide-gray-200", "divide-blue-300", "ring-2", "ring-4", "ring-offset-2"],
|
34
|
+
["cursor-pointer", "cursor-wait", "select-none", "select-text", "resize", "resize-none", "z-10", "z-50", "float-left", "float-right", "clear-both", "clear-none", "box-border", "box-content"],
|
35
|
+
["bg-opacity-50", "bg-opacity-75", "backdrop-blur-sm", "backdrop-blur-lg", "backdrop-filter", "filter", "brightness-90", "brightness-110", "contrast-75", "contrast-125", "saturate-50", "saturate-200"],
|
36
|
+
["focus:outline-none", "focus:ring-2", "focus:ring-offset-2", "focus:border-blue-500", "hover:scale-105", "hover:rotate-3", "active:scale-95", "disabled:opacity-50", "disabled:cursor-not-allowed"],
|
37
|
+
["sm:text-lg", "md:text-xl", "lg:text-2xl", "xl:text-3xl", "2xl:text-4xl", "sm:w-1/2", "md:w-2/3", "lg:w-3/4", "xl:w-full", "2xl:max-w-screen-xl", "sm:p-4", "md:p-6", "lg:p-8", "xl:p-10"],
|
38
|
+
["dark:bg-gray-800", "dark:text-white", "dark:border-gray-600", "dark:hover:bg-gray-700", "dark:focus:ring-blue-800", "bg-white", "text-black", "border-gray-200", "hover:bg-gray-100"],
|
39
|
+
["group-hover:scale-110", "group-hover:rotate-6", "group-focus:outline-none", "group-active:scale-95", "peer-checked:bg-blue-500", "peer-checked:text-white", "peer-disabled:opacity-50"],
|
40
|
+
["animate-spin", "animate-pulse", "animate-bounce", "animate-ping", "motion-safe:animate-spin", "motion-reduce:animate-none", "transition-all", "duration-300", "ease-in-out", "delay-150"],
|
41
|
+
["space-x-4", "space-x-reverse", "space-y-6", "space-y-reverse", "gap-x-4", "gap-y-6", "place-items-center", "place-content-center", "place-self-center", "content-center"],
|
42
|
+
["from-blue-500", "to-purple-500", "via-pink-500", "bg-gradient-to-r", "bg-gradient-to-br", "text-transparent", "bg-clip-text", "bg-origin-border", "bg-no-repeat", "bg-cover"],
|
43
|
+
["columns-2", "columns-3", "break-inside-avoid", "break-after-column", "aspect-square", "aspect-video", "object-right-top", "object-left-bottom", "isolation-auto", "mix-blend-multiply"],
|
44
|
+
["first:pt-0", "last:pb-0", "odd:bg-gray-50", "even:bg-white", "first-letter:text-7xl", "first-line:uppercase", "selection:bg-yellow-200", "selection:text-black"],
|
45
|
+
["[mask-type:luminance]", "[mask-type:alpha]", "[transform-style:preserve-3d]", "[clip-path:circle(50%)]", "[-webkit-text-stroke:2px]", "[text-align-last:justify]"],
|
46
|
+
["will-change-scroll", "will-change-transform", "scroll-smooth", "scroll-mt-2", "scroll-pb-4", "overscroll-contain", "touch-pan-right", "touch-manipulation"],
|
47
|
+
["hyphens-auto", "hyphens-manual", "text-underline-offset-2", "text-decoration-thickness-2", "indent-8", "indent-16", "vertical-align-sub", "vertical-align-super"]
|
48
|
+
]
|
49
|
+
|
50
|
+
require 'benchmark'
|
51
|
+
require 'tail_merge'
|
52
|
+
require 'tailwind_merge'
|
53
|
+
|
54
|
+
# Pre-initialize cached mergers
|
55
|
+
cached_merger = TailwindMerge::Merger.new
|
56
|
+
|
57
|
+
tail_merge_instance = TailMerge.new
|
58
|
+
|
59
|
+
# Normalize all samples to strings
|
60
|
+
normalized_samples = samples.map { |classes| classes.is_a?(Array) ? classes.join(' ') : classes }
|
61
|
+
|
62
|
+
puts "Benchmarking class merging strategies (whole set)..."
|
63
|
+
puts "-" * 50
|
64
|
+
puts
|
65
|
+
|
66
|
+
Benchmark.bm(30) do |x|
|
67
|
+
x.report("Rust: TailMerge.merge (all samples):") do
|
68
|
+
1000.times do
|
69
|
+
normalized_samples.each do |classes|
|
70
|
+
TailMerge.merge(classes)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
x.report("Rust: Cached TailMerge.merge (all samples):") do
|
76
|
+
1000.times do
|
77
|
+
normalized_samples.each do |classes|
|
78
|
+
tail_merge_instance.merge(classes)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
x.report("Ruby: TailwindMerge each time (all samples):") do
|
84
|
+
1000.times do
|
85
|
+
normalized_samples.each do |classes|
|
86
|
+
TailwindMerge::Merger.new.merge(classes)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
x.report("Ruby:Cached TailwindMerge (all samples):") do
|
92
|
+
1000.times do
|
93
|
+
normalized_samples.each do |classes|
|
94
|
+
cached_merger.merge(classes)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/ext/tail_merge/Cargo.toml
CHANGED
data/ext/tail_merge/extconf.rb
CHANGED
data/ext/tail_merge/src/lib.rs
CHANGED
@@ -14,6 +14,7 @@ fn merge_tailwind_classes(args: &[Value]) -> Result<RString, Error> {
|
|
14
14
|
|
15
15
|
// ---------- 2. collect class tokens ------------------------------------
|
16
16
|
let mut tokens = Vec::<String>::new();
|
17
|
+
let is_string_input = matches!(args[0].clone().try_convert::<RString>(), Ok(_));
|
17
18
|
match args[0].clone().try_convert::<RString>() {
|
18
19
|
Ok(rstr) => tokens.extend(rstr.to_string()?.split_whitespace().map(str::to_owned)),
|
19
20
|
Err(_) => {
|
@@ -25,6 +26,24 @@ fn merge_tailwind_classes(args: &[Value]) -> Result<RString, Error> {
|
|
25
26
|
}
|
26
27
|
}
|
27
28
|
|
29
|
+
// Early returns for simple cases
|
30
|
+
if is_string_input {
|
31
|
+
let rstr: RString = args[0].clone().try_convert()?;
|
32
|
+
let s = rstr.to_string()?;
|
33
|
+
if !s.contains(' ') {
|
34
|
+
// Single class string, return as-is
|
35
|
+
return Ok(RString::new(&s));
|
36
|
+
}
|
37
|
+
} else {
|
38
|
+
// Array input
|
39
|
+
if tokens.is_empty() {
|
40
|
+
return Ok(RString::new(""));
|
41
|
+
}
|
42
|
+
if tokens.len() == 1 {
|
43
|
+
return Ok(RString::new(&tokens[0]));
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
28
47
|
// ---------- 3. extract options -----------------------------------------
|
29
48
|
let mut prefix: Option<String> = None;
|
30
49
|
let mut separator: Option<String> = None;
|
@@ -100,8 +119,10 @@ fn merge_tailwind_classes(args: &[Value]) -> Result<RString, Error> {
|
|
100
119
|
|
101
120
|
#[magnus::init]
|
102
121
|
fn init() -> Result<(), Error> {
|
103
|
-
let
|
104
|
-
|
105
|
-
|
122
|
+
let ruby = Ruby::get().unwrap();
|
123
|
+
let tail_merge_class: magnus::RClass = ruby.class_object().const_get("TailMerge")?;
|
124
|
+
let merger_module = tail_merge_class.define_module("Merger")?;
|
125
|
+
// Assuming merge_tailwind_classes is your target function
|
126
|
+
merger_module.define_singleton_method("perform", function!(merge_tailwind_classes, -1))?;
|
106
127
|
Ok(())
|
107
128
|
}
|
data/lib/tail_merge/version.rb
CHANGED
data/lib/tail_merge.rb
CHANGED
@@ -1,9 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "tail_merge/version"
|
4
|
-
require_relative "tail_merge/
|
4
|
+
require_relative "tail_merge/merger"
|
5
5
|
|
6
|
-
|
6
|
+
# Main class for merging tailwind classes.
|
7
|
+
class TailMerge
|
7
8
|
class Error < StandardError; end
|
8
|
-
|
9
|
+
|
10
|
+
def self.merge(classes, options = {})
|
11
|
+
Merger.perform(classes, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
@options = options
|
18
|
+
@class_hash = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge(classes)
|
22
|
+
return "" if classes.empty?
|
23
|
+
|
24
|
+
classes = classes.join(" ") if classes.is_a?(Array)
|
25
|
+
@class_hash[classes] ||= Merger.perform(classes, options)
|
26
|
+
end
|
9
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tail_merge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Achilleas Buisman
|
@@ -36,6 +36,7 @@ files:
|
|
36
36
|
- Cargo.toml
|
37
37
|
- README.md
|
38
38
|
- Rakefile
|
39
|
+
- benchmark/benchmark.rb
|
39
40
|
- ext/tail_merge/Cargo.toml
|
40
41
|
- ext/tail_merge/extconf.rb
|
41
42
|
- ext/tail_merge/src/lib.rs
|