Skip to content

Commit 799b395

Browse files
Reimplements HtmlParser using Symfony DomCrawler (#21)
Thanks a lot to @JohnathonKoster 🚀
1 parent 49a31b8 commit 799b395

File tree

9 files changed

+438
-20
lines changed

9 files changed

+438
-20
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,50 @@ Let's assume you want to style links inside lists differently than a general lin
102102

103103
The ordering does not matter. Classify will take care of that for you.
104104

105+
## Working With CSS Frameworks Like Tailwind CSS
106+
107+
Some CSS frameworks utilize JIT compiling, or have some other means of purging CSS classes from production builds to reduce file sizes. Classify provides a Laravel Artisan command to generate a JavaScript configuration file containing all Classify class names that can be used when configuring your CSS build process.
108+
109+
Running the following command from the root of the project:
110+
111+
```
112+
php artisan classify:export
113+
```
114+
115+
Would create a new (or update an existing) `classify.config.js` JavaScript file containing your Classify class name configuration:
116+
117+
```js
118+
module.exports.classes = [
119+
"mt-8",
120+
"first:mt-0",
121+
"text-xs",
122+
"uppercase",
123+
];
124+
```
125+
126+
This configuration file can be used in conjunction with your CSS framework's build tools. For example, we can add our Classify class names to the Tailwind CSS 3 safe list ([https://tailwindcss.com/docs/content-configuration#safelisting-classes](https://tailwindcss.com/docs/content-configuration#safelisting-classes)):
127+
128+
Within `tailwind.config.js`:
129+
130+
```js
131+
const classify = require('./classify.config');
132+
133+
module.exports = {
134+
content: [
135+
'./pages/**/*.{html,js}'
136+
'./components/**/*.{html,js}',
137+
],
138+
safelist: [
139+
...classify.classes,
140+
'bg-red-500',
141+
'text-3xl',
142+
'lg:text-4xl',
143+
]
144+
// ...
145+
}
146+
```
147+
148+
> **Important**: Remember to run the `php artisan classify:export` command after making these changes to your Tailwind CSS configuration file before you build your front-end assets!
105149
106150
# More about us
107151
- [www.statamic-agency.com](https://statamic-agency.com)

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
"require": {
3131
"php": "^7.4 || ^8.0",
3232
"illuminate/support": "^8.0",
33-
"statamic/cms": ">=3.0.38 || 3.1.* || 3.2.*"
33+
"statamic/cms": ">=3.0.38 || 3.1.* || 3.2.*",
34+
"symfony/dom-crawler": "^5.4",
35+
"symfony/css-selector": "^5.4"
3436
},
3537
"require-dev": {
3638
"orchestra/testbench": "^6.0",

src/Commands/Export.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace VV\Classify\Commands;
4+
5+
use Illuminate\Console\Command;
6+
7+
class Export extends Command
8+
{
9+
protected $signature = 'classify:export';
10+
protected $description = 'Generates a JSON configuration file containing all Classify class names.';
11+
12+
/**
13+
* Generates a JavaScript configuration file containing all Classify class names.
14+
*/
15+
public static function getConfigurationContents(): string
16+
{
17+
$allConfig = config('classify');
18+
$classNames = [];
19+
20+
foreach ($allConfig as $config) {
21+
foreach ($config as $classList) {
22+
$classes = collect(explode(' ', $classList))->reject(function ($class) {
23+
return strlen(trim($class)) == 0;
24+
})->values()->all();
25+
26+
foreach ($classes as $class) {
27+
if (! in_array($class, $classNames)) {
28+
$classNames[] = $class;
29+
}
30+
}
31+
}
32+
}
33+
34+
return 'module.exports.classes = '.json_encode($classNames, JSON_PRETTY_PRINT).';';
35+
}
36+
37+
public function handle()
38+
{
39+
file_put_contents(base_path('classify.config.js'), self::getConfigurationContents());
40+
}
41+
}

src/HtmlParser.php

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,42 @@
22

33
namespace VV\Classify;
44

5+
use Symfony\Component\DomCrawler\Crawler;
6+
57
class HtmlParser implements ClassifyParser
68
{
79
public function parse(Tag $tag, string $value): string
810
{
9-
return preg_replace(
10-
$this->defineRegexPattern($tag),
11-
$this->defineReplacement($tag),
12-
$value
13-
);
14-
}
11+
$selector = $tag->tag;
1512

16-
private function defineRegexPattern(Tag $tag): string
17-
{
18-
$pattern = '';
13+
if (count($tag->before) > 0) {
14+
$selector = implode(' > ', $tag->before).' > '.$selector;
1915

20-
foreach ($tag->before as $name) {
21-
$pattern .= "<{$name}[^>]*>[^<]*";
16+
$firstPart = strtolower($tag->before[0]);
17+
18+
// Guard against producing selectors in the form: body > body > span.
19+
if ($firstPart != 'body') {
20+
$selector = 'body > '.$selector;
21+
}
2222
}
2323

24-
return "/({$pattern})(<{$tag->tag})(?! class)/iU";
25-
}
24+
$crawler = new Crawler($value);
25+
$nodes = $crawler->filter($selector);
2626

27-
private function defineReplacement(Tag $tag): string
28-
{
29-
return "$1<{$tag->tag} class=\"{$tag->classes}\"";
27+
if (count($nodes) == 0) {
28+
return $value;
29+
}
30+
31+
foreach ($nodes as $node) {
32+
$node->setAttribute('class', $tag->classes);
33+
}
34+
35+
// Generate the HTML with our class adjustments made.
36+
$result = $crawler->html();
37+
38+
// Removes the <body> and </body> tags that get added since it's a fragment.
39+
$result = substr($result, 6);
40+
41+
return substr($result, 0, -7);
3042
}
3143
}

src/Modifiers/Classify.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ public function index($value, $params, $context)
2626
* Convert style segment information into the Tag class.
2727
* They will get Sorted by count to parse nested tags first.
2828
*/
29+
2930
$segments = collect($this->getStyleSegments($styleSet))
3031
->map(fn ($classes, $tags) => new Tag($tags, $classes))
31-
->sortByDesc('count');
32+
->sortBy('count');
3233

3334
$segments->each(function ($segment) use (&$value) {
3435
$value = app(ClassifyParser::class)->parse($segment, $value);

src/ServiceProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ class ServiceProvider extends AddonServiceProvider
1111
\VV\Classify\Modifiers\Classify::class,
1212
];
1313

14+
protected $commands = [
15+
\VV\Classify\Commands\Export::class,
16+
];
17+
1418
public function boot()
1519
{
1620
parent::boot();

src/Tag.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,15 @@ public function __construct(string $tags, string $classes)
3939
*/
4040
private function convertTagsToArray(string $tags): array
4141
{
42-
return explode(' ', $tags);
42+
return collect(explode(' ', $tags))->reject(function ($tag) {
43+
// Removes explicitly entered > symbols. These will be added
44+
// back later when constructing the final CSS-style selector.
45+
if ($tag == '>') {
46+
return true;
47+
}
48+
49+
return strlen(trim($tag)) == 0;
50+
})->values()->all();
4351
}
4452

4553
/*

0 commit comments

Comments
 (0)