tailwind-sorter 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -0,0 +1,3 @@
1
+ module TailwindSorter
2
+ VERSION = "0.3.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "tailwind_sorter/sorter"
2
+ require "tailwind_sorter/version"
3
+
4
+ module TailwindSorter
5
+ 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: []