• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WikiZEIT / HTMLMinifier / 25859434769

14 May 2026 12:14PM UTC coverage: 94.681% (-0.4%) from 95.122%
25859434769

push

github

jcubic
handle minification of json+ld

12 of 13 new or added lines in 1 file covered. (92.31%)

89 of 94 relevant lines covered (94.68%)

11.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.68
/src/WikiZEIT/HTMLMinifier.php
1
<?php
2

3
/*
4
 * This is part of WikiZEIT/html-minifier package
5
 * Copyright (c) 2026 Jakub T. Jankiewicz <https://jakub.jankiewicz.org>
6
 * Released under MIT license
7
 */
8

9
namespace WikiZEIT;
10

11
use Wikimedia\Minify\JavaScriptMinifier;
12
use Wikimedia\Minify\CSSMin;
13

14
class HTMLMinifier
15
{
16
    private array $preservedBlocks = [];
17
    private int $placeholderIndex = 0;
18

19
    /** @var string[] Regex patterns for HTML comments to keep */
20
    private array $preservedComments = [
21
        '/^\[if\s/',
22
    ];
23

24
    public static function minify(string $html): string
18✔
25
    {
26
        return (new self())->run($html);
18✔
27
    }
28

29
    public function preserveComment(string $pattern): self
2✔
30
    {
31
        $this->preservedComments[] = $pattern;
2✔
32
        return $this;
2✔
33
    }
34

35
    public function run(string $html): string
20✔
36
    {
37
        $this->preservedBlocks = [];
20✔
38
        $this->placeholderIndex = 0;
20✔
39

40
        $html = $this->preserveBlocks($html);
20✔
41
        $html = $this->removeComments($html);
19✔
42
        $html = $this->collapseWhitespace($html);
19✔
43
        $html = $this->restoreBlocks($html);
19✔
44
        return trim($html);
19✔
45
    }
46

47
    private function placeholder(): string
13✔
48
    {
49
        return '<!--MINIFY_PRESERVE_' . ($this->placeholderIndex++) . '-->';
13✔
50
    }
51

52
    private function preserveBlocks(string $html): string
20✔
53
    {
54
        // Preserve <pre>, <code>, <textarea> content verbatim
55
        $html = preg_replace_callback(
20✔
56
            '#(<\s*(pre|code|textarea)\b[^>]*>)(.*?)(</\s*\2\s*>)#si',
20✔
57
            function ($m) {
20✔
58
                $ph = $this->placeholder();
4✔
59
                $this->preservedBlocks[$ph] = $m[0];
4✔
60
                return $ph;
4✔
61
            },
20✔
62
            $html
20✔
63
        );
20✔
64

65
        // Extract and minify <script> blocks
66
        $html = preg_replace_callback(
20✔
67
            '#(<\s*script\b[^>]*>)(.*?)(</\s*script\s*>)#si',
20✔
68
            function ($m) {
20✔
69
                $ph = $this->placeholder();
8✔
70
                $attrs = $m[1];
8✔
71
                if (preg_match('/\btype\s*=\s*["\']application\/(?:ld\+)?json["\']/i', $attrs)) {
8✔
72
                    $minified = $this->minifyJson(trim($m[2]));
3✔
73
                    $this->preservedBlocks[$ph] = $attrs . $minified . $m[3];
2✔
74
                } elseif (preg_match('/\btype\s*=\s*["\'](?!text\/javascript)[^"\']+["\']/i', $attrs)) {
5✔
75
                    $this->preservedBlocks[$ph] = $m[0];
1✔
76
                } else {
77
                    $minified = $this->minifyJs(trim($m[2]));
4✔
78
                    $this->preservedBlocks[$ph] = $attrs . $minified . $m[3];
4✔
79
                }
80
                return $ph;
7✔
81
            },
20✔
82
            $html
20✔
83
        );
20✔
84

85
        // Extract and minify <style> blocks
86
        $html = preg_replace_callback(
19✔
87
            '#(<\s*style\b[^>]*>)(.*?)(</\s*style\s*>)#si',
19✔
88
            function ($m) {
19✔
89
                $ph = $this->placeholder();
3✔
90
                $minified = $this->minifyCss(trim($m[2]));
3✔
91
                $this->preservedBlocks[$ph] = $m[1] . $minified . $m[3];
3✔
92
                return $ph;
3✔
93
            },
19✔
94
            $html
19✔
95
        );
19✔
96

97
        return $html;
19✔
98
    }
99

100
    private function removeComments(string $html): string
19✔
101
    {
102
        return preg_replace_callback(
19✔
103
            '/<!--(.*?)-->/s',
19✔
104
            function ($m) {
19✔
105
                if (str_starts_with($m[0], '<!--MINIFY_PRESERVE_')) {
16✔
106
                    return $m[0];
12✔
107
                }
108
                foreach ($this->preservedComments as $pattern) {
5✔
109
                    if (preg_match($pattern, $m[1])) {
5✔
110
                        return $m[0];
3✔
111
                    }
112
                }
113
                return '';
3✔
114
            },
19✔
115
            $html
19✔
116
        );
19✔
117
    }
118

119
    private function collapseWhitespace(string $html): string
19✔
120
    {
121
        $html = preg_replace('/\s+/', ' ', $html);
19✔
122
        $html = preg_replace('/>\s+</', '><', $html);
19✔
123
        return $html;
19✔
124
    }
125

126
    private function restoreBlocks(string $html): string
19✔
127
    {
128
        foreach (array_reverse($this->preservedBlocks, true) as $ph => $content) {
19✔
129
            $html = str_replace($ph, $content, $html);
12✔
130
        }
131
        return $html;
19✔
132
    }
133

134
    private function minifyJs(string $js): string
4✔
135
    {
136
        if (trim($js) === '') {
4✔
137
            return '';
1✔
138
        }
139
        try {
140
            return JavaScriptMinifier::minify($js);
3✔
141
        } catch (\Exception $e) {
×
142
            return $js;
×
143
        }
144
    }
145

146
    private function minifyJson(string $json): string
3✔
147
    {
148
        if (trim($json) === '') {
3✔
NEW
149
            return '';
×
150
        }
151
        $data = json_decode($json);
3✔
152
        if (json_last_error() !== JSON_ERROR_NONE) {
3✔
153
            throw new \InvalidArgumentException(
1✔
154
                'Invalid JSON in <script> block: ' . json_last_error_msg()
1✔
155
            );
1✔
156
        }
157
        return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
2✔
158
    }
159

160
    private function minifyCss(string $css): string
3✔
161
    {
162
        if (trim($css) === '') {
3✔
163
            return '';
1✔
164
        }
165
        try {
166
            return CSSMin::minify($css);
2✔
167
        } catch (\Exception $e) {
×
168
            return $css;
×
169
        }
170
    }
171
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc