tailwind-sorter 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +176 -0
- data/Rakefile +2 -0
- data/exe/tailwind_sorter +24 -0
- data/lib/tailwind_sorter/sorter.rb +116 -0
- data/lib/tailwind_sorter/version.rb +3 -0
- data/lib/tailwind_sorter.rb +5 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ee5b0b99b5a652053bd30aa564181ff1d80e620ee224d2bafcf494bd55d6c891
|
4
|
+
data.tar.gz: 827baa288658e2d5ff141fad55b4325ac8ae8b176feb8ce5fd7de5b68793b4b0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b98372d3ead957a4dd5da63259868f8557d96a31200e92ae3f0a0c2ad862e9f00f57d4be784df5f649bbedb03cc493ff3394a44321a37272e26627c13a1a0773
|
7
|
+
data.tar.gz: f867b88d892c3412547d8c24eaa6b819f1735c828c15f950e01cd4ede39526b2a6b42b39b54d1c7af3953414af13b2022a4b4881dca7fba5d4cffde6b542c687
|
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Tailwind sorter
|
2
|
+
|
3
|
+
**A ruby gem to sort the [Tailwind CSS](https://tailwindcss.com) classes in your templates _the custom way_.**
|
4
|
+
|
5
|
+
The gem contains a standalone executable script that can work in two ways:
|
6
|
+
|
7
|
+
- it can edit the given file in place (especially useful when hooked up to a file changes watcher) or
|
8
|
+
- it can just generate warning messages suitable for [Overcommit](https://github.com/sds/overcommit),
|
9
|
+
[Lefthook](https://github.com/evilmartians/lefthook) or any other similar system (or people, if that’s what you
|
10
|
+
prefer).
|
11
|
+
|
12
|
+
Out of the box the script supports sorting classes in [Slim templates](http://slim-lang.com/) but can be configured for
|
13
|
+
anything else. The script also removes duplicate classes.
|
14
|
+
|
15
|
+
_This is what it looks like when Tailwind sorter is auto-run upon saving a changed template file in the RubyMine IDE:_
|
16
|
+
<img src="img/tailwind_sorter_intro.gif" alt="Automatically ordering CSS classes upon file saving"></img>
|
17
|
+
|
18
|
+
Please read the [accompanying post on dev.to](https://dev.to/nejremeslnici/tailwind-css-class-sorter-the-custom-way-35g5) for more details, if interested.
|
19
|
+
|
20
|
+
## Why?
|
21
|
+
|
22
|
+
We are aware of the other good solutions to sorting Tailwind classes but we’ve hit some limit in each of them:
|
23
|
+
|
24
|
+
- [The official Tailwind Prettier plugin](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) is great but does
|
25
|
+
not support all template formats, such as [Slim](http://slim-lang.com/).
|
26
|
+
- [Headwind](https://github.com/heybourn/headwind) is VS Code-only but we needed something in RubyMine and Overcommit,
|
27
|
+
too.
|
28
|
+
- There are ports to the environments we need
|
29
|
+
([Tailwind Formatter](https://plugins.jetbrains.com/plugin/13376-tailwind-formatter/) JetBrains plugin,
|
30
|
+
[RustyWind](https://github.com/avencera/rustywind) CLI tool) but none of these support non-standard template formats.
|
31
|
+
Also, we like to sort our Tailwind classes a bit differently than Headwind et al. default to.
|
32
|
+
|
33
|
+
In our opinion, especially since the [JIT mode](https://tailwindcss.com/docs/just-in-time-mode) has been introduced to
|
34
|
+
Tailwind, sorters operating over a huge static list of "default" pre-ordered classes are becoming less useful as each
|
35
|
+
developer will inevitably come to their **own unique set of classes** making it almost impossible to hard-wire a common
|
36
|
+
solution. This problem is even larger if you’ve added
|
37
|
+
[custom utility classes](https://tailwindcss.com/docs/adding-new-utilities) to your project.
|
38
|
+
|
39
|
+
Above all, it is **surprisingly easy** to create a custom sorting script – the one we use and present here is only
|
40
|
+
~110 lines. So, take this script and config as a **template for you to revise and adapt**.
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
Since version 0.3, the script has been packed into a gem so that it can be used directly from ruby as well as a standalone script (now a binstub). If you just want to grab the original ruby script, have a look at the [`original-script` tag](https://github.com/NejRemeslnici/tailwind-sorter/tree/original-script).
|
45
|
+
|
46
|
+
|
47
|
+
### Installing the gem
|
48
|
+
|
49
|
+
Install it directly:
|
50
|
+
|
51
|
+
```
|
52
|
+
gem install tailwind-sorter
|
53
|
+
```
|
54
|
+
|
55
|
+
or put it in your gemfile
|
56
|
+
|
57
|
+
```
|
58
|
+
# Gemfile
|
59
|
+
group :development do
|
60
|
+
gem "tailwind-sorter"
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Create a binstub, i.e. a standalone executable script for the sorter:
|
65
|
+
|
66
|
+
```sh
|
67
|
+
$ bundle binstubs tailwind-sorter
|
68
|
+
```
|
69
|
+
|
70
|
+
This will create a `bin/tailwind_sorter` binstub file.
|
71
|
+
|
72
|
+
The gem has **no dependencies**, apart from ruby (tested on ruby 3.0+) and its stdlib.
|
73
|
+
|
74
|
+
## Configuration
|
75
|
+
|
76
|
+
Without customizing, the script will – somehow – work but its full potential will be available only when properly
|
77
|
+
configured.
|
78
|
+
|
79
|
+
There are two important places to configure in the YAML file:
|
80
|
+
|
81
|
+
- **regular expressions** to match the classes in your files: out of the box, the script matches classes in the Slim
|
82
|
+
format (such as `section#flash-messages.hidden.mt-4`) and classes in the context of the `class` attribute in ruby /
|
83
|
+
Rails helpers (`link_to "E-shop", eshop_path, class: "no-underline font-bold red-100")`,
|
84
|
+
|
85
|
+
- **CSS classes order and grouping**: the `classes_order` section in the YAML file determines the order in which the
|
86
|
+
classes will be sorted. If you want the classes with Tailwind variants (such as `sm:`, `hover:` etc.) to always be
|
87
|
+
ordered towards the end of line, put the classes in one big group, otherwise split them into any groups you want and
|
88
|
+
they will be ordered last in the particular group.
|
89
|
+
|
90
|
+
Unknown (i.e. custom) classes will be **ordered first by default**. If you want them ordered last, you'll have to resort to using the original script and replacing the [`default_index`](https://github.com/NejRemeslnici/tailwind-sorter/blob/original-script/bin/tailwind_sorter.rb#L20) parameter in it with a big-enough number. We recommend ordering custom classes first though as in our opinion such classes usually bear more important meanings than the Tailwind ones and this setup also makes it easier to spot typos in class names.
|
91
|
+
|
92
|
+
The default sort order of the classes resembles the one of Headwind which, in turn, seems to be inspired by the order of
|
93
|
+
the sections in the [official Tailwind documentation](https://tailwindcss.com/docs).
|
94
|
+
|
95
|
+
More details about the configuration file can be found in [the wiki](https://github.com/NejRemeslnici/tailwind-sorter/wiki/The-config-file-explanation).
|
96
|
+
|
97
|
+
### Adding your unique set of Tailwind classes
|
98
|
+
|
99
|
+
The script works best if you only include the classes that you really use in your project. Once you grab all the classes
|
100
|
+
e.g. from your [purged](https://tailwindcss.com/docs/optimizing-for-production) / [JIT-ed](https://tailwindcss.com/docs/just-in-time-mode) production CSS bundle, you can initially reorder them using the following ruby snippet. Suppose you
|
101
|
+
have the ”default“ Tailwind classes sorted (taken e.g. from
|
102
|
+
[here](https://github.com/avencera/rustywind/blob/master/src/defaults.rs)), one per line, in
|
103
|
+
the `default_classes.txt` file and your own (unordered) classes in `our_classes.txt`. Then the sorting could go along these lines:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
head = File.readlines("default_classes.txt").map(&:strip)
|
107
|
+
our = File.readlines("our_classes.txt").map(&:strip)
|
108
|
+
sorted_classes = our.sort_by { |css_class| head.index(css_class) || 10_000 }
|
109
|
+
File.open("sorted_classes.txt", "w") { |f| f.write(sorted_classes.join("\n")) }
|
110
|
+
```
|
111
|
+
|
112
|
+
Then, you can grab these sorted classes, update the position of your own custom classes (you’ll find them near the end of
|
113
|
+
the `sorted_classes.txt` file) and move all of them to the appropriate sections of the YAML config file.
|
114
|
+
|
115
|
+
## Running the script
|
116
|
+
|
117
|
+
Of course, you can run the script manually, like so:
|
118
|
+
|
119
|
+
```sh
|
120
|
+
bin/tailwind_sorter app/views/my_template.html.slim
|
121
|
+
```
|
122
|
+
|
123
|
+
The script finds all css classes and reorders them in-place in the file.
|
124
|
+
|
125
|
+
You can tweak the configuration file path instead of the default `config/tailwind_sorter.yml` with the `-c` parameter:
|
126
|
+
|
127
|
+
```sh
|
128
|
+
bin/tailwind_sorter -c path/to/my/config_file.yml app/views/my_template.html.slim
|
129
|
+
```
|
130
|
+
|
131
|
+
## Running automatically via your IDE / editor
|
132
|
+
|
133
|
+
Perhaps the best way to run the script is using your editor or IDE. Many editors provide the possibility to watch your
|
134
|
+
edited files and **run arbitrary command when they are changed / saved**.
|
135
|
+
|
136
|
+
We use Tailwind sorter this way, the script is triggered by ”file watchers“ configured in RubyMine and it works great.
|
137
|
+
Have a look at [the wiki](https://github.com/NejRemeslnici/tailwind-sorter/wiki) for a guide to set up such integration.
|
138
|
+
|
139
|
+
## Guarding sort order via Overcommit
|
140
|
+
|
141
|
+
We use [Overcommit](https://github.com/sds/overcommit) to guard a common set of rules configured in our project during
|
142
|
+
each commit. Here, we provide
|
143
|
+
a [simple pre-commit hook](https://github.com/NejRemeslnici/tailwind-sorter/blob/main/.git-hooks/pre_commit/check_css_classes_order.rb)
|
144
|
+
and a sample
|
145
|
+
[configuration in the `.overcommit.yml` file](https://github.com/NejRemeslnici/tailwind-sorter/blob/main/.overcommit.yml#L31)
|
146
|
+
. The hook calls Tailwind sorter with the `-w` argument, asking it to not change the file but only print the ordering
|
147
|
+
problems found.
|
148
|
+
|
149
|
+
## Running the sorter from ruby code
|
150
|
+
|
151
|
+
To run the sorter from ruby code, use the following line:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
TailwindSorter::Sorter.run("app/views/my_template.html.slim")
|
155
|
+
```
|
156
|
+
|
157
|
+
You can also optionally pass in ome arguments such as `warn_only: true` to only show warning instead of overwriting the file or `config_file: "path/to/my/config_file.yml"` for custom config path.
|
158
|
+
|
159
|
+
## Running tests
|
160
|
+
|
161
|
+
```sh
|
162
|
+
bundle install # to install the rspec gem
|
163
|
+
bundle exec rspec
|
164
|
+
.............
|
165
|
+
|
166
|
+
Finished in 0.34583 seconds (files took 0.07635 seconds to load)
|
167
|
+
13 examples, 0 failures
|
168
|
+
```
|
169
|
+
|
170
|
+
## Answers for the curious
|
171
|
+
|
172
|
+
### But I heard ruby is slow. Is this fast enough?
|
173
|
+
|
174
|
+
When we initially reordered CSS classes in all our templates (~900 Slim files) with the script changing nearly 4000
|
175
|
+
lines, the whole process took less than 30 seconds. This makes the processing speed of approximately 30 files per
|
176
|
+
second. Judge for yourself if this is fast enough for your needs or not.
|
data/Rakefile
ADDED
data/exe/tailwind_sorter
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "tailwind_sorter/sorter"
|
4
|
+
|
5
|
+
warn_only = false
|
6
|
+
config_file = "config/tailwind_sorter.yml"
|
7
|
+
|
8
|
+
while (arg = ARGV.shift)
|
9
|
+
case arg
|
10
|
+
when "-w"
|
11
|
+
warn_only = true
|
12
|
+
when "-c"
|
13
|
+
config_file = ARGV.shift
|
14
|
+
else
|
15
|
+
file_name = arg
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
TailwindSorter::Sorter.run(file_name, warn_only: warn_only, config_file: config_file)
|
21
|
+
rescue ArgumentError => e
|
22
|
+
STDERR.puts e.message
|
23
|
+
exit 1
|
24
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module TailwindSorter
|
5
|
+
class Sorter
|
6
|
+
# reorders multiple variants, e.g.: "focus:sm:block" -> "sm:focus:block"
|
7
|
+
def sort_variants(css_class_with_variants, variants_order:)
|
8
|
+
*variants, css_class = css_class_with_variants.split(":")
|
9
|
+
return css_class_with_variants if variants.length <= 1
|
10
|
+
|
11
|
+
variants.sort_by! { |variant| variants_order.index(variant) }
|
12
|
+
"#{variants.join(':')}:#{css_class}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Constructs the sorting key for sorting CSS classes in the following way:
|
16
|
+
#
|
17
|
+
# group_index, variant1_index, variant2_index, class_index
|
18
|
+
# "sm:focus:flex" "01,01,11,0010"
|
19
|
+
# "flex" "01,00,00,0010"
|
20
|
+
# "nr-custom-class" "00,00,00,0000"
|
21
|
+
def sorting_key(css_class_with_variants, variants_order:, classes_order:, class_groups:, sort_order:, default_index: 0)
|
22
|
+
*variants, css_class = css_class_with_variants.split(":")
|
23
|
+
key = [
|
24
|
+
format("%02d", class_groups.index { |group| classes_order[group].include?(css_class) } || default_index),
|
25
|
+
format("%02d", variants_order.index(variants[0]) || default_index),
|
26
|
+
format("%02d", variants_order.index(variants[1]) || default_index),
|
27
|
+
format("%04d", sort_order.index(css_class) || default_index)
|
28
|
+
].join(",")
|
29
|
+
|
30
|
+
# puts "#{css_class_with_variants} #{key}"
|
31
|
+
key
|
32
|
+
end
|
33
|
+
|
34
|
+
def sort_classes(file, regexps:, variants_order:, classes_order:, default_index: 0, warn_only: false)
|
35
|
+
class_groups = classes_order.keys
|
36
|
+
sort_order = classes_order.values.flatten
|
37
|
+
|
38
|
+
infile = File.open(file)
|
39
|
+
outfile = Tempfile.create("#{File.basename(file)}.sorted")
|
40
|
+
|
41
|
+
calculate_sorting_key = lambda do |css_class_with_variants|
|
42
|
+
sorting_key(css_class_with_variants, variants_order:, classes_order:, class_groups:, sort_order:, default_index:)
|
43
|
+
end
|
44
|
+
|
45
|
+
changed = false
|
46
|
+
infile.each_line do |line|
|
47
|
+
line_number = infile.lineno
|
48
|
+
|
49
|
+
regexps.each do |_regexp_name, regexp_config|
|
50
|
+
regexp = regexp_config["regexp"]
|
51
|
+
prefix = regexp_config["class_prefix"]
|
52
|
+
split_by = "#{regexp_config['class_splitter']}#{prefix}"
|
53
|
+
|
54
|
+
next unless (match = line.match(regexp))
|
55
|
+
|
56
|
+
matched_classes = match["classes"]
|
57
|
+
# puts "#{line_number}: #{matched_classes}"
|
58
|
+
|
59
|
+
classes = matched_classes.split(split_by).map(&:strip).reject(&:empty?).uniq.map do |css_class_with_variants|
|
60
|
+
sort_variants(css_class_with_variants, variants_order: variants_order)
|
61
|
+
end
|
62
|
+
|
63
|
+
sorted_classes = classes.sort_by { |css_class| calculate_sorting_key.call(css_class) }
|
64
|
+
# puts sorted_classes.join('.')
|
65
|
+
|
66
|
+
if warn_only
|
67
|
+
orig_keys = classes.map { |css_class| calculate_sorting_key.call(css_class) }
|
68
|
+
sorted_keys = sorted_classes.map { |css_class| calculate_sorting_key.call(css_class) }
|
69
|
+
# puts orig_keys.inspect
|
70
|
+
# puts sorted_keys.inspect
|
71
|
+
if orig_keys != sorted_keys
|
72
|
+
puts("#{file}:#{line_number}:CSS classes are not sorted well. Please run 'tailwind_sorter #{file}'.")
|
73
|
+
end
|
74
|
+
else
|
75
|
+
orig_line = line.dup unless changed
|
76
|
+
line.sub!(regexp, "\\k<before>#{prefix}#{sorted_classes.join(split_by)}")
|
77
|
+
changed = true if !changed && line != orig_line
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
outfile.puts line
|
82
|
+
end
|
83
|
+
|
84
|
+
success = true
|
85
|
+
|
86
|
+
rescue StandardError
|
87
|
+
success = false
|
88
|
+
|
89
|
+
ensure
|
90
|
+
infile.close
|
91
|
+
outfile.close
|
92
|
+
|
93
|
+
success && changed ? FileUtils.mv(outfile, file) : File.unlink(outfile)
|
94
|
+
end
|
95
|
+
|
96
|
+
def run(file_name, warn_only: false, config_file: "config/tailwind_sorter.yml")
|
97
|
+
raise ArgumentError, "File '#{file_name}' does not exist" unless file_name && File.exist?(file_name)
|
98
|
+
raise ArgumentError, "Configuration file '#{config_file}' does not exist" unless config_file && File.exist?(config_file)
|
99
|
+
|
100
|
+
config = YAML.load_file(config_file)
|
101
|
+
file_extension = File.extname(file_name)
|
102
|
+
|
103
|
+
regexps = config["regexps"].select { |_k, v| v["file_extension"] == file_extension }
|
104
|
+
regexps.each { |_k, v| v["regexp"] = Regexp.new(v["regexp"]) }
|
105
|
+
|
106
|
+
sort_classes(file_name, regexps:,
|
107
|
+
classes_order: config["classes_order"],
|
108
|
+
variants_order: config["variants_order"],
|
109
|
+
warn_only:)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.run(...)
|
113
|
+
new.run(...)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tailwind-sorter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- NejŘemeslníci.cz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Simple but customizable sorter for TailwindCSS classes
|
14
|
+
email:
|
15
|
+
- support@nejremeslnici.cz
|
16
|
+
executables:
|
17
|
+
- tailwind_sorter
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- Rakefile
|
23
|
+
- exe/tailwind_sorter
|
24
|
+
- lib/tailwind_sorter.rb
|
25
|
+
- lib/tailwind_sorter/sorter.rb
|
26
|
+
- lib/tailwind_sorter/version.rb
|
27
|
+
homepage: https://github.com/NejRemeslnici/tailwind-sorter
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata:
|
31
|
+
homepage_uri: https://github.com/NejRemeslnici/tailwind-sorter
|
32
|
+
source_code_uri: https://github.com/NejRemeslnici/tailwind-sorter
|
33
|
+
changelog_uri: https://github.com/NejRemeslnici/tailwind-sorter
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '3.0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubygems_version: 3.4.19
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: Customizable TailwindCSS classes sorter
|
53
|
+
test_files: []
|