htmlnano 1.1.1 → 2.0.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.
@@ -0,0 +1,838 @@
1
+ # Modules
2
+
3
+ By default the modules should only perform safe transforms, see the module documentation below for details.
4
+ You can disable modules by passing `false` as option, and enable them by passing `true`.
5
+
6
+
7
+ ### collapseAttributeWhitespace
8
+ Collapse redundant white spaces in list-like attributes (`class`, `rel`, `ping`).
9
+
10
+ #### Example
11
+ Source:
12
+ ```html
13
+ <a class=" content page " style=" display: block; " href=" https://example.com"></a>
14
+ ```
15
+
16
+ Minified:
17
+ ```html
18
+ <a class="content page" style="display: block;" href="https://example.com"></a>
19
+ ```
20
+
21
+
22
+
23
+ ### collapseWhitespace
24
+ Collapses redundant white spaces (including new lines). It doesn’t affect white spaces in the elements `<style>`, `<textarea>`, `<script>` and `<pre>`.
25
+
26
+ #### Options
27
+ - `conservative` — collapses all redundant white spaces to 1 space (default)
28
+ - `aggressive` — collapses all whitespaces that are redundant and safe to remove
29
+ - `all` — collapses all redundant white spaces
30
+
31
+ #### Side effects
32
+
33
+ *all*
34
+ `<i>hello</i> <i>world</i>` or `<i>hello</i><br><i>world</i>` after minification will be rendered as `helloworld`.
35
+ To prevent that use either the default `conservative` option, or the `aggressive` option.
36
+
37
+ #### Example
38
+ Source:
39
+ ```html
40
+ <div>
41
+ hello world!
42
+ <a href="#">answer</a>
43
+ <style>div { color: red; } </style>
44
+ <main></main>
45
+ </div>
46
+ ```
47
+
48
+ Minified (with `all`):
49
+ ```html
50
+ <div>hello world!<a href="#">answer</a><style>div { color: red; } </style><main></main></div>
51
+ ```
52
+
53
+ Minified (with `aggressive`):
54
+ ```html
55
+ <div> hello world! <a href="#">answer</a> <style>div { color: red; } </style><main></main></div>
56
+ ```
57
+
58
+ Minified (with `conservative`):
59
+ ```html
60
+ <div> hello world! <a href="#">answer</a> <style>div { color: red; } </style> <main></main> </div>
61
+ ```
62
+
63
+
64
+ ### deduplicateAttributeValues
65
+ Remove duplicate values from list-like attributes (`class`, `rel`, `ping`).
66
+
67
+ #### Example
68
+ Source:
69
+ ```html
70
+ <div class="sidebar left sidebar"></div>
71
+ ```
72
+
73
+ Minified:
74
+ ```html
75
+ <div class="sidebar left"></div>
76
+ ```
77
+
78
+
79
+ ### removeComments
80
+ #### Options
81
+ - `safe` – removes all HTML comments except the conditional comments and [`<!--noindex--><!--/noindex-->`](https://yandex.com/support/webmaster/controlling-robot/html.xml) (default)
82
+ - `all` — removes all HTML comments
83
+ - A `RegExp` — only HTML comments matching the given regexp will be removed.
84
+ - A `Function` that returns boolean — removes HTML comments that can make the given callback function returns truthy value.
85
+
86
+ #### Example
87
+
88
+ Source:
89
+
90
+ ```js
91
+ {
92
+ removeComments: 'all'
93
+ }
94
+ ```
95
+
96
+ ```html
97
+ <div><!-- test --></div>
98
+ ```
99
+
100
+ Minified:
101
+
102
+ ```html
103
+ <div></div>
104
+ ```
105
+
106
+ Source:
107
+
108
+ ```js
109
+ {
110
+ removeComments: /<!--(\/)?noindex-->/
111
+ }
112
+ ```
113
+
114
+ ```html
115
+ <div><!--noindex-->this text will not be indexed<!--/noindex-->Lorem ipsum dolor sit amet<!--more-->Lorem ipsum dolor sit amet</div>
116
+ ```
117
+
118
+ Minified:
119
+
120
+ ```html
121
+ <div>this text will not be indexedLorem ipsum dolor sit amet<!--more-->Lorem ipsum dolor sit amet</div>
122
+ ```
123
+
124
+ Source:
125
+
126
+ ```js
127
+ {
128
+ removeComments: (comments) => {
129
+ if (comments.includes('noindex')) return true;
130
+ return false;
131
+ }
132
+ }
133
+ ```
134
+
135
+ ```html
136
+ <div><!--noindex-->this text will not be indexed<!--/noindex-->Lorem ipsum dolor sit amet<!--more-->Lorem ipsum dolor sit amet</div>
137
+ ```
138
+
139
+ Minified:
140
+
141
+ ```html
142
+ <div>this text will not be indexedLorem ipsum dolor sit amet<!--more-->Lorem ipsum dolor sit amet</div>
143
+ ```
144
+
145
+
146
+ ### removeEmptyAttributes
147
+ Removes empty [safe-to-remove](https://github.com/posthtml/htmlnano/blob/master/lib/modules/removeEmptyAttributes.es6) attributes.
148
+
149
+ #### Side effects
150
+ This module could break your styles or JS if you use selectors with attributes:
151
+ ```CSS
152
+ img[style=""] {
153
+ margin: 10px;
154
+ }
155
+ ```
156
+
157
+ #### Example
158
+ Source:
159
+ ```html
160
+ <img src="foo.jpg" alt="" style="">
161
+ ```
162
+
163
+ Minified:
164
+ ```html
165
+ <img src="foo.jpg" alt="">
166
+ ```
167
+
168
+ ### removeAttributeQuotes
169
+ Remove quotes around attributes when possible, see [HTML Standard - 12.1.2.3 Attributes - Unquoted attribute value syntax](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2).
170
+
171
+ #### Example
172
+ Source:
173
+ ```html
174
+ <div class="foo" title="hello world"></div>
175
+ ```
176
+
177
+ Minified:
178
+ ```html
179
+ <div class=foo title="hello world"></div>
180
+ ```
181
+
182
+ #### Notice
183
+ The feature is implemented by [posthtml-render's `quoteAllAttributes`](https://github.com/posthtml/posthtml-render#options), which is one of the PostHTML's option. So `removeAttributeQuotes` could be overriden by other PostHTML's plugins and PostHTML's configuration.
184
+
185
+ For example:
186
+
187
+ ```js
188
+ posthtml([
189
+ htmlnano({
190
+ removeAttributeQuotes: true
191
+ })
192
+ ]).process(html, {
193
+ quoteAllAttributes: true
194
+ })
195
+ ```
196
+
197
+ `removeAttributeQuotes` will not work because PostHTML's `quoteAllAttributes` takes the priority.
198
+
199
+ ### removeUnusedCss
200
+
201
+ Removes unused CSS inside `<style>` tags with either [uncss](https://github.com/uncss/uncss)
202
+ or [PurgeCSS](https://github.com/FullHuman/purgecss).
203
+
204
+ #### With uncss
205
+
206
+ You have to install `uncss` in order to use this feature:
207
+
208
+ ```bash
209
+ npm install --save-dev uncss
210
+ # if you prefer yarn
211
+ # yarn add --dev uncss
212
+ # if you prefer pnpm
213
+ # pnpm install --save-dev uncss
214
+ ```
215
+
216
+ You can also use a mainted fork [@novaatwarren/uncss](https://www.npmjs.com/package/@novaatwarren/uncss) instead.
217
+
218
+
219
+ ##### Options
220
+ See [the documentation of uncss](https://github.com/uncss/uncss) for all supported options.
221
+
222
+ uncss options can be passed directly to the `removeUnusedCss` module:
223
+ ```js
224
+ htmlnano.process(html, {
225
+ removeUnusedCss: {
226
+ ignore: ['.do-not-remove']
227
+ }
228
+ });
229
+ ```
230
+
231
+ The following uncss options are ignored if passed to the module:
232
+
233
+ - `stylesheets`
234
+ - `ignoreSheets`
235
+ - `raw`
236
+
237
+ #### With PurgeCSS
238
+
239
+ Use PurgeCSS instead of uncss by adding `tool: 'purgeCSS'` to the options.
240
+
241
+ You have to install `purgecss` in order to use this feature:
242
+
243
+ ```bash
244
+ npm install --save-dev purgecss
245
+ # if you prefer yarn
246
+ # yarn add --dev purgecss
247
+ # if you prefer pnpm
248
+ # pnpm install --save-dev purgecss
249
+ ```
250
+
251
+ ##### Options
252
+
253
+ See [the documentation of PurgeCSS](https://www.purgecss.com) for all supported options.
254
+
255
+ PurgeCSS options can be passed directly to the `removeUnusedCss` module:
256
+ ```js
257
+ htmlnano.process(html, {
258
+ removeUnusedCss: {
259
+ tool: 'purgeCSS',
260
+ safelist: ['.do-not-remove']
261
+ }
262
+ });
263
+ ```
264
+
265
+ The following PurgeCSS options are ignored if passed to the module:
266
+
267
+ - `content`
268
+ - `css`
269
+ - `extractors`
270
+
271
+ #### Example
272
+ Source:
273
+ ```html
274
+ <div class="b">
275
+ <style>
276
+ .a {
277
+ margin: 10px 10px 10px 10px;
278
+ }
279
+ .b {
280
+ color: #ff0000;
281
+ }
282
+ </style>
283
+ </div>
284
+ ```
285
+
286
+ Optimized:
287
+ ```html
288
+ <div class="b">
289
+ <style>
290
+ .b {
291
+ color: #ff0000;
292
+ }
293
+ </style>
294
+ </div>
295
+ ```
296
+
297
+
298
+ ### minifyCss
299
+ Minifies CSS with [cssnano](http://cssnano.co/) inside `<style>` tags and `style` attributes.
300
+
301
+ You have to install `cssnano` and `postcss` in order to use this feature:
302
+
303
+ ```bash
304
+ npm install --save-dev cssnano postcss
305
+ # if you prefer yarn
306
+ # yarn add --dev cssnano postcss
307
+ # if you prefer pnpm
308
+ # pnpm install --save-dev cssnano postcss
309
+ ```
310
+
311
+ #### Options
312
+ See [the documentation of cssnano](http://cssnano.co/docs/optimisations/) for all supported optimizations.
313
+ By default CSS is minified with preset `default`, which shouldn't have any side-effects.
314
+
315
+ To use another preset or disabled some optimizations pass options to `minifyCss` module:
316
+ ```js
317
+ htmlnano.process(html, {
318
+ minifyCss: {
319
+ preset: ['default', {
320
+ discardComments: {
321
+ removeAll: true,
322
+ },
323
+ }]
324
+ }
325
+ });
326
+ ```
327
+
328
+ #### Example
329
+ Source:
330
+ ```html
331
+ <div>
332
+ <style>
333
+ h1 {
334
+ margin: 10px 10px 10px 10px;
335
+ color: #ff0000;
336
+ }
337
+ </style>
338
+ </div>
339
+ ```
340
+
341
+ Minified:
342
+ ```html
343
+ <div>
344
+ <style>h1{margin:10px;color:red}</style>
345
+ </div>
346
+ ```
347
+
348
+
349
+ ### minifyJs
350
+ Minifies JS using [Terser](https://github.com/fabiosantoscode/terser) inside `<script>` tags.
351
+
352
+ You have to install `terser` in order to use this feature:
353
+
354
+ ```bash
355
+ npm install --save-dev terser
356
+ # if you prefer yarn
357
+ # yarn add --dev terser
358
+ # if you prefer pnpm
359
+ # pnpm install --save-dev terser
360
+ ```
361
+
362
+ #### Options
363
+ See [the documentation of Terser](https://github.com/fabiosantoscode/terser#api-reference) for all supported options.
364
+ Terser options can be passed directly to the `minifyJs` module:
365
+ ```js
366
+ htmlnano.process(html, {
367
+ minifyJs: {
368
+ output: { quote_style: 1 },
369
+ },
370
+ });
371
+ ```
372
+
373
+
374
+
375
+ #### Example
376
+ Source:
377
+ ```html
378
+ <div>
379
+ <script>
380
+ /* comment */
381
+ const foo = function () {
382
+
383
+ };
384
+ </script>
385
+ </div>
386
+ ```
387
+
388
+ Minified:
389
+ ```html
390
+ <div>
391
+ <script>const foo=function(){};</script>
392
+ </div>
393
+ ```
394
+
395
+
396
+ ### minifyJson
397
+ Minifies JSON inside `<script type="application/json"></script>`.
398
+
399
+ #### Example
400
+ Source:
401
+ ```html
402
+ <script type="application/json">
403
+ {
404
+ "user": "me"
405
+ }
406
+ </script>
407
+ ```
408
+
409
+ Minified:
410
+ ```html
411
+ <script type="application/json">{"user":"me"}</script>
412
+ ```
413
+
414
+
415
+ ### minifySvg
416
+ Minifies SVG inside `<svg>` tags using [SVGO](https://github.com/svg/svgo/).
417
+
418
+ #### Options
419
+ See [the documentation of SVGO](https://github.com/svg/svgo/blob/master/README.md) for all supported options.
420
+ SVGO options can be passed directly to the `minifySvg` module:
421
+ ```js
422
+ htmlnano.process(html, {
423
+ minifySvg: {
424
+ plugins: [
425
+ {
426
+ name: 'preset-default',
427
+ params: {
428
+ overrides: {
429
+ builtinPluginName: {
430
+ optionName: 'optionValue'
431
+ },
432
+ },
433
+ },
434
+ }
435
+ ]
436
+ }
437
+ });
438
+ ```
439
+
440
+ #### Example
441
+ Source:
442
+ ```html
443
+ <svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
444
+ <rect width="100%" height="100%" fill="red" />
445
+
446
+ <circle cx="150" cy="100" r="80" fill="green" />
447
+
448
+ <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
449
+ </svg>`
450
+ ```
451
+
452
+ Minified:
453
+ ```html
454
+ <svg baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="red"/><circle cx="150" cy="100" r="80" fill="green"/><text x="150" y="125" font-size="60" text-anchor="middle" fill="#fff">SVG</text></svg>
455
+ ```
456
+
457
+ ### minifyConditionalComments
458
+
459
+ Minify content inside conditional comments.
460
+
461
+ #### Example
462
+
463
+ Source:
464
+
465
+ ```html
466
+ <!--[if lte IE 7]>
467
+ <style type="text/css">
468
+ .title {
469
+ color: red;
470
+ }
471
+ </style>
472
+ <![endif]-->
473
+ ```
474
+
475
+ Minified:
476
+
477
+ ```html
478
+ <!--[if lte IE 7]><style>.title{color:red}</style><![endif]-->
479
+ ```
480
+
481
+ ### removeRedundantAttributes
482
+ Removes redundant attributes from tags if they contain default values:
483
+ - `method="get"` from `<form>`
484
+ - `type="text"` from `<input>`
485
+ - `type="submit"` from `<button>`
486
+ - `language="javascript"` and `type="text/javascript"` from `<script>`
487
+ - `charset` from `<script>` if it's an external script
488
+ - `media="all"` from `<style>` and `<link>`
489
+ - `type="text/css"` from `<link rel="stylesheet">`
490
+
491
+ #### Options
492
+ This module is disabled by default, change option to true to enable this module.
493
+
494
+ #### Side effects
495
+ This module could break your styles or JS if you use selectors with attributes:
496
+ ```CSS
497
+ form[method="get"] {
498
+ color: red;
499
+ }
500
+ ```
501
+
502
+ #### Example
503
+ Source:
504
+ ```html
505
+ <form method="get">
506
+ <input type="text">
507
+ </form>
508
+ ```
509
+
510
+ Minified:
511
+ ```html
512
+ <form>
513
+ <input>
514
+ </form>
515
+ ```
516
+
517
+
518
+ ### collapseBooleanAttributes
519
+ Collapses boolean attributes (like `disabled`) to the minimized form.
520
+
521
+ #### Options
522
+ If your document uses [AMP](https://www.ampproject.org/), set the `amphtml` flag
523
+ to collapse additonal, AMP-specific boolean attributes:
524
+ ```Json
525
+ "collapseBooleanAttributes": {
526
+ "amphtml": true
527
+ }
528
+ ```
529
+
530
+ #### Side effects
531
+ This module could break your styles or JS if you use selectors with attributes:
532
+ ```CSS
533
+ button[disabled="disabled"] {
534
+ color: red;
535
+ }
536
+ ```
537
+
538
+ #### Example
539
+ Source:
540
+ ```html
541
+ <button disabled="disabled">click</button>
542
+ <script defer=""></script>
543
+ ```
544
+
545
+ Minified:
546
+ ```html
547
+ <button disabled>click</button>
548
+ <script defer></script>
549
+ ```
550
+
551
+
552
+ ### mergeStyles
553
+ Merges multiple `<style>` with the same `media` and `type` into one tag.
554
+ `<style scoped>...</style>` are skipped.
555
+
556
+ #### Example
557
+ Source:
558
+ ```html
559
+ <style>h1 { color: red }</style>
560
+ <style media="print">div { color: blue }</style>
561
+
562
+ <style type="text/css" media="print">a {}</style>
563
+ <style>div { font-size: 20px }</style>
564
+ ```
565
+
566
+ Minified:
567
+ ```html
568
+ <style>h1 { color: red } div { font-size: 20px }</style>
569
+ <style media="print">div { color: blue } a {}</style>
570
+ ```
571
+
572
+
573
+ ### mergeScripts
574
+ Merge multiple `<script>` with the same attributes (`id, class, type, async, defer`) into one (last) tag.
575
+
576
+ #### Side effects
577
+ It could break your code if the tags with different attributes share the same variable scope.
578
+ See the example below.
579
+
580
+ #### Example
581
+ Source:
582
+ ```html
583
+ <script>const foo = 'A:1';</script>
584
+ <script class="test">foo = 'B:1';</script>
585
+ <script type="text/javascript">foo = 'A:2';</script>
586
+ <script defer>foo = 'C:1';</script>
587
+ <script>foo = 'A:3';</script>
588
+ <script defer="defer">foo = 'C:2';</script>
589
+ <script class="test" type="text/javascript">foo = 'B:2';</script>
590
+ ```
591
+
592
+ Minified:
593
+ ```html
594
+ <script>const foo = 'A:1'; foo = 'A:2'; foo = 'A:3';</script>
595
+ <script defer="defer">foo = 'C:1'; foo = 'C:2';</script>
596
+ <script class="test" type="text/javascript">foo = 'B:1'; foo = 'B:2';</script>
597
+ ```
598
+
599
+
600
+ ### custom
601
+ It's also possible to pass custom modules in the minifier.
602
+ As a function:
603
+ ```js
604
+ const options = {
605
+ custom: function (tree, options) {
606
+ // Some minification
607
+ return tree;
608
+ }
609
+ };
610
+ ```
611
+
612
+ Or as a list of functions:
613
+ ```js
614
+ const options = {
615
+ custom: [
616
+ function (tree, options) {
617
+ // Some minification
618
+ return tree;
619
+ },
620
+
621
+ function (tree, options) {
622
+ // Some other minification
623
+ return tree;
624
+ }
625
+ ]
626
+ };
627
+ ```
628
+
629
+ `options` is an object with all options that were passed to the plugin.
630
+
631
+ ### sortAttributesWithLists
632
+ Sort values in list-like attributes (`class`, `rel`, `ping`).
633
+
634
+ The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.
635
+
636
+ #### Options
637
+
638
+ - `alphabetical`: Default option. Sort attribute values in alphabetical order.
639
+ - `frequency`: Sort attribute values by frequency.
640
+
641
+ #### Example
642
+
643
+ **alphabetical**
644
+
645
+ Source:
646
+ ```html
647
+ <div class="foo baz bar">click</div>
648
+ ```
649
+
650
+ Processed:
651
+ ```html
652
+ <div class="bar baz foo">click</div>
653
+ ```
654
+
655
+ **frequency**
656
+
657
+ Source:
658
+ ```html
659
+ <div class="foo baz bar"></div><div class="bar foo"></div>
660
+ ```
661
+
662
+ Processed:
663
+ ```html
664
+ <div class="foo bar baz"></div><div class="foo bar"></div>
665
+ ```
666
+
667
+ ### sortAttributes
668
+ Sort attributes inside elements.
669
+
670
+ The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.
671
+
672
+ #### Options
673
+
674
+ - `alphabetical`: Default option. Sort attributes in alphabetical order.
675
+ - `frequency`: Sort attributes by frequency.
676
+
677
+ #### Example
678
+
679
+ **alphabetical**
680
+
681
+ Source:
682
+ ```html
683
+ <input type="text" class="form-control" name="testInput" autofocus="" autocomplete="off" id="testId">
684
+ ```
685
+
686
+ Processed:
687
+ ```html
688
+ <input autocomplete="off" autofocus="" class="form-control" id="testId" name="testInput" type="text">
689
+ ```
690
+
691
+ **frequency**
692
+
693
+ Source:
694
+ ```html
695
+ <input type="text" class="form-control" name="testInput" id="testId">
696
+ <a id="testId" href="#" class="testClass"></a>
697
+ <img width="20" src="../images/image.png" height="40" alt="image" class="cls" id="id2">
698
+ ```
699
+
700
+ Processed:
701
+ ```html
702
+ <input class="form-control" id="testId" type="text" name="testInput">
703
+ <a class="testClass" id="testId" href="#"></a>
704
+ <img class="cls" id="id2" width="20" src="../images/image.png" height="40" alt="image">
705
+ ```
706
+
707
+ ### minifyUrls
708
+ Convert absolute URL to relative URL using [relateurl](https://www.npmjs.com/package/relateurl).
709
+
710
+ You have to install `relateurl`, `terser` and `srcset` in order to use this feature:
711
+
712
+ ```bash
713
+ npm install --save-dev relateurl terser srcset
714
+ # if you prefer yarn
715
+ # yarn add --dev relateurl terser srcset
716
+ # if you prefer pnpm
717
+ # pnpm install --save-dev relateurl terser srcset
718
+ ```
719
+
720
+ #### Options
721
+
722
+ The base URL to resolve against. Support `String` & `URL`.
723
+
724
+ ```js
725
+ htmlnano.process(html, {
726
+ minifyUrls: 'https://example.com' // Valid configuration
727
+ });
728
+ ```
729
+
730
+ ```js
731
+ htmlnano.process(html, {
732
+ minifyUrls: new URL('https://example.com') // Valid configuration
733
+ });
734
+ ```
735
+
736
+ ```js
737
+ htmlnano.process(html, {
738
+ minifyUrls: false // The module will be disabled
739
+ });
740
+ ```
741
+
742
+ ```js
743
+ htmlnano.process(html, {
744
+ minifyUrls: true // Invalid configuration, the module will be disabled
745
+ });
746
+ ```
747
+
748
+ #### Example
749
+
750
+ **Basic Usage**
751
+
752
+ Configuration:
753
+
754
+ ```js
755
+ htmlnano.process(html, {
756
+ minifyUrls: 'https://example.com'
757
+ });
758
+ ```
759
+
760
+ Source:
761
+
762
+ ```html
763
+ <a href="https://example.com/foo/bar/baz">bar</a>
764
+ ```
765
+
766
+ Minified:
767
+
768
+ ```html
769
+ <a href="foo/bar/baz">bar</a>
770
+ ```
771
+
772
+ **With sub-directory**
773
+
774
+ Configuration:
775
+
776
+ ```js
777
+ htmlnano.process(html, {
778
+ minifyUrls: 'https://example.com/foo/baz/'
779
+ });
780
+ ```
781
+
782
+ Source:
783
+
784
+ ```html
785
+ <a href="https://example.com/foo/bar">bar</a>
786
+ ```
787
+
788
+ Minified:
789
+
790
+ ```html
791
+ <a href="../bar">bar</a>
792
+ ```
793
+
794
+ ### removeOptionalTags
795
+ Remove certain tags that can be omitted, see [HTML Standard - 13.1.2.4 Optional tags](https://html.spec.whatwg.org/multipage/syntax.html#optional-tags).
796
+
797
+ #### Example
798
+
799
+ Source:
800
+
801
+ ```html
802
+ <html><head><title>Title</title></head><body><p>Hi</p></body></html>
803
+ ```
804
+
805
+ Minified:
806
+
807
+ ```html
808
+ <title>Title</title><p>Hi</p>
809
+ ```
810
+
811
+ #### Notice
812
+ Due to [the limitation of PostHTML](https://github.com/posthtml/htmlnano/issues/99), htmlnano can't remove only the start tag or the end tag of an element. Currently, htmlnano only supports removing the following optional tags, as htmlnano can remove their start tag and end tag at the same time:
813
+
814
+ - `html`
815
+ - `head`
816
+ - `body`
817
+ - `colgroup`
818
+ - `tbody`
819
+
820
+ ### normalizeAttributeValues
821
+
822
+ Normalize casing of attribute values.
823
+
824
+ The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.
825
+
826
+ #### Example
827
+
828
+ Source:
829
+
830
+ ```html
831
+ <form method="GET"></form>
832
+ ```
833
+
834
+ Minified:
835
+
836
+ ```html
837
+ <form method="get"></form>
838
+ ```